Repository: timoniq/telegrinder (language: python)

Issue:
Update Bot API types based on the latest schema changes
This is an automated PR. Please check the diff, and the action logs, to check for any funky behaviour.
rename SingleChoice -> Choice (more viby)

exception in task leads to task cancellation
### ⚪️ Checklist ⚪️
* [x] ❕ I'm sure this is a **bug** in the **framework** ❕
* [x] 🔎 I have searched for **similar** *bugs* in `issues/PRs` 🔍
* [x] 🟢 I have the **latest** version of the **framework** installed 🟢

### ✏️ Description ✏️
<!-- Describe the bug. -->
```
The bot task is closed if there are issues with the internet connection on the client's side.
```

### 🪄 Code example 🪄
<!--  Provide a minimal example. -->
```python
from envparse import Env
from telegrinder import Telegrinder, API, Token
from telegrinder import logger
from telegrinder.client import AiohttpClient
import asyncio
from app.handlers import dps

api = API(Token.from_env())
bot = Telegrinder(api)
env = Env()
logger.set_level(env.str("LOGGING"))
SUPPLIERS_URL_API = env.str("SUPPLIERS_URL_API")
DISCOUNT_URL_API = env.str("DISCOUNT_URL_API")


@bot.loop_wrapper.interval(minutes=10)
async def update():
    client = AiohttpClient()
    headers = {}
    response = await client.request_json(
        SUPPLIERS_URL_API + "/api/v3/orders/new", "GET", headers=headers
    )

    # other things


if __name__ == "__main__":
    for dp in dps:
        bot.dispatch.load(dp)
    bot.run_forever()
```

### 📝 Logs 📝
<!-- Provide logs/errors to review the issue. -->
```
Traceback (most recent call last):
File "main.py", line 1, in <module>
...
telegrinder | ERROR   | 2024-07-15 00:50:52 | telegrinder.bot.polling.polling:listen:119 > Client connection failed, attempted to reconnect...
telegrinder | ERROR   | 2024-07-15 14:18:57 | telegrinder.bot.polling.polling:listen:119 > Client connection failed, attempted to reconnect...
telegrinder | ERROR   | 2024-07-15 21:03:56 | telegrinder.tools.loop_wrapper.loop_wrapper:run_event_loop:117 > Server disconnected
  File "/project/venv/lib/python3.12/site-packages/telegrinder/tools/loop_wrapper/loop_wrapper.py", line 115, in run_event_loop
    task_result.result()
  File "/project/venv/lib/python3.12/site-packages/telegrinder/tools/loop_wrapper/loop_wrapper.py", line 51, in __call__
    await self.handler(*args, **kwargs)
  File "/project/app/__main__.py", line 27, in update
    response = await client.request_json(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/project/venv/lib/python3.12/site-packages/telegrinder/client/aiohttp.py", line 66, in request_json
    response = await self.request_raw(url, method, data, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/project/venv/lib/python3.12/site-packages/telegrinder/client/aiohttp.py", line 49, in request_raw
    async with self.session.request(
  File "/project/venv/lib/python3.12/site-packages/aiohttp/client.py", line 1197, in __aenter__
    self._resp = await self._coro
                 ^^^^^^^^^^^^^^^^
  File "/project/venv/lib/python3.12/site-packages/aiohttp/client.py", line 608, in _request
    await resp.start(conn)
  File "/project/venv/lib/python3.12/site-packages/aiohttp/client_reqrep.py", line 976, in start
    message, payload = await protocol.read()  # type: ignore[union-attr]
                       ^^^^^^^^^^^^^^^^^^^^^
  File "/project/venv/lib/python3.12/site-packages/aiohttp/streams.py", line 640, in read
    await self._waiter
aiohttp.client_exceptions.ServerDisconnectedError: Server disconnected
Task was destroyed but it is pending!
task: <Task pending name='Task-35647' coro=<Dispatch.feed() done, defined at /project/venv/lib/python3.12/site-packages/telegrinder/bot/dispatch/dispatch.py:136> wait_for=<Future pending cb=[Task.task_wakeup()]>>
telegrinder | ERROR | 2024-07-16 14:37:55 | telegrinder.bot.polling.polling:listen:119 > Client connection failed, attempted to reconnect...
```



Discussion / hints:

Fix patch:
diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
index 9f70c09e..5c7759d7 100644
--- a/.github/ISSUE_TEMPLATE/bug.md
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -1,6 +1,6 @@
 ---
 name: "\U0001F41B Bug report \U0001F41B"
-about: "Reporting a bug."
+about: "\U0001F41B Reporting a bug. \U0001F41B"
 title: ""
 labels: ["bug", "fix"]
 assignees: "luwqz1"
@@ -12,21 +12,19 @@ assignees: "luwqz1"
 * [ ] 🟢 I have the **latest** version of the **framework** installed 🟢
 
 ### ✏️ Description ✏️
-<!-- Describe the bug -->
-`When I..., it...`
+<!-- Describe the bug. -->
+_No response_
+
 
 ### 🪄 Code example 🪄
-<!--  Provide a minimal reproducible example -->
+<!--  Provide a minimal example. -->
 ```python
-from telegrinder import API, Telegrinder, Token
-
-...
+from telegrinder import ...
 ```
 
 ### 📝 Logs 📝
-<!-- Provide logs/errors to review the issue -->
-```
+<!-- Provide logs/errors to review the issue. -->
+```console
 Traceback (most recent call last):
 File "main.py", line 1, in <module>
-...
-```
\ No newline at end of file
+```
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index f9874976..4a5a141b 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,5 @@
 blank_issues_enabled: false
 contact_links:
-  - name: 💬 Telegram forum 💬
-    url: https://t.me/botoforum/148235
-    about: ✏️ You can ask questions about the framework. ✏️
\ No newline at end of file
+  - name: 💬 Telegram chat 💬
+    url: https://t.me/botoforum
+    about: ✏️ You can ask questions about the framework. ✏️
diff --git a/.github/ISSUE_TEMPLATE/new_feat.md b/.github/ISSUE_TEMPLATE/new_feat.md
index a71701e4..2937a41e 100644
--- a/.github/ISSUE_TEMPLATE/new_feat.md
+++ b/.github/ISSUE_TEMPLATE/new_feat.md
@@ -1,6 +1,6 @@
 ---
 name: "\U0001F680 New feature \U0001F680"
-about: "I have a suggest and may want to implement it."
+about: "\U0001F680 I want to propose a new feature. \U0001F680"
 title: ""
 labels: ["new_feature"]
 assignees: "luwqz1"
@@ -8,16 +8,17 @@ assignees: "luwqz1"
 
 ## 🚀 Feature Request 🚀
 
-**✏️ Is your feature request related to a problem? Please describe ✏️**
-A clear and concise description of what the problem is. Ex.
-```
-I have an issue when...
-```
+**✏️ New feature description ✏️**
+<!-- A clear and concise description. -->
+_No response_
+
 
-**🪄 Provide a minimal reproducible example 🪄**
-A small code sample that will demonstrate your feature.
+**🪄 Provide a minimal example 🪄**
+<!-- A small code sample that will demonstrate your feature. -->
 ```python
+from telegrinder import ...
 ```
 
 **✨ Teachability, Documentation, Adoption ✨**
-If you can, explain how users will be able to use this and possibly write out a version the docs.
+<!-- If you can, explain how users will be able to use this and possibly write out a version the docs. -->
+_No response_
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 00000000..0661a6ed
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,11 @@
+version: 2
+
+updates:
+  - package-ecosystem: pip
+    directory: "/"
+    schedule:
+      interval: "daily"
+    allow:
+      - dependency-type: "direct"
+    open-pull-requests-limit: 4
+    target-branch: "dev"
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index e5960eb5..b95369d4 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,14 +1,12 @@
 ### ✏️ Description ✏️
-<!-- 
+<!--
 Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
 -->
+_No response_
 
 `🐛 Fixes # (issue)`
 
 ### 📍 Type of change 📍
-
-<!-- Please delete options that are not relevant.-->
-
 - [ ] 🐛 Bug fix (non-breaking change which fixes an issue) 🐛
 - [ ] 🚀 New feature (non-breaking change which adds functionality) 🚀
 - [ ] ⚡ Breaking change (fix or feature that would cause existing functionality not to work as expected) ⚡
@@ -17,4 +15,4 @@ Please include a summary of the change and which issue is fixed. Please also inc
 ### ⚪️ Checklist ⚪️
 * [ ] 📝 Your code has documentation 📝
 * [ ] 🔧 Your code has tests 🔧
-* [ ] 🪄 Your code has example usage in `/examples` 🪄
\ No newline at end of file
+* [ ] 🪄 Your code has example usage in `/examples` 🪄
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
new file mode 100644
index 00000000..2eac75d7
--- /dev/null
+++ b/.github/workflows/pull-request.yml
@@ -0,0 +1,50 @@
+name: PR
+
+on: [pull_request]
+
+defaults:
+  run:
+    shell: bash
+
+jobs:
+  pre-commit:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: asottile/workflows/.github/actions/fast-checkout@v1.4.1
+
+      - name: Install poetry and dependencies
+        uses: ./.github/actions/install-dependencies
+        with:
+          os: ubuntu-latest
+          python-version: "3.11"
+
+      - name: Load pre-commit cache
+        id: cached-pre-commit
+        uses: actions/cache@v3
+        with:
+          key: pre-commit-${{ env.pythonLocation }}-${{ hashFiles('.pre-commit-config.yaml') }}
+          path: ~/.cache/pre-commit
+
+      - name: Run pre-commit
+        run: poetry run pre-commit run --all-files
+
+      - name: Run pre-commit-ci-lite
+        uses: pre-commit-ci/lite-action@v1.0.1
+        if: failure() || cancelled()
+
+      - uses: andymckay/cancel-action@0.3
+        if: failure()
+
+  lint-and-test:
+    strategy:
+      matrix:
+        python-version:
+          - "3.11"
+        os:
+          - ubuntu-latest
+          - windows-latest
+
+    uses: ./.github/workflows/lint-and-test.yml
+    with:
+      os: ${{ matrix.os }}
+      python-version: ${{ matrix.python-version }}
diff --git a/.github/workflows/typegen.yml b/.github/workflows/typegen.yml
index 6d64b431..ba000089 100644
--- a/.github/workflows/typegen.yml
+++ b/.github/workflows/typegen.yml
@@ -1,4 +1,4 @@
-name: Generate Telegram Bot API types
+name: Generating Telegram Bot API types
 
 on:
   workflow_dispatch: {}
@@ -7,24 +7,22 @@ on:
 
 jobs:
   build:
-    name: Generate API types
+    name: Generating Telegram Bot API types
     runs-on: ubuntu-latest
     steps:
         - uses: actions/checkout@v4
-        
+
         - name: Set up CPython 3.11
           uses: actions/setup-python@v5
           with:
             python-version: "3.11"
-        
+
         - name: Install poetry
-          run: >
-            pip install --upgrade pip setuptools wheel pipx
-            pipx install poetry
-      
+          run: pip install --upgrade pip setuptools wheel && pip install --user pipx && pipx install poetry
+
         - name: Install depends
           run: poetry install
-      
+
         - name: Run typegen
           run: poetry run python typegen
 
diff --git a/.gitignore b/.gitignore
index 5f6fffa6..fa1c356c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -141,4 +141,4 @@ dmypy.json
 poetry.lock
 
 .vscode
-local
\ No newline at end of file
+local
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..fc54e9a2
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,47 @@
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.6.0
+    hooks:
+      - id: check-merge-conflict
+        stages: [commit, push]
+      - id: check-ast
+        stages: [commit]
+      - id: trailing-whitespace
+        stages: [commit]
+      - id: end-of-file-fixer
+        stages: [commit]
+
+  - repo: local
+    hooks:
+      - id: basedpyright
+        name: basedpyright type checker
+        description: "Run 'basedpyright' for extremely fast Python type checking"
+        entry: basedpyright . --level ERROR
+        language: system
+        types: [python]
+        stages: [commit]
+
+  - repo: https://github.com/astral-sh/ruff-pre-commit
+    rev: v0.4.9
+    hooks:
+      - id: ruff-format
+        name: ruff-format
+        description: "Run 'ruff format' for extremely fast Python formatting"
+        language: system
+        types: [python]
+        stages: [commit]
+
+      - id: ruff
+        name: ruff-isort
+        description: "Run 'ruff --select I --select F401 --fix' for extremely fast Python sorting imports"
+        entry: ruff check --select I --select F401 --fix
+        language: system
+        types: [python]
+        stages: [commit]
+
+      - id: ruff
+        name: ruff-check
+        description: "Run 'ruff-format' for extremely fast Python linting"
+        language: system
+        types: [python]
+        stages: [push]
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 9cceeb0b..a12abeff 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -10,4 +10,4 @@ build:
     - mkdocs build --clean --site-dir $READTHEDOCS_OUTPUT/html --config-file mkdocs.yml
 
 mkdocs:
-  configuration: mkdocs.yml
\ No newline at end of file
+  configuration: mkdocs.yml
diff --git a/LICENSE b/LICENSE
index e9024b6d..9f78e47e 100755
--- a/LICENSE
+++ b/LICENSE
@@ -19,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
+SOFTWARE.
diff --git a/contributing.md b/contributing.md
index 954828d1..7c5fabd3 100755
--- a/contributing.md
+++ b/contributing.md
@@ -8,4 +8,4 @@ BTC: `bc1qxc4g6m0qz0kkfa24yxegnl6hz94h8u5k9swd3k`
 
 ## For developers
 
-PRs are welcome, feel free to contribute.
\ No newline at end of file
+PRs are welcome, feel free to contribute.
diff --git a/docs/guide/code_separation.md b/docs/guide/code_separation.md
index d7d5fc96..b4c989c2 100755
--- a/docs/guide/code_separation.md
+++ b/docs/guide/code_separation.md
@@ -73,4 +73,4 @@ if __name__ == "__main__":
     for dp in dps:
         bot.dispatch.load(dp)
     bot.run_forever()
-```
\ No newline at end of file
+```
diff --git a/docs/guide/index.md b/docs/guide/index.md
index 73c0edc7..90f45b5c 100755
--- a/docs/guide/index.md
+++ b/docs/guide/index.md
@@ -2,4 +2,4 @@
 
 This is telegrinder guide for beginners.
 
-* [Code Separation](code_separation.md)
\ No newline at end of file
+* [Code Separation](code_separation.md)
diff --git a/docs/index.md b/docs/index.md
index 522c4c9b..1414dfca 100755
--- a/docs/index.md
+++ b/docs/index.md
@@ -6,4 +6,4 @@ You may be interested in:
 
 * [Guide](guide/index.md)
 * [API](api.md)
-* [Tools](tools/index.md)
\ No newline at end of file
+* [Tools](tools/index.md)
diff --git a/docs/tools/checkbox.md b/docs/tools/checkbox.md
index 6dd07cce..2c05edae 100755
--- a/docs/tools/checkbox.md
+++ b/docs/tools/checkbox.md
@@ -1,11 +1,11 @@
 # Checkbox
 
-Via special scenarios you can easily create some common constructs. 
+Via special scenarios you can easily create some common constructs.
 Scenarios are designed to be easy-to-use and as customizable as possible.
 
 One of them is checkbox scenario, which is used to create an inline checkbox with particular options.
 
-Callback data is generated and processed automatically. Checkbox performs event collection via `asyncio.Event`, so it is extremely easy to integrate such scenario in your funnel bot.
+Callback data is generate_noded and processed automatically. Checkbox performs event collection via `asyncio.Event`, so it is extremely easy to integrate such scenario in your funnel bot.
 
 There is an obvious disadvantage of such way of collecting events: states are supposed to be short.
 
@@ -27,53 +27,53 @@ bot = Telegrinder(api=api)
 @bot.on.message(Text("/start"))
 async def start(m: Message):
     # now we need to create a checkbox
-    
-    # `msg` will be sent to the user with 
-    # the checkbox reply markup. It usually 
+
+    # `msg` will be sent to the user with
+    # the checkbox reply markup. It usually
     # contains an offering to choose needed options.
-    
-    # `ready_text` is the text on the button 
+
+    # `ready_text` is the text on the button
     # which will be used to submit the checkbox.
-    
+
     # `max_in_row` sets the limit for options in a row
     checkbox = Checkbox(
-        m.chat.id, 
+        m.chat.id,
         msg="Choose accessories you need:",
         multiple_choice=True,
         ready_text="That's all",
         max_in_row=2
     )
-    
-    # now you can add options 
+
+    # now you can add options
     # into you checkbox. You can do it via .add_option method.
     # This also can be done in builder interface like this:
     # Checkbox(...).add_option(...).add_option(...)
-    
+
     # checkbox option consists of four elements:
     # `name` of option is needed to later return the pick status
     # of option in dict
     # `default_text` is displayed on the button when option is not picked
     # `picked_text` is opposite
     # `is_picked` is needed to preset the button state
-    
+
     checkbox.add_option(
         name="case",
         default_text="Case 5$",
         picked_text="[ Case 5$ ]",
         is_picked=False
     )
-    
+
     checkbox.add_option(
-        "charger", "Charger 9$", "[ Charger 9$ ]", 
+        "charger", "Charger 9$", "[ Charger 9$ ]",
         is_picked=True
     )
-    
+
     # now your checkbox is ready to use.
     # picked is a dictionary option name to option state
     # message_id is the id of the one sent with `msg` text
     picked, message_id = await checkbox.wait(m.ctx_api, bot.dispatch)
-    
-    # usually this message is edited 
+
+    # usually this message is edited
     # with received information
     await m.ctx_api.edit_message_text(
         m.chat.id,
@@ -90,4 +90,4 @@ bot.run_forever()
 
 # Advanced Usage
 
-WIP
\ No newline at end of file
+WIP
diff --git a/docs/tools/formatting.md b/docs/tools/formatting.md
index 8489eb4f..d4c02ae9 100755
--- a/docs/tools/formatting.md
+++ b/docs/tools/formatting.md
@@ -16,7 +16,7 @@ Formatter is derived from `FormatString` to work string formatting with the foll
 * `escape(string: str) -> EscapedString` | escaping string
 * `italic(string: str) -> TagFormat` | __italic text__
 * `link(href: str, string: str | None = None) -> TagFormat` | https://link
-* `mention(string: str, user_id: int) -> TagFormat` | mention entity 
+* `mention(string: str, user_id: int) -> TagFormat` | mention entity
 * `pre_code(string: str, lang: str | ProgrammingLanguage | None = None) -> TagFormat` | ```pre code```
 * `resolve_domain(username: str, string: str | None = None) -> TagFormat` | tg://resolve?domain=username
 * `spoiler(string: str) -> TagFormat` -> ||spoiler text||
@@ -25,7 +25,6 @@ Formatter is derived from `FormatString` to work string formatting with the foll
 * `strike(string: str) -> TagFormat` -> ~~strikethrough text~~
 * `tg_emoji(string: str, emoji_id: int) -> TagFormat` | telegram emoji by emoji id
 * `underline(string: str) -> TagFormat` -> <u>underline text</u>
-* `user_open_message(user_id: int, message: str | None = None, string: str | None = None) -> TagFormat` | tg://openmessage?user_id=123456789&msg?text=AnyMessage
 
 ```python
 from telegrinder.tools.formatting import HTMLFormatter, bold, spoiler
@@ -33,7 +32,7 @@ from telegrinder.tools.formatting import HTMLFormatter, bold, spoiler
 HTMLFormatter(spoiler("I want tea."))  # if you want use only formatting functions
 "Just string | " + bold("bold string")  # if you want concat str with formatting functions or HTMLFormatter instance (there's no difference between right or left)
 ```
-  
+
 Also formatter has a `.format(self, *args, **kwargs)` method for formatting a string with `{}`, it can support specifiers whose names are the same as the format names (other than special):
 
 ```python
@@ -62,7 +61,6 @@ To use special formats, you have to use special dataclasses:
 * `StartBotLink(bot_id: str | int, data: str, string: str | None = None)` -> `start_bot_link`
 * `StartGroupLink(bot_id: str | int, data: str, string: str | None = None)` -> `start_group_link`
 * `TgEmoji(string: str, emoji_id: int)` -> `tg_emoji`
-* `UserOpenMessage(user_id: int, message: str | None = None, string: str | None = None)` -> `user_open_message`
 
 ```python
 from telegrinder.tools.formatting import HTMLFormatter, Mention, Link, CodeBlock
@@ -103,8 +101,6 @@ HTMLFormatter("start game in the {:spoiler}").format(StartBotLink("telegrinder_b
 HTMLFormatter("Get a bonus from the {:italic} in the chosen group").format(StartGroupLink("nice123_bot", "get_bonus", "nice cool bot"))
 
 HTMLFormatter("Join our {:bold+underline}").format(InviteChatLink("+kMj2234KklsSka2-", "chat"))
-
-HTMLFormatter("Please, write about your problem to our {:underline}").format(UserOpenMessage(123456789, string="manager"))
 ```
 
 HTMLFormatter also has a property of parse mode string.
diff --git a/docs/tools/global_context.md b/docs/tools/global_context.md
index 2590c5ca..e57f465b 100755
--- a/docs/tools/global_context.md
+++ b/docs/tools/global_context.md
@@ -131,7 +131,7 @@ class MyGlobalContext(GlobalContext):
     COOKIES: typing.Final[set[str]] = CtxVar({"chocolate", "chip", "cracker"}, const=True)
     # NOTE: type hints typing.Final, typing.ClassVar mark the fields as a constant, so such fields will not be present in the __init__ signature.
 
-ctx = MyGlobalContext(supplier="Brooklyn Born Chocolate")  # <'MyGlobalContext@my_ctx' -> (<GlobalCtxVar(supplier=<CtxVar(value='Brooklyn Born Chocolate')>)>, <GlobalCtxVar(COOKIES=<ConstCtxVar(value={'chocolate', 'chip', 'cracker'})>)>)> 
+ctx = MyGlobalContext(supplier="Brooklyn Born Chocolate")  # <'MyGlobalContext@my_ctx' -> (<GlobalCtxVar(supplier=<CtxVar(value='Brooklyn Born Chocolate')>)>, <GlobalCtxVar(COOKIES=<ConstCtxVar(value={'chocolate', 'chip', 'cracker'})>)>)>
 ```
 
 Function `ctx_var(value: T, *, const: bool = False) -> T` is the same as dataclass `CtxVar` but it returns this dataclass as type T.
@@ -154,4 +154,4 @@ Telegrinder has a basic type-hinted global context `TelegrinderCtx` __(context n
 
 ```python
 from telegrinder.tools.global_context import TelegrinderCtx
-```
\ No newline at end of file
+```
diff --git a/docs/tools/index.md b/docs/tools/index.md
index 5b649113..0a1244ea 100755
--- a/docs/tools/index.md
+++ b/docs/tools/index.md
@@ -6,4 +6,4 @@ Useful tools that are used with Telegrinder.
 * [Formatting](formatting.md)
 * [Keyboard Set](keyboard_set.md)
 * [Global Context](global_context.md)
-* [Error Handler](error_handler.md)
\ No newline at end of file
+* [Error Handler](error_handler.md)
diff --git a/examples/assets/i18n/en/LC_MESSAGES/messages.po b/examples/assets/i18n/en/LC_MESSAGES/messages.po
index 4a170a5e..9a78aceb 100755
--- a/examples/assets/i18n/en/LC_MESSAGES/messages.po
+++ b/examples/assets/i18n/en/LC_MESSAGES/messages.po
@@ -29,4 +29,4 @@ msgid "hello"
 msgstr ""
 
 msgid "Hello, {name}!"
-msgstr ""
\ No newline at end of file
+msgstr ""
diff --git a/examples/assets/i18n/ru/LC_MESSAGES/messages.po b/examples/assets/i18n/ru/LC_MESSAGES/messages.po
index 93fc18f8..db19da9b 100755
--- a/examples/assets/i18n/ru/LC_MESSAGES/messages.po
+++ b/examples/assets/i18n/ru/LC_MESSAGES/messages.po
@@ -30,4 +30,4 @@ msgid "hello"
 msgstr "привет"
 
 msgid "Hello, {name}!"
-msgstr "Привет, {name}!"
\ No newline at end of file
+msgstr "Привет, {name}!"
diff --git a/examples/assets/kaktovik.txt b/examples/assets/kaktovik.txt
index 0cb112b2..9b89a59f 100755
--- a/examples/assets/kaktovik.txt
+++ b/examples/assets/kaktovik.txt
@@ -1,1 +1,1 @@
-𝋀𝋁𝋂𝋃𝋄𝋅𝋆𝋇𝋈𝋉𝋊𝋋𝋌𝋍𝋎𝋏𝋐𝋑𝋒𝋓"
\ No newline at end of file
+𝋀𝋁𝋂𝋃𝋄𝋅𝋆𝋇𝋈𝋉𝋊𝋋𝋌𝋍𝋎𝋏𝋐𝋑𝋒𝋓"
diff --git a/examples/assets/kb_set_config.yaml b/examples/assets/kb_set_config.yaml
index aecfa8e1..64118f00 100755
--- a/examples/assets/kb_set_config.yaml
+++ b/examples/assets/kb_set_config.yaml
@@ -20,4 +20,4 @@ items:
     - text: "Buy banana"
       callback_data:
         name: "Banana"
-        cost: 456
\ No newline at end of file
+        cost: 456
diff --git a/examples/blueprint_bot/start.py b/examples/blueprint_bot/start.py
index 8ed90001..bfb8e78e 100755
--- a/examples/blueprint_bot/start.py
+++ b/examples/blueprint_bot/start.py
@@ -17,9 +17,7 @@ async def funnel_handler(message: Message):
         msg.from_user.first_name,
         msg.text,
     )
-    await message.answer(
-        "And what is the pattern of the beautiful scarf you wear in the winter nights?"
-    )
+    await message.answer("And what is the pattern of the beautiful scarf you wear in the winter nights?")
     msg, _ = await wm.wait(dp.message, message, HasText())
     logger.info(
         "{}'s pattern of the winter scarf is {}",
diff --git a/examples/blueprint_bot/with_enum.py b/examples/blueprint_bot/with_enum.py
index 3988d8cf..4cbce699 100755
--- a/examples/blueprint_bot/with_enum.py
+++ b/examples/blueprint_bot/with_enum.py
@@ -1,20 +1,20 @@
 from telegrinder import Dispatch, Message
 from telegrinder.bot.dispatch.handler.message_reply import MessageReplyHandler
 from telegrinder.modules import logger
-from telegrinder.rules import HasText, Markup, RuleEnum, Text, TextMessageRule
+from telegrinder.rules import ABCRule, HasText, Markup, RuleEnum, Text
 
 from .client import wm
 
 dp = Dispatch()
 
 
-class Commands(RuleEnum[Message]):
+class Commands(RuleEnum):
     PLAY = Text(["/play", "/game"])
     GET_PRESENTS = Text("/get_presents")
     COMMIT_SUICIDE = Markup(["/suicide", "/suicide <reason>"])
 
 
-class WasNaughty(TextMessageRule):
+class WasNaughty(ABCRule, requires=[HasText()]):
     async def check(self, message: Message, _: dict) -> bool:
         await message.answer("Ok, but were you naughty this year?")
         m, _ = await wm.wait(
diff --git a/examples/single_choice.py b/examples/choice.py
similarity index 71%
rename from examples/single_choice.py
rename to examples/choice.py
index b7aceb87..9198a565 100755
--- a/examples/single_choice.py
+++ b/examples/choice.py
@@ -1,4 +1,4 @@
-from telegrinder import API, Message, SingleChoice, Telegrinder, Token, WaiterMachine
+from telegrinder import API, Choice, Message, Telegrinder, Token, WaiterMachine
 from telegrinder.modules import logger
 from telegrinder.rules import Text
 
@@ -12,13 +12,13 @@
 @bot.on.message(Text("/choice"))
 async def action(m: Message):
     chosen, m_id = await (
-        SingleChoice(wm, m.chat.id, "Choose something", max_in_row=1)
+        Choice(wm, m.chat.id, "Choose something", max_in_row=1)
         .add_option("apple", "Apple 🔴", "Apple 🟢")
         .add_option("banana", "Banana 🔴", "Banana 🟢", is_picked=True)
         .add_option("pear", "Pear 🔴", "Pear 🟢")
         .wait(m.ctx_api, bot.dispatch.callback_query)
     )
-    await m.edit(text=f"You chose {chosen}", message_id=m_id)
+    await m.edit(text=f"You chose - {chosen}", message_id=m_id)
 
 
 bot.run_forever()
diff --git a/examples/command.py b/examples/command.py
index 51f365aa..fde7647f 100755
--- a/examples/command.py
+++ b/examples/command.py
@@ -45,9 +45,21 @@ async def split_handler(
     sep: str = " ",
     count: int | None = None,
 ) -> None:
-    await message.answer(
-        " | ".join(string.split(sep, count) if count is not None else string.split(sep))
+    await message.answer(" | ".join(string.split(sep, count) if count is not None else string.split(sep)))
+
+
+@bot.on.message(
+    Command(
+        ["sum", "s"],
+        Argument("x", [int_validator]),
+        Argument("y", [int_validator]),
+        Argument("r", [int_validator], optional=True),
     )
+)
+async def sum_handler(message: Message, x: int, y: int, r: int | None = None) -> str:
+    if r is not None:
+        return "Yes" if x + y == r else "No"
+    return str(x + y)
 
 
 bot.run_forever()
diff --git a/examples/composition.py b/examples/composition.py
deleted file mode 100755
index 39b0b5f8..00000000
--- a/examples/composition.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import typing
-
-import aiosqlite  # type: ignore
-
-from telegrinder import API, Telegrinder, Token
-from telegrinder.bot import rules
-from telegrinder.bot.dispatch import CompositionDispatch
-from telegrinder.modules import logger
-from telegrinder.node import Photo, RuleContext, ScalarNode, Source, Text, generate
-
-api = API(token=Token.from_env())
-bot = Telegrinder(api, dispatch=CompositionDispatch())
-logger.set_level("INFO")
-
-
-@bot.loop_wrapper.lifespan.on_startup
-async def create_tables() -> None:
-    async with aiosqlite.connect("test.db") as conn:
-        await conn.execute(
-            "create table if not exists logs("
-            "id integer primary key autoincrement, "
-            "msg text"
-            ")"
-        )
-
-
-# Pure node examples
-class DB(ScalarNode, aiosqlite.Connection):
-    @classmethod
-    async def compose(cls) -> typing.AsyncGenerator[aiosqlite.Connection, None]:
-        connection = await aiosqlite.connect("test.db")
-        yield connection
-        logger.info("Closing connection")
-        await connection.close()
-
-
-@bot.on()
-async def photo_handler(photo: Photo, source: Source, db: DB):
-    await source.send("File ID: " + photo.sizes[-1].file_id)
-    await db.execute("insert into logs(msg) values (?)", (photo.sizes[-1].file_id,))
-    await db.commit()
-    logger.info("Finished handling")
-
-
-# Container generated node examples
-@bot.on(
-    generate((Text,), lambda text: text == "hello"),
-    generate((Source,), lambda src: src.chat.username.unwrap_or_none() == "weirdlashes"),
-)
-async def hi_handler(source: Source):
-    await source.send("Hi !!")
-
-
-@bot.on(generate((Text,), lambda text: int(text) if text.isdigit() else None))
-async def integer_handler(source: Source, container: tuple[int]):
-    (integer,) = container
-    await source.send("{} + 3 = {}".format(integer, integer + 3))
-
-
-# Rule node examples
-@bot.on()
-async def handler_ruleset_as_context(
-    ctx: RuleContext[rules.Markup("/name <name>"),],
-    src: Source,
-) -> None:
-    name = ctx["name"]
-    await src.send(f"Hi, {name}")
-
-
-class Context(
-    RuleContext[
-        rules.Markup("<a:int> / <b:int> = <c:float>"),
-        rules.IsUser(),
-    ]
-):
-    a: int
-    b: int
-    c: float
-
-
-@bot.on()
-async def handler_ruleset_dataclass_context(
-    ctx: Context,
-    src: Source,
-) -> None:
-    if ctx.a / ctx.b == ctx.c:
-        await src.send("Right! ^___^")
-    else:
-        await src.send("Wrong! >:(")
-
-
-bot.run_forever()
diff --git a/examples/easy.py b/examples/easy.py
index 24ba9531..437f783e 100755
--- a/examples/easy.py
+++ b/examples/easy.py
@@ -2,8 +2,9 @@
 import random
 
 from telegrinder import API, Message, Telegrinder, Token
-from telegrinder.bot import WaiterMachine
+from telegrinder.bot import WaiterMachine, clear_wm_storage_worker
 from telegrinder.bot.dispatch.handler.message_reply import MessageReplyHandler
+from telegrinder.bot.rules.is_from import IsUser
 from telegrinder.modules import logger
 from telegrinder.rules import FuzzyText, HasText, Markup, Text
 from telegrinder.types import InputFile
@@ -14,6 +15,8 @@
 kitten_bytes = pathlib.Path("examples/assets/kitten.jpg").read_bytes()
 logger.set_level("INFO")
 
+bot.dispatch.message.auto_rules.append(IsUser())
+
 
 @bot.on.message(Text("/start"))
 async def start(message: Message):
@@ -25,6 +28,7 @@ async def start(message: Message):
         bot.dispatch.message,
         message,
         Text(["fine", "bad"], ignore_case=True),
+        exit=MessageReplyHandler("Oh, ok, exiting state...", Text("/exit")),
         default=MessageReplyHandler("Fine or bad"),
     )
 
@@ -67,9 +71,7 @@ async def reverse(message: Message, text: str):
 @bot.on.message(Markup(["/predict", "/predict <thing>"]))
 async def predict(message: Message, thing: str | None = None):
     probability_percent = random.randint(0, 100)
-    await message.answer(
-        f"I predict the probability {thing or 'it'} will happen is {probability_percent}%"
-    )
+    await message.answer(f"I predict the probability {thing or 'it'} will happen is {probability_percent}%")
 
 
 @bot.on.message(FuzzyText("hello"))
@@ -77,4 +79,5 @@ async def hello(message: Message):
     await message.reply("Hi!")
 
 
+bot.loop_wrapper.add_task(clear_wm_storage_worker(wm, bot.dispatch))
 bot.run_forever(skip_updates=True)
diff --git a/examples/formatting.py b/examples/formatting.py
index 44055aec..3f36b28b 100755
--- a/examples/formatting.py
+++ b/examples/formatting.py
@@ -4,6 +4,8 @@
 from telegrinder.tools import HTMLFormatter, bold, italic, mention
 
 api = API(token=Token.from_env())
+api.default_params["parse_mode"] = HTMLFormatter.PARSE_MODE
+
 bot = Telegrinder(api)
 logger.set_level("INFO")
 
@@ -12,18 +14,15 @@
 async def formatting(m: Message):
     await m.answer(
         HTMLFormatter(bold(italic("bold italic text!"))),
-        parse_mode=HTMLFormatter.PARSE_MODE,
     )
     await m.answer(
         "python library 'telegrinder' - "
-        + HTMLFormatter(
-            "{:bold} for effective and reliable telegram {:bold+italic} {:underline}!"
-        ).format("Framework", "bot", "building"),
-        parse_mode=HTMLFormatter.PARSE_MODE,
+        + HTMLFormatter("{:bold} for effective and reliable telegram {:bold+italic} {:underline}!").format(
+            "Framework", "bot", "building"
+        ),
     )
     await m.answer(
         "this is " + mention(m.from_user.first_name, m.from_user.id),
-        parse_mode=HTMLFormatter.PARSE_MODE,
     )
 
 
diff --git a/examples/global_context.py b/examples/global_context.py
index b3e6108b..ebaa5c33 100755
--- a/examples/global_context.py
+++ b/examples/global_context.py
@@ -47,15 +47,11 @@ async def pre(self, event: Message, ctx: Context) -> bool:
 @bot.on.message(Text("/formatting"))
 async def formatting(_: Message) -> dict[str, typing.Any]:
     global_ctx.formatting = not global_ctx.formatting
-    return formatting_text(
-        "Formatting ", bold("enabled!" if global_ctx.formatting else "disabled!")
-    )
+    return formatting_text("Formatting ", bold("enabled!" if global_ctx.formatting else "disabled!"))
 
 
 @bot.on.message(
-    MessageEntities(
-        [MessageEntityType.TEXT_MENTION, MessageEntityType.MENTION, MessageEntityType.URL]
-    )
+    MessageEntities([MessageEntityType.TEXT_MENTION, MessageEntityType.MENTION, MessageEntityType.URL])
     & Markup(["/get_user @<username>", "/get_user t.me/<username>", "/get_user <username>"])
 )
 async def get_user_by_username(
diff --git a/examples/inline_bot.py b/examples/inline_bot.py
index 35407bac..54ed842f 100755
--- a/examples/inline_bot.py
+++ b/examples/inline_bot.py
@@ -13,9 +13,7 @@
 
 @bot.on.inline_query(InlineQueryText("test"))
 async def test_inline(q: InlineQuery):
-    await q.answer(
-        inline_query_article("1", "Press me", input_text_message_content("I tested inline query"))
-    )
+    await q.answer(inline_query_article("1", "Press me", input_text_message_content("I tested inline query")))
 
 
 @bot.on.inline_query()
@@ -23,9 +21,7 @@ async def reverse_inline(q: InlineQuery):
     if not q.query:
         return
     await q.answer(
-        inline_query_article(
-            "1", "Send reversed", input_text_message_content("I tested inline query")
-        ),
+        inline_query_article("1", "Send reversed", input_text_message_content("I tested inline query")),
     )
 
 
diff --git a/examples/kaktovik.py b/examples/kaktovik.py
index 71de399e..e38afec7 100755
--- a/examples/kaktovik.py
+++ b/examples/kaktovik.py
@@ -2,7 +2,7 @@
 
 from telegrinder import API, Message, Telegrinder, Token
 from telegrinder.modules import logger
-from telegrinder.rules import Integer, IsPrivate
+from telegrinder.rules import IsInteger, IsPrivate
 from telegrinder.tools.formatting import HTMLFormatter, Link
 
 api = API(Token.from_env())
@@ -12,7 +12,7 @@
 logger.set_level("INFO")
 
 
-@bot.on.message(Integer())
+@bot.on.message(IsInteger())
 async def integer_handler(message: Message) -> None:
     integer = int(message.text.unwrap())
     lst = []
diff --git a/examples/kb_set.py b/examples/kb_set.py
index 5111cacb..2ec02c08 100755
--- a/examples/kb_set.py
+++ b/examples/kb_set.py
@@ -56,9 +56,7 @@ async def choose_handler(m: Message):
         bot.dispatch.message,
         m,
         Text(["yes", "no"], ignore_case=True),
-        default=MessageReplyHandler(
-            "Please make a decision: Yes or No. This is extremely important!"
-        ),
+        default=MessageReplyHandler("Please make a decision: Yes or No. This is extremely important!"),
     )
     if answer.text.unwrap().lower() == "yes":
         await answer.reply("Rockets have been launched.")
diff --git a/examples/message_entities.py b/examples/message_entities.py
index 6f145f3e..9b93f1e7 100755
--- a/examples/message_entities.py
+++ b/examples/message_entities.py
@@ -15,8 +15,7 @@ async def handler_mention_me(message: Message, message_entities: list[MessageEnt
     my_username = (await api.get_me()).map(lambda me: me.username).unwrap()
     if (
         not my_username
-        or my_username
-        != message.text.unwrap()[message_entities[0].offset + 1 : message_entities[0].length]
+        or my_username != message.text.unwrap()[message_entities[0].offset + 1 : message_entities[0].length]
     ):
         return
     await message.delete()
diff --git a/examples/nodes.py b/examples/nodes.py
new file mode 100755
index 00000000..e44c1be6
--- /dev/null
+++ b/examples/nodes.py
@@ -0,0 +1,32 @@
+import typing
+
+import aiosqlite  # type: ignore
+
+from telegrinder.modules import logger
+from telegrinder.node import ScalarNode, per_call
+
+
+class DB(ScalarNode, aiosqlite.Connection):
+    @classmethod
+    async def compose(cls) -> typing.AsyncGenerator[aiosqlite.Connection, None]:
+        connection = await aiosqlite.connect("test.db")
+        logger.info("Opening connection")
+        yield connection
+        logger.info("Closing connection")
+        await connection.close()
+
+
+# DB connection will be only opened once per event
+# to change this and resolve node each time when needed use:
+@per_call
+class DB2(ScalarNode, aiosqlite.Connection): ...
+
+
+async def create_tables() -> None:
+    async with aiosqlite.connect("test.db") as conn:
+        await conn.execute(
+            "create table if not exists admins("
+            "id integer primary key autoincrement, "
+            "telegram_id text unique"
+            ")"
+        )
diff --git a/examples/raw_event.py b/examples/raw_event.py
index 35a41b13..b4492dd6 100644
--- a/examples/raw_event.py
+++ b/examples/raw_event.py
@@ -24,9 +24,7 @@ async def check(self, update: Update, ctx: Context) -> bool:
         match update.get_event(MessageReactionUpdated):
             case Some(event) if event.user:
                 user = event.user.unwrap()
-                message_id = bot.dispatch.global_context.get_value(
-                    f"{user.id}:{event.chat.id}", int
-                )
+                message_id = bot.dispatch.global_context.get_value(f"{user.id}:{event.chat.id}", int)
                 return message_id.unwrap_or_none() == event.message_id
         return False
 
@@ -53,8 +51,7 @@ async def chat_boost(update: Update):
 @bot.on.raw_event(UpdateType.MESSAGE_REACTION, ReactionRule(), dataclass=MessageReactionUpdated)
 async def message_reaction(message_reaction: MessageReactionUpdated):
     new_reactions = [
-        x.v.emoji.value if x.v.type == "emoji" else "*custom reaction*"
-        for x in message_reaction.new_reaction
+        x.v.emoji.value if x.v.type == "emoji" else "*custom reaction*" for x in message_reaction.new_reaction
     ]
     await bot.api.send_message(
         chat_id=message_reaction.chat.id,
diff --git a/examples/rule_enum.py b/examples/rule_enum.py
index 1a529cae..8f666b48 100755
--- a/examples/rule_enum.py
+++ b/examples/rule_enum.py
@@ -7,7 +7,7 @@
 logger.set_level("INFO")
 
 
-class CancelOrUsername(RuleEnum[Message]):
+class CancelOrUsername(RuleEnum):
     CANCEL = Text("/cancel")
     USERNAME = Markup("@<username>") | Markup("t.me/<username>")
 
diff --git a/examples/start_command.py b/examples/start_command.py
index facf95e6..9478e2ae 100755
--- a/examples/start_command.py
+++ b/examples/start_command.py
@@ -13,9 +13,7 @@ async def start_handler(message: Message, param: int | None) -> None:
         await message.answer("You have no integer start query((")
         return
     await message.answer(
-        "Ahah you integer start query is so funny, " "its {0} and {0}-42={1}".format(
-            param, param - 42
-        )
+        "Ahah you integer start query is so funny, " "its {0} and {0}-42={1}".format(param, param - 42)
     )
 
 
diff --git a/examples/upload.py b/examples/upload.py
index 2e2f8c83..c376d7ae 100755
--- a/examples/upload.py
+++ b/examples/upload.py
@@ -1,19 +1,19 @@
 import pathlib
 
-from telegrinder import API, Button, Keyboard, Message, Telegrinder, Token
+from telegrinder import API, Message, Telegrinder, Token
+from telegrinder.bot.rules.is_from import IsPrivate
 from telegrinder.modules import logger
-from telegrinder.rules import Text
+from telegrinder.rules import IsUser, Text
 from telegrinder.types import InputFile
 
 api = API(token=Token.from_env())
 bot = Telegrinder(api)
-kb = (Keyboard().add(Button("Button 1")).add(Button("Button 2"))).get_markup()
-cool_bytes = pathlib.Path("assets/satie.jpeg").read_bytes()
+cool_bytes = pathlib.Path("examples/assets/satie.jpeg").read_bytes()
 
 logger.set_level("INFO")
 
 
-@bot.on.message(Text("/photo"))
+@bot.on.message(Text("/photo"), IsPrivate() & IsUser())
 async def start(message: Message):
     await message.answer_photo(
         InputFile("satie.jpeg", cool_bytes),
diff --git a/examples/user_by_username.py b/examples/user_by_username.py
index 11e46268..63b98598 100755
--- a/examples/user_by_username.py
+++ b/examples/user_by_username.py
@@ -85,14 +85,12 @@ async def get_user_by_username(self, username: str) -> Option[User]:
             aiosqlite.connect(db_path) as conn,
             conn.cursor() as cur,
         ):
-            row = await (
-                await cur.execute("SELECT * FROM users WHERE username = ?", (username,))
-            ).fetchone()
+            row = await (await cur.execute("SELECT * FROM users WHERE username = ?", (username,))).fetchone()
             if row is None:
                 return Nothing()
             return decoder.convert(
                 obj=get_result_with_names(cur, row, as_bool={"is_premium", "is_bot"}),
-                type=Option[User],
+                type=Option[User],  # type: ignore
             )
 
     async def get_user_by_id(self, user_id: int) -> Option[User]:
@@ -100,14 +98,12 @@ async def get_user_by_id(self, user_id: int) -> Option[User]:
             aiosqlite.connect(db_path) as conn,
             conn.cursor() as cur,
         ):
-            row = await (
-                await cur.execute("SELECT * FROM users WHERE id = ?", (user_id,))
-            ).fetchone()
+            row = await (await cur.execute("SELECT * FROM users WHERE id = ?", (user_id,))).fetchone()
             if row is None:
                 return Nothing()
             return decoder.convert(
                 obj=get_result_with_names(cur, row, as_bool={"is_premium", "is_bot"}),
-                type=Option[User],
+                type=Option[User],  # type: ignore
             )
 
 
@@ -135,10 +131,7 @@ class MentionRule(
 ):
     async def check(self, message: Message, ctx: Context) -> bool:
         user = None
-        if (
-            ctx.get("message_entities")
-            and ctx.message_entities[0].type == MessageEntityType.TEXT_MENTION
-        ):
+        if ctx.get("message_entities") and ctx.message_entities[0].type == MessageEntityType.TEXT_MENTION:
             user = ctx.message_entities[0].user.unwrap()
             if (await db.get_user_by_id(user.id)).unwrap_or_none() is None:
                 await db.set_user(user)
@@ -162,7 +155,7 @@ async def get_user(_: Message, mentioned_user: User) -> str:
     return f"""
     Id -> {mentioned_user.id}
     Is bot -> {'✅' if mentioned_user.is_bot else '❌'}
-    First name -> {mentioned_user.first_name} 
+    First name -> {mentioned_user.first_name}
     Last name -> {mentioned_user.last_name.unwrap_or(repr('UNKNOWN'))}
     Is premium -> {'✅' if mentioned_user.is_premium.unwrap_or(False) else '❌'}
     Language code -> {mentioned_user.language_code.unwrap_or(repr('UNKNOWN'))}
diff --git a/examples/webhook_bot/app.py b/examples/webhook_bot/app.py
index 7212dabd..4acf27aa 100644
--- a/examples/webhook_bot/app.py
+++ b/examples/webhook_bot/app.py
@@ -6,7 +6,7 @@
 from contextlib import asynccontextmanager, suppress
 
 import uvicorn  # type: ignore
-from bot import dp
+from bot import dp  # type: ignore
 from envparse import env
 from fastapi import FastAPI, Request, Response  # type: ignore
 
@@ -21,8 +21,8 @@
 PORT = typing.cast(int, env.int("PORT"))  # > port, can be either 443, 80, 88, or 8443.
 WEBHOOK_PATH = typing.cast(
     str, env.str("WEBHOOK_PATH") + token
-)  # > webhook path: /bot/ + bot token: 123:ABC...
-WEBHOOK_URL = HOST + WEBHOOK_PATH  # > host: https://site.com + webhook path: /bot/123:ABC...
+)  # > webhook path, for example: /bot/ + bot token: 123:ABC...
+WEBHOOK_URL = HOST + WEBHOOK_PATH  # > host + webhook path
 SECRET_TOKEN = secrets.token_urlsafe(64)  # > random secret token
 
 
@@ -39,11 +39,11 @@ async def lifespan(_):
 @app.post(WEBHOOK_PATH, response_class=Response)  # type: ignore
 async def webhook_bot(request: Request) -> Response:  # type: ignore
     if request.headers.get("X-Telegram-Bot-Api-Secret-Token") != SECRET_TOKEN:  # type: ignore
-        return Response(status_code=404)
+        return Response(status_code=404)  # type: ignore
     update = Update.from_bytes(await request.body())  # type: ignore
     logger.debug("Webhook received update (update_id={})", update.update_id)
     asyncio.get_running_loop().create_task(dp.feed(update, api))
-    return Response(status_code=200)
+    return Response(status_code=200)  # type: ignore
 
 
 if __name__ == "__main__":
diff --git a/examples/webhook_bot/bot.py b/examples/webhook_bot/bot.py
index b95b3e2b..d654ba9b 100644
--- a/examples/webhook_bot/bot.py
+++ b/examples/webhook_bot/bot.py
@@ -38,9 +38,7 @@
 @dp.message(Text("/start") | FuzzyText("hello"))
 async def start(message: Message) -> None:
     me = (await message.ctx_api.get_me()).unwrap().full_name
-    await message.answer(
-        f"Hello, {message.from_user.full_name}, im {me} and i work on a webhook server!"
-    )
+    await message.answer(f"Hello, {message.from_user.full_name}, im {me} and i work on a webhook server!")
 
 
 @dp.message(Text("/cars"))
@@ -93,9 +91,7 @@ async def handle_query_quote(cb: CallbackQuery) -> None:
         HasText(),
         default=MessageReplyHandler("Im still waiting for your message!"),
     )
-    await msg.reply(
-        HTMLFormatter(block_quote(msg.text.unwrap())), parse_mode=HTMLFormatter.PARSE_MODE
-    )
+    await msg.reply(HTMLFormatter(block_quote(msg.text.unwrap())), parse_mode=HTMLFormatter.PARSE_MODE)
 
 
 @dp.callback_query(CallbackDataEq("action/guess"))
diff --git a/examples/with_nodes.py b/examples/with_nodes.py
new file mode 100644
index 00000000..afcb3776
--- /dev/null
+++ b/examples/with_nodes.py
@@ -0,0 +1,82 @@
+from examples.nodes import DB, create_tables
+from telegrinder import API, Message, Telegrinder, Token, node
+from telegrinder.bot.dispatch import Context
+from telegrinder.bot.rules import ABCRule, Markup, Text
+from telegrinder.modules import logger
+
+api = API(token=Token.from_env())
+bot = Telegrinder(api)
+logger.set_level("DEBUG")
+
+
+class IsChat(ABCRule):
+    async def check(self, source: node.Source) -> bool:
+        return source.chat.id < 0
+
+
+class IsAdmin(ABCRule):
+    async def check(self, source: node.Source, db: DB, context: Context) -> bool:
+        result = await db.execute("select * from admins where telegram_id = ?", (source.from_user.id,))
+        context["is_admin"] = True
+        return bool(await result.fetchone())
+
+
+async def promote(user_id: int, *, db: DB) -> None:
+    await db.execute("insert into admins(telegram_id) values (?) on conflict do nothing", (user_id,))
+    await db.commit()
+
+
+@bot.on.message(IsChat())
+async def photo_in_chat_handler(message: Message, p: node.Photo) -> None:
+    photo_size = p.sizes[-1]
+    await message.answer("Photo ratio H/W: {}".format(photo_size.height / photo_size.width))
+
+
+### Two handlers below require DB node
+# DB node is marked with scope = PER_EVENT
+# therefore DB node will only be resolved once on event
+# If you run this code you will see that connection opening
+# and closing is going to happen only once.
+
+
+@bot.on.message()
+async def photo_handler(
+    message: Message,
+    db: DB,
+    photo: node.Photo,
+) -> None:
+    await message.answer("Got a photo in private message")
+
+
+@bot.on.message()
+async def integer_handler(
+    message: Message,
+    db: DB,
+    i: node.TextInteger,
+) -> None:
+    await message.answer(f"{i} + 3 = {i + 3}")
+
+
+@bot.on.message(IsAdmin(), Text("/op"))
+async def add_admin_handler(message: Message, db: DB) -> str | None:
+    if not message.reply_to_message:
+        return "Need reply"
+    await promote(message.reply_to_message.unwrap().from_user.id, db=db)
+    await message.answer("Done")
+
+
+@bot.on.message(Markup("/getadmin <token>"))
+async def getadmin_handler(message: Message, token: str, db: DB) -> str:
+    if token != api.token:
+        return "Wrong token"
+    await promote(message.from_user.id, db=db)
+    return "Done"
+
+
+@bot.on.message(Text("/amiadmin"), IsAdmin().optional())
+async def amiadmin_handler(message: Message, is_admin: bool = False):
+    await message.answer("You are " + ("not " if not is_admin else "") + "an admin")
+
+
+bot.loop_wrapper.lifespan.on_startup(create_tables())
+bot.run_forever()
diff --git a/pyproject.toml b/pyproject.toml
old mode 100755
new mode 100644
index 4007da84..7b231c5a
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
 [tool.poetry]
 name = "telegrinder"
-version = "0.1.dev168"
-description = "Framework for effective and reliable async telegram bot building."
+version = "0.1.dev170"
+description = "Modern visionary telegram bot framework."
 authors = ["timoniq <tesseradecades@mail.ru>"]
 maintainers = ["luwqz1 <howluwqz1@gmail.com>"]
 license = "MIT"
@@ -17,9 +17,12 @@ keywords = [
   "custom rules",
   "telegram",
   "telegram bot api framework",
-  "telegrinder",
+  "telegrinder framework",
   "middleware",
   "composition",
+  "framework",
+  "telegrinder",
+  "waiter machine"
 ]
 classifiers = [
   "Environment :: Console",
@@ -31,6 +34,9 @@ classifiers = [
   "Typing :: Typed",
 ]
 
+[tool.poetry.urls]
+"Bug Tracker" = "https://github.com/timoniq/telegrinder/issues"
+
 [tool.poetry.dependencies]
 python = "^3.11"
 aiohttp = "^3.8.1"
@@ -43,21 +49,17 @@ certifi = "^2024.2.2"
 msgspec = "^0.18.6"
 requests = "^2.28.1"
 typing-extensions = "^4.10.0"
-fntypes = "^0.1.2.post1"
+fntypes = "^0.1.2.post2"
 
 [tool.poetry.group.dev.dependencies]
-ruff = "^0.4.4"
+ruff = "^0.5.0"
 basedpyright = "^1.12.1"
-isort = "5.13.2"
 sort-all = "1.2.0"
-black = "^24.4.2"
 pytest = "^8.0.0"
 pytest-asyncio = "^0.23.5"
-pytest-cov = "^4.0.0"
+pytest-cov = "^5.0.0"
 pytest-mock = "^3.10.0"
 
-[tool.poetry.dev-dependencies]
-
 [build-system]
 requires = ["poetry-core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"
@@ -68,13 +70,10 @@ target-version = "py311"
 lint.select = ["I", "N", "ANN", "COM", "YTT", "TCH", "SIM"]
 lint.ignore = [
   "ANN001", "ANN002", "ANN003", "ANN101", "ANN102", "ANN201", "ANN202",
-  "ANN204", "ANN206", "ANN401", "COM812", "N818", "TCH001", "TCH004"
+  "ANN204", "ANN206", "ANN401", "COM812", "N805", "N818", "TCH001", "TCH002",
+  "TCH004"
 ]
 
-[tool.black]
-line-length = 110
-target-version = ["py311", "py312"]
-
 [tool.pytest.ini_options]
 asyncio_mode = "auto"
 
@@ -84,5 +83,16 @@ asyncio_mode = "auto"
 [tool.ruff.lint.flake8-quotes]
 docstring-quotes = "double"
 
-[tool.basedpyright]
-typeCheckingMode = "basic"
\ No newline at end of file
+[tool.pyright]
+exclude = [
+    "**/__pycache__",
+    "examples/blueprint_bot",
+    "examples/webhook_bot",
+    "docs",
+    "local",
+]
+typeCheckingMode = "basic"
+pythonPlatform = "All"
+pythonVersion = "3.11"
+reportMissingImports = true
+reportMissingTypeStubs = false
diff --git a/readme.md b/readme.md
index d9e15bfa..851bbd47 100755
--- a/readme.md
+++ b/readme.md
@@ -69,6 +69,6 @@ Join our [telegram forum](https://t.me/botoforum).
 
 # License
 
-Telegrinder is [MIT licensed](./LICENSE)  
+Telegrinder is [MIT licensed](./LICENSE)\
 Copyright © 2022-2024 [timoniq](https://github.com/timoniq)\
 Copyright © 2024 [luwqz1](https://github.com/luwqz1)
diff --git a/telegrinder/__init__.py b/telegrinder/__init__.py
index 05e78377..6c66861d 100755
--- a/telegrinder/__init__.py
+++ b/telegrinder/__init__.py
@@ -1,6 +1,6 @@
 """Telegrinder
 
-Framework for effective and reliable telegram bot building.
+Modern visionary telegram bot framework.
 
 * Type hinted
 * Customizable and extensible
@@ -51,16 +51,20 @@ async def start(message: Message):
     BaseView,
     CallbackQueryCute,
     CallbackQueryReturnManager,
+    CallbackQueryRule,
     CallbackQueryView,
     ChatJoinRequestCute,
+    ChatJoinRequestRule,
     ChatJoinRequestView,
     ChatMemberUpdatedCute,
     ChatMemberView,
     Checkbox,
+    Choice,
     Dispatch,
     FuncHandler,
     InlineQueryCute,
     InlineQueryReturnManager,
+    InlineQueryRule,
     MessageCute,
     MessageReplyHandler,
     MessageReturnManager,
@@ -69,7 +73,6 @@ async def start(message: Message):
     Polling,
     RawEventView,
     ShortState,
-    SingleChoice,
     Telegrinder,
     UpdateCute,
     ViewBox,
@@ -152,6 +155,9 @@ async def start(message: Message):
     "CallbackQueryView",
     "ChatJoinRequest",
     "ChatJoinRequestCute",
+    "CallbackQueryRule",
+    "ChatJoinRequestRule",
+    "InlineQueryRule",
     "ChatJoinRequestView",
     "ChatMemberUpdated",
     "ChatMemberUpdatedCute",
@@ -190,7 +196,7 @@ async def start(message: Message):
     "ShortState",
     "SimpleI18n",
     "SimpleTranslator",
-    "SingleChoice",
+    "Choice",
     "Telegrinder",
     "Token",
     "Update",
diff --git a/telegrinder/bot/__init__.py b/telegrinder/bot/__init__.py
index 57ab3d02..6862adc5 100755
--- a/telegrinder/bot/__init__.py
+++ b/telegrinder/bot/__init__.py
@@ -22,7 +22,6 @@
     CallbackQueryView,
     ChatJoinRequestView,
     ChatMemberView,
-    CompositionDispatch,
     Context,
     Dispatch,
     FuncHandler,
@@ -35,11 +34,12 @@
     ShortState,
     ViewBox,
     WaiterMachine,
+    clear_wm_storage_worker,
     register_manager,
 )
 from .polling import ABCPolling, Polling
-from .rules import ABCRule, CallbackQueryRule, MessageRule
-from .scenario import ABCScenario, Checkbox, SingleChoice
+from .rules import ABCRule, CallbackQueryRule, ChatJoinRequestRule, InlineQueryRule, MessageRule
+from .scenario import ABCScenario, Checkbox, Choice
 
 __all__ = (
     "ABCDispatch",
@@ -59,12 +59,13 @@
     "CallbackQueryReturnManager",
     "CallbackQueryRule",
     "CallbackQueryView",
+    "ChatJoinRequestRule",
+    "InlineQueryRule",
     "ChatJoinRequestCute",
     "ChatJoinRequestView",
     "ChatMemberUpdatedCute",
     "ChatMemberView",
     "Checkbox",
-    "CompositionDispatch",
     "Context",
     "Dispatch",
     "FuncHandler",
@@ -79,10 +80,11 @@
     "Polling",
     "RawEventView",
     "ShortState",
-    "SingleChoice",
+    "Choice",
     "Telegrinder",
     "UpdateCute",
     "ViewBox",
     "WaiterMachine",
     "register_manager",
+    "clear_wm_storage_worker",
 )
diff --git a/telegrinder/bot/cute_types/base.py b/telegrinder/bot/cute_types/base.py
index effdd661..c28564e7 100755
--- a/telegrinder/bot/cute_types/base.py
+++ b/telegrinder/bot/cute_types/base.py
@@ -9,28 +9,28 @@
 from telegrinder.model import Model, get_params
 
 F = typing.TypeVar("F", bound=typing.Callable[..., typing.Any])
-CuteT = typing.TypeVar("CuteT", bound="BaseCute")
-UpdateT = typing.TypeVar("UpdateT", bound=Model)
+Cute = typing.TypeVar("Cute", bound="BaseCute")
+Update = typing.TypeVar("Update", bound=Model)
 
 Executor: typing.TypeAlias = typing.Callable[
-    [CuteT, str, dict[str, typing.Any]],
+    [Cute, str, dict[str, typing.Any]],
     typing.Awaitable[Result[typing.Any, typing.Any]],
 ]
 
 if typing.TYPE_CHECKING:
 
-    class BaseCute(Model, typing.Generic[UpdateT]):
+    class BaseCute(Model, typing.Generic[Update]):
         api: ABCAPI
 
         @classmethod
-        def from_update(cls, update: UpdateT, bound_api: ABCAPI) -> typing.Self: ...
+        def from_update(cls, update: Update, bound_api: ABCAPI) -> typing.Self: ...
 
         @property
         def ctx_api(self) -> API: ...
 
 else:
 
-    class BaseCute(typing.Generic[UpdateT]):
+    class BaseCute(typing.Generic[Update]):
         @classmethod
         def from_update(cls, update, bound_api):
             return cls(**update.to_dict(), api=bound_api)
@@ -47,13 +47,13 @@ def to_dict(self, *, exclude_fields=None):
 
 def compose_method_params(
     params: dict[str, typing.Any],
-    update: CuteT,
+    update: Cute,
     *,
     default_params: set[str | tuple[str, str]] | None = None,
-    validators: dict[str, typing.Callable[[CuteT], bool]] | None = None,
+    validators: dict[str, typing.Callable[[Cute], bool]] | None = None,
 ) -> dict[str, typing.Any]:
     """Compose method `params` from `update` by `default_params` and `validators`.
-    
+
     :param params: Method params.
     :param update: Update object.
     :param default_params: Default params. \
@@ -84,21 +84,19 @@ def compose_method_params(
 def shortcut(
     method_name: str,
     *,
-    executor: Executor[CuteT] | None = None,
+    executor: Executor[Cute] | None = None,
     custom_params: set[str] | None = None,
 ):
     def wrapper(func: F) -> F:
         @wraps(func)
         async def inner(
-            self: CuteT,
+            self: Cute,
             *args: typing.Any,
             **kwargs: typing.Any,
         ) -> typing.Any:
             if executor is None:
                 return await func(self, *args, **kwargs)
-            signature_params = {
-                k: p for k, p in inspect.signature(func).parameters.items() if k != "self"
-            }
+            signature_params = {k: p for k, p in inspect.signature(func).parameters.items() if k != "self"}
             params: dict[str, typing.Any] = {}
             index = 0
 
diff --git a/telegrinder/bot/cute_types/callback_query.py b/telegrinder/bot/cute_types/callback_query.py
index ee5e9d58..6050da4d 100755
--- a/telegrinder/bot/cute_types/callback_query.py
+++ b/telegrinder/bot/cute_types/callback_query.py
@@ -9,6 +9,7 @@
 from telegrinder.msgspec_utils import Option, decoder
 from telegrinder.types import (
     CallbackQuery,
+    Chat,
     InlineKeyboardMarkup,
     InputFile,
     InputMedia,
@@ -44,7 +45,7 @@ def is_topic_message(self) -> Option[bool]:
         by the bot with the callback button that originated the query."""
 
         return self.message.map(
-            lambda m: m.only().map(lambda m: m.is_topic_message.unwrap_or(False)).unwrap_or(False)
+            lambda m: m.only().map(lambda m: m.is_topic_message.unwrap_or(False)).unwrap_or(False),
         )
 
     @property
@@ -54,12 +55,7 @@ def message_thread_id(self) -> Option[int]:
         This will be present if the message is sent
         by the bot with the callback button that originated the query."""
 
-        return (
-            self.message.unwrap()
-            .only()
-            .map(lambda m: m.message_thread_id.unwrap())
-            .cast(Some, Nothing)
-        )
+        return self.message.unwrap().only().map(lambda m: m.message_thread_id.unwrap()).cast(Some, Nothing)
 
     @property
     def message_id(self) -> Option[int]:
@@ -69,6 +65,14 @@ def message_id(self) -> Option[int]:
 
         return self.message.map(lambda m: m.v.message_id)
 
+    @property
+    def chat(self) -> Option[Chat]:
+        """Optional. Chat the callback query originated from. This will be present
+        if the message is sent by the bot with the callback button that originated the query.
+        """
+
+        return self.message.map(lambda m: m.v.chat)
+
     def decode_callback_data(self) -> Option[dict[str, typing.Any]]:
         if "cached_callback_data" in self.__dict__:
             return self.__dict__["cached_callback_data"]
@@ -78,7 +82,7 @@ def decode_callback_data(self) -> Option[dict[str, typing.Any]]:
         self.__dict__["cached_callback_data"] = data
         return data
 
-    @shortcut("answer_callback_query")
+    @shortcut("answer_callback_query", custom_params={"callback_query_id"})
     async def answer(
         self,
         text: str | None = None,
@@ -90,8 +94,8 @@ async def answer(
     ) -> Result[bool, APIError]:
         """Shortcut `API.answer_callback_query()`, see the [documentation](https://core.telegram.org/bots/api#answercallbackquery)
 
-        Use this method to send answers to callback queries sent from inline keyboards. 
-        The answer will be displayed to the user as a notification at the top of the 
+        Use this method to send answers to callback queries sent from inline keyboards.
+        The answer will be displayed to the user as a notification at the top of the
         chat screen or as an alert. On success, True is returned.
 
         :param callback_query_id: Unique identifier for the query to be answered.
@@ -119,7 +123,14 @@ async def answer(
 
     @shortcut(
         "copy_message",
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={
+            "reply_parameters",
+            "message_thread_id",
+            "chat_id",
+            "message_id",
+            "from_chat_id",
+            "reply_markup",
+        },
     )
     async def copy(
         self,
@@ -138,11 +149,11 @@ async def copy(
     ) -> Result[MessageId, APIError]:
         """Shortcut `API.copy_message()`, see the [documentation](https://core.telegram.org/bots/api#copymessage)
 
-        Use this method to copy messages of any kind. 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 forwardMessage, 
-        but the copied message doesn't have a link to the original message. Returns 
+        Use this method to copy messages of any kind. 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 forwardMessage,
+        but the copied message doesn't have a link to the original message. Returns
         the MessageId of the sent message on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -178,7 +189,7 @@ async def copy(
 
         return await MessageCute.copy(self, **get_params(locals()))  # type: ignore
 
-    @shortcut("delete_message", custom_params={"message_thread_id"})
+    @shortcut("delete_message", custom_params={"message_thread_id", "chat_id", "message_id"})
     async def delete(
         self,
         chat_id: int | None = None,
@@ -188,16 +199,16 @@ async def delete(
     ) -> Result[bool, APIError]:
         """Shortcut `API.delete_message()`, see the [documentation](https://core.telegram.org/bots/api#deletemessage)
 
-        Use this method to delete a message, including service messages, with the 
-        following limitations: - A message can only be deleted if it was sent less 
-        than 48 hours ago. - Service messages about a supergroup, channel, or forum 
-        topic creation can't be deleted. - A dice message in a private chat can only 
-        be deleted if it was sent more than 24 hours ago. - Bots can delete outgoing 
-        messages in private chats, groups, and supergroups. - Bots can delete incoming 
-        messages in private chats. - Bots granted can_post_messages permissions 
-        can delete outgoing messages in channels. - If the bot is an administrator 
-        of a group, it can delete any message there. - If the bot has can_delete_messages 
-        permission in a supergroup or a channel, it can delete any message there. 
+        Use this method to delete a message, including service messages, with the
+        following limitations: - A message can only be deleted if it was sent less
+        than 48 hours ago. - Service messages about a supergroup, channel, or forum
+        topic creation can't be deleted. - A dice message in a private chat can only
+        be deleted if it was sent more than 24 hours ago. - Bots can delete outgoing
+        messages in private chats, groups, and supergroups. - Bots can delete incoming
+        messages in private chats. - Bots granted can_post_messages permissions
+        can delete outgoing messages in channels. - If the bot is an administrator
+        of a group, it can delete any message there. - If the bot has can_delete_messages
+        permission in a supergroup or a channel, it can delete any message there.
         Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -230,8 +241,8 @@ async def edit_text(
     ) -> Result[Variative[MessageCute, bool], APIError]:
         """Shortcut `API.edit_message_text()`, see the [documentation](https://core.telegram.org/bots/api#editmessagetext)
 
-        Use this method to edit text and game messages. On success, if the edited 
-        message is not an inline message, the edited Message is returned, otherwise 
+        Use this method to edit text and game messages. On success, if the edited
+        message is not an inline message, the edited Message is returned, otherwise
         True is returned.
 
         :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the \
@@ -281,9 +292,9 @@ async def edit_live_location(
     ) -> Result[Variative[MessageCute, bool], APIError]:
         """Shortcut `API.edit_message_live_location()`, see the [documentation](https://core.telegram.org/bots/api#editmessagelivelocation)
 
-        Use this method to edit live location messages. A location can be edited 
-        until its live_period expires or editing is explicitly disabled by a call 
-        to stopMessageLiveLocation. On success, if the edited message is not an 
+        Use this method to edit live location messages. A location can be edited
+        until its live_period expires or editing is explicitly disabled by a call
+        to stopMessageLiveLocation. On success, if the edited message is not an
         inline message, the edited Message is returned, otherwise True is returned.
 
         :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the \
@@ -333,8 +344,8 @@ async def edit_caption(
     ) -> Result[Variative[MessageCute, bool], APIError]:
         """Shortcut `API.edit_message_caption()`, see the [documentation](https://core.telegram.org/bots/api#editmessagecaption)
 
-        Use this method to edit captions of messages. On success, if the edited message 
-        is not an inline message, the edited Message is returned, otherwise True 
+        Use this method to edit captions of messages. On success, if the edited message
+        is not an inline message, the edited Message is returned, otherwise True
         is returned.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
@@ -388,12 +399,12 @@ async def edit_media(
     ) -> Result[Variative[MessageCute, bool], APIError]:
         """Shortcut `API.edit_message_media()`, see the [documentation](https://core.telegram.org/bots/api#editmessagemedia)
 
-        Use this method to edit animation, audio, document, photo, or video messages. 
-        If a message is part of a message album, then it can be edited only to an audio 
-        for audio albums, only to a document for document albums and to a photo or 
-        a video otherwise. When an inline message is edited, a new file can't be uploaded; 
-        use a previously uploaded file via its file_id or specify a URL. On success, 
-        if the edited message is not an inline message, the edited Message is returned, 
+        Use this method to edit animation, audio, document, photo, or video messages.
+        If a message is part of a message album, then it can be edited only to an audio
+        for audio albums, only to a document for document albums and to a photo or
+        a video otherwise. When an inline message is edited, a new file can't be uploaded;
+        use a previously uploaded file via its file_id or specify a URL. On success,
+        if the edited message is not an inline message, the edited Message is returned,
         otherwise True is returned.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
@@ -417,7 +428,7 @@ async def edit_media(
 
         :param caption_entities: A JSON-serialized list of special entities that appear in the caption, \
         which can be specified instead of parse_mode.
-        
+
         :param type: Required if media is not an `str | InputMedia` object. Type of the media, \
         must be one of `photo`, `video`, `animation`, `audio`, `document`.
 
@@ -441,8 +452,8 @@ async def edit_reply_markup(
     ) -> Result[Variative[MessageCute, bool], APIError]:
         """Shortcut `API.edit_message_reply_markup()`, see the [documentation](https://core.telegram.org/bots/api#editmessagereplymarkup)
 
-        Use this method to edit only the reply markup of messages. On success, if 
-        the edited message is not an inline message, the edited Message is returned, 
+        Use this method to edit only the reply markup of messages. On success, if
+        the edited message is not an inline message, the edited Message is returned,
         otherwise True is returned.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
diff --git a/telegrinder/bot/cute_types/chat_join_request.py b/telegrinder/bot/cute_types/chat_join_request.py
index 809491c2..6ec67c91 100644
--- a/telegrinder/bot/cute_types/chat_join_request.py
+++ b/telegrinder/bot/cute_types/chat_join_request.py
@@ -9,9 +9,7 @@
 from .chat_member_updated import ChatMemberShortcuts, chat_member_interaction
 
 
-class ChatJoinRequestCute(
-    BaseCute[ChatJoinRequest], ChatJoinRequest, ChatMemberShortcuts, kw_only=True
-):
+class ChatJoinRequestCute(BaseCute[ChatJoinRequest], ChatJoinRequest, ChatMemberShortcuts, kw_only=True):
     api: ABCAPI
 
     @property
@@ -31,8 +29,8 @@ async def approve(
     ) -> Result[bool, APIError]:
         """Shortcut `API.approve_chat_join_request()`, see the [documentation](https://core.telegram.org/bots/api#approvechatjoinrequest)
 
-        Use this method to approve a chat join request. The bot must be an administrator 
-        in the chat for this to work and must have the can_invite_users administrator 
+        Use this method to approve a chat join request. The bot must be an administrator
+        in the chat for this to work and must have the can_invite_users administrator
         right. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -52,8 +50,8 @@ async def decline(
     ) -> Result[bool, APIError]:
         """Shortcut `API.decline_chat_join_request()`, see the [documentation](https://core.telegram.org/bots/api#declinechatjoinrequest)
 
-        Use this method to decline a chat join request. The bot must be an administrator 
-        in the chat for this to work and must have the can_invite_users administrator 
+        Use this method to decline a chat join request. The bot must be an administrator
+        in the chat for this to work and must have the can_invite_users administrator
         right. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -63,3 +61,6 @@ async def decline(
         """
 
         ...
+
+
+__all__ = ("ChatJoinRequestCute",)
diff --git a/telegrinder/bot/cute_types/chat_member_updated.py b/telegrinder/bot/cute_types/chat_member_updated.py
index ddde9532..28f0c97d 100644
--- a/telegrinder/bot/cute_types/chat_member_updated.py
+++ b/telegrinder/bot/cute_types/chat_member_updated.py
@@ -40,10 +40,10 @@ async def ban_chat_member(
     ) -> Result[bool, APIError]:
         """Shortcut `API.ban_chat_member()`, see the [documentation](https://core.telegram.org/bots/api#banchatmember)
 
-        Use this method to ban a user in a group, a supergroup or a channel. In the case 
-        of supergroups and channels, the user will not be able to return to the chat 
-        on their own using invite links, etc., unless unbanned first. The bot must 
-        be an administrator in the chat for this to work and must have the appropriate 
+        Use this method to ban a user in a group, a supergroup or a channel. In the case
+        of supergroups and channels, the user will not be able to return to the chat
+        on their own using invite links, etc., unless unbanned first. The bot must
+        be an administrator in the chat for this to work and must have the appropriate
         administrator rights. Returns True on success.
 
         :param chat_id: Unique identifier for the target group or username of the target supergroup \
@@ -72,12 +72,12 @@ async def unban_chat_member(
     ) -> Result[bool, APIError]:
         """Shortcut `API.unban_chat_member()`, see the [documentation](https://core.telegram.org/bots/api#unbanchatmember)
 
-        Use this method to unban a previously banned user in a supergroup or channel. 
-        The user will not return to the group or channel automatically, but will 
-        be able to join via link, etc. The bot must be an administrator for this to 
-        work. By default, this method guarantees that after the call the user is 
-        not a member of the chat, but will be able to join it. So if the user is a member 
-        of the chat they will also be removed from the chat. If you don't want this, 
+        Use this method to unban a previously banned user in a supergroup or channel.
+        The user will not return to the group or channel automatically, but will
+        be able to join via link, etc. The bot must be an administrator for this to
+        work. By default, this method guarantees that after the call the user is
+        not a member of the chat, but will be able to join it. So if the user is a member
+        of the chat they will also be removed from the chat. If you don't want this,
         use the parameter only_if_banned. Returns True on success.
 
         :param chat_id: Unique identifier for the target group or username of the target supergroup \
@@ -106,9 +106,9 @@ async def restrict_chat_member(
     ) -> Result[bool, APIError]:
         """Shortcut `API.restrict_chat_member()`, see the [documentation](https://core.telegram.org/bots/api#restrictchatmember)
 
-        Use this method to restrict a user in a supergroup. The bot must be an administrator 
-        in the supergroup for this to work and must have the appropriate administrator 
-        rights. Pass True for all permissions to lift restrictions from a user. 
+        Use this method to restrict a user in a supergroup. The bot must be an administrator
+        in the supergroup for this to work and must have the appropriate administrator
+        rights. Pass True for all permissions to lift restrictions from a user.
         Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -155,9 +155,9 @@ async def promote_chat_member(
     ) -> Result[bool, APIError]:
         """Shortcut `API.promote_chat_member()`, see the [documentation](https://core.telegram.org/bots/api#promotechatmember)
 
-        Use this method to promote or demote a user in a supergroup or a channel. The 
-        bot must be an administrator in the chat for this to work and must have the 
-        appropriate administrator rights. Pass False for all boolean parameters 
+        Use this method to promote or demote a user in a supergroup or a channel. The
+        bot must be an administrator in the chat for this to work and must have the
+        appropriate administrator rights. Pass False for all boolean parameters
         to demote a user. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -217,7 +217,7 @@ async def set_chat_administrator_custom_title(
     ) -> Result[bool, APIError]:
         """Shortcut `API.set_chat_administrator_custom_title()`, see the [documentation](https://core.telegram.org/bots/api#setchatadministratorcustomtitle)
 
-        Use this method to set a custom title for an administrator in a supergroup 
+        Use this method to set a custom title for an administrator in a supergroup
         promoted by the bot. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -244,3 +244,9 @@ def from_user(self) -> User:
     @property
     def user_id(self) -> int:
         return self.from_user.id
+
+
+__all__ = (
+    "ChatMemberUpdatedCute",
+    "ChatMemberShortcuts",
+)
diff --git a/telegrinder/bot/cute_types/inline_query.py b/telegrinder/bot/cute_types/inline_query.py
index 2e62d37a..51c9fe18 100755
--- a/telegrinder/bot/cute_types/inline_query.py
+++ b/telegrinder/bot/cute_types/inline_query.py
@@ -34,7 +34,7 @@ async def answer(
     ) -> Result[bool, APIError]:
         """Shortcut `API.answer_inline_query()`, see the [documentation](https://core.telegram.org/bots/api#answerinlinequery)
 
-        Use this method to send answers to an inline query. On success, True is returned. 
+        Use this method to send answers to an inline query. On success, True is returned.
         No more than 50 results per query are allowed.
 
         :param inline_query_id: Unique identifier for the answered query.
diff --git a/telegrinder/bot/cute_types/message.py b/telegrinder/bot/cute_types/message.py
index 20978266..92bbb818 100755
--- a/telegrinder/bot/cute_types/message.py
+++ b/telegrinder/bot/cute_types/message.py
@@ -39,7 +39,8 @@
 if typing.TYPE_CHECKING:
     from datetime import datetime
 
-    from .callback_query import CallbackQueryCute
+    from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
+
 
 MediaType: typing.TypeAlias = typing.Literal[
     "animation",
@@ -48,12 +49,7 @@
     "photo",
     "video",
 ]
-ReplyMarkup: typing.TypeAlias = typing.Union[
-    InlineKeyboardMarkup,
-    ReplyKeyboardMarkup,
-    ReplyKeyboardRemove,
-    ForceReply,
-]
+ReplyMarkup: typing.TypeAlias = InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply
 
 
 async def execute_method_answer(
@@ -84,9 +80,9 @@ async def execute_method_answer(
             x
             if isinstance(x, bool)
             else (
-                MessageCute.from_update(x, bound_api=message.api)
+                message.from_update(x, bound_api=message.api)
                 if not isinstance(x, list)
-                else [MessageCute.from_update(m, bound_api=message.api) for m in x]
+                else [message.from_update(m, bound_api=message.api) for m in x]
             )
         )
     )
@@ -124,12 +120,14 @@ async def execute_method_edit(
             ),
         },
     )
+
     if "inline_message_id" in params:
         params.pop("message_id", None)
         params.pop("chat_id", None)
+
     result = await getattr(update.ctx_api, method_name)(**params)
     return result.map(
-        lambda v: Variative[MessageCute, bool](
+        lambda v: Variative["MessageCute", bool](
             v.only()
             .map(
                 lambda x: MessageCute.from_update(x, bound_api=update.api),
@@ -183,7 +181,7 @@ def custom_emoji_id(self) -> Option[str]:
     @shortcut(
         "send_message",
         executor=execute_method_answer,
-        custom_params={"link_preview_options", "reply_parameters", "message_thread_id"},
+        custom_params={"link_preview_options", "reply_parameters", "message_thread_id", "chat_id", "text"},
     )
     async def answer(
         self,
@@ -191,6 +189,7 @@ async def answer(
         chat_id: int | str | None = None,
         message_thread_id: int | None = None,
         business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         parse_mode: str | None = None,
         entities: list[MessageEntity] | None = None,
         disable_notification: bool | None = None,
@@ -221,6 +220,9 @@ async def answer(
         :param entities: A JSON-serialized list of special entities that appear in message text, \
         which can be specified instead of parse_mode.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param link_preview_options: Link preview generation options for the message.
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
@@ -238,7 +240,7 @@ async def answer(
     @shortcut(
         "send_message",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id", "message_id"},
     )
     async def reply(
         self,
@@ -247,6 +249,7 @@ async def reply(
         message_id: int | None = None,
         message_thread_id: int | None = None,
         business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         parse_mode: str | None = None,
         entities: list[MessageEntity] | None = None,
         disable_notification: bool | None = None,
@@ -261,8 +264,8 @@ async def reply(
         Use this method to send a reply to a message with text messages. On success, the sent Message is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
-        will be sent.    
-    
+        will be sent.
+
         :param chat_id: Unique identifier for the target chat or username of the target channel \
         (in the format @channelusername).
 
@@ -277,6 +280,9 @@ async def reply(
         :param entities: A JSON-serialized list of special entities that appear in message text, \
         which can be specified instead of parse_mode.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param link_preview_options: Link preview generation options for the message.
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
@@ -291,7 +297,7 @@ async def reply(
 
         ...
 
-    @shortcut("delete_message", custom_params={"message_thread_id"})
+    @shortcut("delete_message", custom_params={"message_thread_id", "chat_id", "message_id"})
     async def delete(
         self,
         chat_id: int | None = None,
@@ -301,16 +307,16 @@ async def delete(
     ) -> Result[bool, APIError]:
         """Shortcut `API.delete_message()`, see the [documentation](https://core.telegram.org/bots/api#deletemessage)
 
-        Use this method to delete a message, including service messages, with the 
-        following limitations: - A message can only be deleted if it was sent less 
-        than 48 hours ago. - Service messages about a supergroup, channel, or forum 
-        topic creation can't be deleted. - A dice message in a private chat can only 
-        be deleted if it was sent more than 24 hours ago. - Bots can delete outgoing 
-        messages in private chats, groups, and supergroups. - Bots can delete incoming 
-        messages in private chats. - Bots granted can_post_messages permissions 
-        can delete outgoing messages in channels. - If the bot is an administrator 
-        of a group, it can delete any message there. - If the bot has can_delete_messages 
-        permission in a supergroup or a channel, it can delete any message there. 
+        Use this method to delete a message, including service messages, with the
+        following limitations: - A message can only be deleted if it was sent less
+        than 48 hours ago. - Service messages about a supergroup, channel, or forum
+        topic creation can't be deleted. - A dice message in a private chat can only
+        be deleted if it was sent more than 24 hours ago. - Bots can delete outgoing
+        messages in private chats, groups, and supergroups. - Bots can delete incoming
+        messages in private chats. - Bots granted can_post_messages permissions
+        can delete outgoing messages in channels. - If the bot is an administrator
+        of a group, it can delete any message there. - If the bot has can_delete_messages
+        permission in a supergroup or a channel, it can delete any message there.
         Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -332,7 +338,7 @@ async def delete(
     @shortcut(
         "edit_message_text",
         executor=execute_method_edit,
-        custom_params={"link_preview_options", "message_thread_id"},
+        custom_params={"link_preview_options", "message_thread_id", "message_id"},
     )
     async def edit(
         self,
@@ -348,8 +354,8 @@ async def edit(
     ) -> Result[Variative[MessageCute, bool], APIError]:
         """Shortcut `API.edit_message_text()`, see the [documentation](https://core.telegram.org/bots/api#editmessagetext)
 
-        Use this method to edit text and game messages. On success, if the edited 
-        message is not an inline message, the edited Message is returned, otherwise 
+        Use this method to edit text and game messages. On success, if the edited
+        message is not an inline message, the edited Message is returned, otherwise
         True is returned.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
@@ -377,7 +383,7 @@ async def edit(
 
     @shortcut(
         "copy_message",
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id", "message_id", "from_chat_id"},
     )
     async def copy(
         self,
@@ -396,11 +402,11 @@ async def copy(
     ) -> Result[MessageId, APIError]:
         """Shortcut `API.copy_message()`, see the [documentation](https://core.telegram.org/bots/api#copymessage)
 
-        Use this method to copy messages of any kind. 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 forwardMessage, 
-        but the copied message doesn't have a link to the original message. Returns 
+        Use this method to copy messages of any kind. 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 forwardMessage,
+        but the copied message doesn't have a link to the original message. Returns
         the MessageId of the sent message on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -453,7 +459,7 @@ async def copy(
 
     @shortcut(
         "set_message_reaction",
-        custom_params={"message_thread_id"},
+        custom_params={"message_thread_id", "reaction", "chat_id", "message_id"},
     )
     async def react(
         self,
@@ -468,9 +474,9 @@ async def react(
     ) -> Result[bool, APIError]:
         """Shortcut `API.set_message_reaction()`, see the [documentation](https://core.telegram.org/bots/api#setmessagereaction)
 
-        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 
+        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.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -502,7 +508,7 @@ async def react(
             )
         return await self.ctx_api.set_message_reaction(**params)
 
-    @shortcut("forward_message", custom_params={"message_thread_id"})
+    @shortcut("forward_message", custom_params={"message_thread_id", "from_chat_id", "message_id"})
     async def forward(
         self,
         chat_id: int | str,
@@ -515,8 +521,8 @@ async def forward(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.forward_message()`, see the [documentation](https://core.telegram.org/bots/api#forwardmessage)
 
-        Use this method to forward messages of any kind. Service messages and messages 
-        with protected content can't be forwarded. On success, the sent Message 
+        Use this method to forward messages of any kind. Service messages and messages
+        with protected content can't be forwarded. On success, the sent Message
         is returned.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -548,7 +554,7 @@ async def forward(
             lambda message: MessageCute.from_update(message, bound_api=self.api),
         )
 
-    @shortcut("pin_chat_message", custom_params={"message_thread_id"})
+    @shortcut("pin_chat_message", custom_params={"message_thread_id", "chat_id", "message_id"})
     async def pin(
         self,
         chat_id: int | str | None = None,
@@ -559,10 +565,10 @@ async def pin(
     ) -> Result[bool, "APIError"]:
         """Shortcut `API.pin_chat_message()`, see the [documentation](https://core.telegram.org/bots/api#pinchatmessage)
 
-        Use this method to add a message to the list of pinned messages in a chat. If 
-        the chat is not a private chat, the bot must be an administrator in the chat 
-        for this to work and must have the 'can_pin_messages' administrator right 
-        in a supergroup or 'can_edit_messages' administrator right in a channel. 
+        Use this method to add a message to the list of pinned messages in a chat. If
+        the chat is not a private chat, the bot must be an administrator in the chat
+        for this to work and must have the 'can_pin_messages' administrator right
+        in a supergroup or 'can_edit_messages' administrator right in a channel.
         Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -586,7 +592,7 @@ async def pin(
         )
         return await self.ctx_api.pin_chat_message(**params)
 
-    @shortcut("unpin_chat_message", custom_params={"message_thread_id"})
+    @shortcut("unpin_chat_message", custom_params={"message_thread_id", "chat_id", "message_id"})
     async def unpin(
         self,
         chat_id: int | str | None = None,
@@ -596,10 +602,10 @@ async def unpin(
     ) -> Result[bool, "APIError"]:
         """Shortcut `API.unpin_chat_message()`, see the [documentation](https://core.telegram.org/bots/api#unpinchatmessage)
 
-        Use this method to remove a message from the list of pinned messages in a chat. 
-        If the chat is not a private chat, the bot must be an administrator in the chat 
-        for this to work and must have the 'can_pin_messages' administrator right 
-        in a supergroup or 'can_edit_messages' administrator right in a channel. 
+        Use this method to remove a message from the list of pinned messages in a chat.
+        If the chat is not a private chat, the bot must be an administrator in the chat
+        for this to work and must have the 'can_pin_messages' administrator right
+        in a supergroup or 'can_edit_messages' administrator right in a channel.
         Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -620,7 +626,7 @@ async def unpin(
     @shortcut(
         "send_audio",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_audio(
         self,
@@ -628,6 +634,7 @@ async def answer_audio(
         chat_id: int | str | None = None,
         message_thread_id: int | None = None,
         business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
@@ -643,10 +650,10 @@ async def answer_audio(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_audio()`, see the [documentation](https://core.telegram.org/bots/api#sendaudio)
 
-        Use this method to send audio files, if you want Telegram clients to display 
-        them in the music player. Your audio must be in the .MP3 or .M4A format. On 
-        success, the sent Message is returned. Bots can currently send audio files 
-        of up to 50 MB in size, this limit may be changed in the future. For sending 
+        Use this method to send audio files, if you want Telegram clients to display
+        them in the music player. Your audio must be in the .MP3 or .M4A format. On
+        success, the sent Message is returned. Bots can currently send audio files
+        of up to 50 MB in size, this limit may be changed in the future. For sending
         voice messages, use the sendVoice method instead.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -658,6 +665,9 @@ async def answer_audio(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param audio: Audio file to send. Pass a file_id as String to send an audio file that exists \
         on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram \
         to get an audio file from the Internet, or upload a new one using multipart/form-data. \
@@ -700,7 +710,7 @@ async def answer_audio(
     @shortcut(
         "send_animation",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_animation(
         self,
@@ -708,9 +718,11 @@ async def answer_animation(
         chat_id: int | str | None = None,
         message_thread_id: int | None = None,
         business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
         duration: int | None = None,
         width: int | None = None,
         height: int | None = None,
@@ -724,8 +736,8 @@ async def answer_animation(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_animation()`, see the [documentation](https://core.telegram.org/bots/api#sendanimation)
 
-        Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without 
-        sound). On success, the sent Message is returned. Bots can currently send 
+        Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without
+        sound). On success, the sent Message is returned. Bots can currently send
         animation files of up to 50 MB in size, this limit may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -737,6 +749,9 @@ async def answer_animation(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param animation: Animation to send. Pass a file_id as String to send an animation that exists \
         on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram \
         to get an animation from the Internet, or upload a new animation using multipart/form-data. \
@@ -765,6 +780,8 @@ async def answer_animation(
         :param caption_entities: A JSON-serialized list of special entities that appear in the caption, \
         which can be specified instead of parse_mode.
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param has_spoiler: Pass True if the animation needs to be covered with a spoiler animation. \
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
@@ -782,7 +799,7 @@ async def answer_animation(
     @shortcut(
         "send_document",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_document(
         self,
@@ -790,10 +807,12 @@ async def answer_document(
         chat_id: int | str | None = None,
         message_thread_id: int | None = None,
         business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
         disable_content_type_detection: bool | None = None,
+        show_caption_above_media: bool | None = None,
         thumbnail: InputFile | str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
@@ -803,13 +822,16 @@ async def answer_document(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_document()`, see the [documentation](https://core.telegram.org/bots/api#senddocument)
 
-        Use this method to send general files. On success, the sent Message is returned. 
-        Bots can currently send files of any type of up to 50 MB in size, this limit 
+        Use this method to send general files. On success, the sent Message is returned.
+        Bots can currently send files of any type of up to 50 MB in size, this limit
         may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
         will be sent.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param chat_id: Unique identifier for the target chat or username of the target channel \
         (in the format @channelusername).
 
@@ -841,6 +863,8 @@ async def answer_document(
         :param disable_content_type_detection: Disables automatic server-side content type detection for files uploaded \
         using multipart/form-data.
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
@@ -856,7 +880,7 @@ async def answer_document(
     @shortcut(
         "send_photo",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_photo(
         self,
@@ -864,9 +888,11 @@ async def answer_photo(
         chat_id: int | str | None = None,
         message_thread_id: int | None = None,
         business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
         has_spoiler: bool | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
@@ -887,6 +913,9 @@ async def answer_photo(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param photo: Photo to send. Pass a file_id as String to send a photo that exists on the Telegram \
         servers (recommended), pass an HTTP URL as a String for Telegram to get a \
         photo from the Internet, or upload a new photo using multipart/form-data. \
@@ -909,6 +938,8 @@ async def answer_photo(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -920,7 +951,7 @@ async def answer_photo(
     @shortcut(
         "send_sticker",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_sticker(
         self,
@@ -928,6 +959,7 @@ async def answer_sticker(
         chat_id: int | str | None = None,
         emoji: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         business_connection_id: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
@@ -937,7 +969,7 @@ async def answer_sticker(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_sticker()`, see the [documentation](https://core.telegram.org/bots/api#sendsticker)
 
-        Use this method to send static .WEBP, animated .TGS, or video .WEBM stickers. 
+        Use this method to send static .WEBP, animated .TGS, or video .WEBM stickers.
         On success, the sent Message is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -949,6 +981,9 @@ async def answer_sticker(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param sticker: Sticker to send. Pass a file_id as String to send a file that exists on the \
         Telegram servers (recommended), pass an HTTP URL as a String for Telegram \
         to get a .WEBP sticker from the Internet, or upload a new .WEBP or .TGS sticker \
@@ -973,23 +1008,16 @@ async def answer_sticker(
     @shortcut(
         "send_video",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_video(
         self,
         video: InputFile | str,
         chat_id: int | str | None = None,
+        emoji: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         business_connection_id: str | None = None,
-        caption: str | None = None,
-        parse_mode: str | None = None,
-        caption_entities: list[MessageEntity] | None = None,
-        duration: int | None = None,
-        width: int | None = None,
-        height: int | None = None,
-        thumbnail: InputFile | str | None = None,
-        has_spoiler: bool | None = None,
-        supports_streaming: bool | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
         reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
@@ -998,9 +1026,9 @@ async def answer_video(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_video()`, see the [documentation](https://core.telegram.org/bots/api#sendvideo)
 
-        Use this method to send video files, Telegram clients support MPEG4 videos 
-        (other formats may be sent as Document). On success, the sent Message is 
-        returned. Bots can currently send video files of up to 50 MB in size, this 
+        Use this method to send video files, Telegram clients support MPEG4 videos
+        (other formats may be sent as Document). On success, the sent Message is
+        returned. Bots can currently send video files of up to 50 MB in size, this
         limit may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1012,6 +1040,9 @@ async def answer_video(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param video: Video to send. Pass a file_id as String to send a video that exists on the Telegram \
         servers (recommended), pass an HTTP URL as a String for Telegram to get a \
         video from the Internet, or upload a new video using multipart/form-data. \
@@ -1046,6 +1077,8 @@ async def answer_video(
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
         :param reply_parameters: Description of the message to reply to.
@@ -1059,13 +1092,14 @@ async def answer_video(
     @shortcut(
         "send_video_note",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_video_note(
         self,
         video_note: InputFile | str,
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         duration: int | None = None,
         length: int | None = None,
         message_thread_id: int | None = None,
@@ -1078,8 +1112,8 @@ async def answer_video_note(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_video_note()`, see the [documentation](https://core.telegram.org/bots/api#sendvideonote)
 
-        As of v.4.0, Telegram clients support rounded square MPEG4 videos of up 
-        to 1 minute long. Use this method to send video messages. On success, the 
+        As of v.4.0, Telegram clients support rounded square MPEG4 videos of up
+        to 1 minute long. Use this method to send video messages. On success, the
         sent Message is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1091,6 +1125,9 @@ async def answer_video_note(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param video_note: Video note to send. Pass a file_id as String to send a video note that exists \
         on the Telegram servers (recommended) or upload a new video using multipart/form-data. \
         More information on Sending Files: https://core.telegram.org/bots/api#sending-files. \
@@ -1123,7 +1160,7 @@ async def answer_video_note(
     @shortcut(
         "send_voice",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_voice(
         self,
@@ -1131,6 +1168,7 @@ async def answer_voice(
         chat_id: int | str | None = None,
         message_thread_id: int | None = None,
         business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
@@ -1143,10 +1181,10 @@ async def answer_voice(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_voice()`, see the [documentation](https://core.telegram.org/bots/api#sendvoice)
 
-        Use this method to send audio files, if you want Telegram clients to display 
-        the file as a playable voice message. For this to work, your audio must be 
-        in an .OGG file encoded with OPUS (other formats may be sent as Audio or Document). 
-        On success, the sent Message is returned. Bots can currently send voice 
+        Use this method to send audio files, if you want Telegram clients to display
+        the file as a playable voice message. For this to work, your audio must be
+        in an .OGG file encoded with OPUS (other formats may be sent as Audio or Document).
+        On success, the sent Message is returned. Bots can currently send voice
         messages of up to 50 MB in size, this limit may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1158,6 +1196,9 @@ async def answer_voice(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param voice: Audio file to send. Pass a file_id as String to send a file that exists on the \
         Telegram servers (recommended), pass an HTTP URL as a String for Telegram \
         to get a file from the Internet, or upload a new one using multipart/form-data. \
@@ -1188,7 +1229,7 @@ async def answer_voice(
     @shortcut(
         "send_poll",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_poll(
         self,
@@ -1197,11 +1238,13 @@ async def answer_poll(
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         question_parse_mode: str | None = None,
         question_entities: list[MessageEntity] | None = None,
         is_anonymous: bool | None = None,
         type: typing.Literal["quiz", "regular"] | None = None,
         allows_multiple_answers: bool | None = None,
+        show_caption_above_media: bool | None = None,
         correct_option_id: int | None = None,
         explanation: str | None = None,
         explanation_parse_mode: str | None = None,
@@ -1228,6 +1271,9 @@ async def answer_poll(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param question_parse_mode: Mode for parsing entities in the question. See formatting options for more \
         details. Currently, only custom emoji entities are allowed.
 
@@ -1270,6 +1316,8 @@ async def answer_poll(
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
         :param reply_parameters: Description of the message to reply to.
@@ -1283,7 +1331,7 @@ async def answer_poll(
     @shortcut(
         "send_venue",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_venue(
         self,
@@ -1294,6 +1342,7 @@ async def answer_venue(
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         foursquare_id: str | None = None,
         foursquare_type: str | None = None,
         google_place_id: str | None = None,
@@ -1306,7 +1355,7 @@ async def answer_venue(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_venue()`, see the [documentation](https://core.telegram.org/bots/api#sendvenue)
 
-        Use this method to send information about a venue. On success, the sent Message 
+        Use this method to send information about a venue. On success, the sent Message
         is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1318,6 +1367,9 @@ async def answer_venue(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param latitude: Latitude of the venue.
 
         :param longitude: Longitude of the venue.
@@ -1350,7 +1402,7 @@ async def answer_venue(
     @shortcut(
         "send_dice",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_dice(
         self,
@@ -1358,6 +1410,7 @@ async def answer_dice(
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
         reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
@@ -1366,7 +1419,7 @@ async def answer_dice(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_dice()`, see the [documentation](https://core.telegram.org/bots/api#senddice)
 
-        Use this method to send an animated emoji that will display a random value. 
+        Use this method to send an animated emoji that will display a random value.
         On success, the sent Message is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1378,6 +1431,9 @@ async def answer_dice(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param emoji: 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 `🎲`. \
@@ -1397,14 +1453,15 @@ async def answer_dice(
     @shortcut(
         "send_game",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_game(
         self,
+        game_short_name: str,
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
-        game_short_name: str | None = None,
+        message_effect_id: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
         reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
@@ -1423,6 +1480,9 @@ async def answer_game(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param game_short_name: Short name of the game, serves as the unique identifier for the game. Set \
         up your games via @BotFather.
 
@@ -1440,19 +1500,20 @@ async def answer_game(
     @shortcut(
         "send_invoice",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_invoice(
         self,
         title: str,
         description: str,
         payload: str,
-        provider_token: str,
         currency: str,
         prices: list[LabeledPrice],
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
+        provider_token: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         max_tip_amount: int | None = None,
         suggested_tip_amounts: list[int] | None = None,
         start_parameter: str | None = None,
@@ -1487,6 +1548,9 @@ async def answer_invoice(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param title: Product name, 1-32 characters.
 
         :param description: Product description, 1-255 characters.
@@ -1560,7 +1624,7 @@ async def answer_invoice(
     @shortcut(
         "send_chat_action",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_chat_action(
         self,
@@ -1572,10 +1636,10 @@ async def answer_chat_action(
     ) -> Result[bool, APIError]:
         """Shortcut `API.send_chat_action()`, see the [documentation](https://core.telegram.org/bots/api#sendchataction)
 
-        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. We only recommend using this method when a response from the 
+        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. We only recommend using this method when a response from the
         bot will take a noticeable amount of time to arrive.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1605,6 +1669,7 @@ async def answer_media_group(
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
@@ -1615,8 +1680,8 @@ async def answer_media_group(
     ) -> Result[list[MessageCute], APIError]:
         """Shortcut `API.send_media_group()`, see the [documentation](https://core.telegram.org/bots/api#sendmediagroup)
 
-        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 
+        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 Messages that were sent is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1628,6 +1693,9 @@ async def answer_media_group(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param media: A JSON-serialized array describing messages to be sent, must include 2-10 \
         items.
 
@@ -1664,7 +1732,7 @@ async def answer_media_group(
     @shortcut(
         "send_location",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_location(
         self,
@@ -1673,6 +1741,7 @@ async def answer_location(
         chat_id: int | str | None = None,
         message_thread_id: int | None = None,
         business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         horizontal_accuracy: float | None = None,
         heading: int | None = None,
         live_period: int | None = None,
@@ -1696,6 +1765,9 @@ async def answer_location(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param latitude: Latitude of the location.
 
         :param longitude: Longitude of the location.
@@ -1726,7 +1798,7 @@ async def answer_location(
     @shortcut(
         "send_contact",
         executor=execute_method_answer,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def answer_contact(
         self,
@@ -1737,6 +1809,7 @@ async def answer_contact(
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
         reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
@@ -1756,6 +1829,9 @@ async def answer_contact(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param phone_number: Contact's phone number.
 
         :param first_name: Contact's first name.
@@ -1779,14 +1855,15 @@ async def answer_contact(
     @shortcut(
         "send_audio",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_audio(
         self,
         audio: InputFile | str,
         chat_id: int | str | None = None,
-        business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
@@ -1802,10 +1879,10 @@ async def reply_audio(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_audio()`, see the [documentation](https://core.telegram.org/bots/api#sendaudio)
 
-        Use this method to send a reply to a message with audio files, if you want Telegram clients to display 
-        them in the music player. Your audio must be in the .MP3 or .M4A format. On 
-        success, the sent Message is returned. Bots can currently send audio files 
-        of up to 50 MB in size, this limit may be changed in the future. For sending 
+        Use this method to send a reply to a message with audio files, if you want Telegram clients to display
+        them in the music player. Your audio must be in the .MP3 or .M4A format. On
+        success, the sent Message is returned. Bots can currently send audio files
+        of up to 50 MB in size, this limit may be changed in the future. For sending
         voice messages, use the sendVoice method instead.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1817,6 +1894,9 @@ async def reply_audio(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param audio: Audio file to send. Pass a file_id as String to send an audio file that exists \
         on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram \
         to get an audio file from the Internet, or upload a new one using multipart/form-data. \
@@ -1859,17 +1939,19 @@ async def reply_audio(
     @shortcut(
         "send_animation",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_animation(
         self,
         animation: InputFile | str,
         chat_id: int | str | None = None,
-        business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
         duration: int | None = None,
         width: int | None = None,
         height: int | None = None,
@@ -1883,8 +1965,8 @@ async def reply_animation(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_animation()`, see the [documentation](https://core.telegram.org/bots/api#sendanimation)
 
-        Use this method to send a reply to a message with animation files (GIF or H.264/MPEG-4 AVC video without 
-        sound). On success, the sent Message is returned. Bots can currently send 
+        Use this method to send a reply to a message with animation files (GIF or H.264/MPEG-4 AVC video without
+        sound). On success, the sent Message is returned. Bots can currently send
         animation files of up to 50 MB in size, this limit may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1896,6 +1978,9 @@ async def reply_animation(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param animation: Animation to send. Pass a file_id as String to send an animation that exists \
         on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram \
         to get an animation from the Internet, or upload a new animation using multipart/form-data. \
@@ -1928,6 +2013,8 @@ async def reply_animation(
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
         :param reply_parameters: Description of the message to reply to.
@@ -1941,18 +2028,20 @@ async def reply_animation(
     @shortcut(
         "send_document",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_document(
         self,
         document: InputFile | str,
         chat_id: int | str | None = None,
-        business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
         disable_content_type_detection: bool | None = None,
+        show_caption_above_media: bool | None = None,
         thumbnail: InputFile | str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
@@ -1962,8 +2051,8 @@ async def reply_document(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_document()`, see the [documentation](https://core.telegram.org/bots/api#senddocument)
 
-        Use this method to send a reply to a message with general files. On success, the sent Message is returned. 
-        Bots can currently send files of any type of up to 50 MB in size, this limit 
+        Use this method to send a reply to a message with general files. On success, the sent Message is returned.
+        Bots can currently send files of any type of up to 50 MB in size, this limit
         may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1975,6 +2064,9 @@ async def reply_document(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param document: File to send. Pass a file_id as String to send a file that exists on the Telegram \
         servers (recommended), pass an HTTP URL as a String for Telegram to get a \
         file from the Internet, or upload a new one using multipart/form-data. \
@@ -2002,6 +2094,8 @@ async def reply_document(
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
         :param reply_parameters: Description of the message to reply to.
@@ -2015,17 +2109,19 @@ async def reply_document(
     @shortcut(
         "send_photo",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_photo(
         self,
         photo: InputFile | str,
         chat_id: int | str | None = None,
-        business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
         has_spoiler: bool | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
@@ -2046,6 +2142,9 @@ async def reply_photo(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param photo: Photo to send. Pass a file_id as String to send a photo that exists on the Telegram \
         servers (recommended), pass an HTTP URL as a String for Telegram to get a \
         photo from the Internet, or upload a new photo using multipart/form-data. \
@@ -2066,6 +2165,8 @@ async def reply_photo(
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
         :param reply_parameters: Description of the message to reply to.
@@ -2079,15 +2180,16 @@ async def reply_photo(
     @shortcut(
         "send_sticker",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_sticker(
         self,
         sticker: InputFile | str,
         chat_id: int | str | None = None,
-        business_connection_id: str | None = None,
         emoji: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
+        business_connection_id: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
         reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
@@ -2096,7 +2198,7 @@ async def reply_sticker(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_sticker()`, see the [documentation](https://core.telegram.org/bots/api#sendsticker)
 
-        Use this method to send a reply to a message with static .WEBP, animated .TGS, or video .WEBM stickers. 
+        Use this method to send a reply to a message with static .WEBP, animated .TGS, or video .WEBM stickers.
         On success, the sent Message is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -2108,6 +2210,9 @@ async def reply_sticker(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param sticker: Sticker to send. Pass a file_id as String to send a file that exists on the \
         Telegram servers (recommended), pass an HTTP URL as a String for Telegram \
         to get a .WEBP sticker from the Internet, or upload a new .WEBP or .TGS sticker \
@@ -2132,23 +2237,16 @@ async def reply_sticker(
     @shortcut(
         "send_video",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_video(
         self,
-        video: InputFile | str,
+        sticker: InputFile | str,
         chat_id: int | str | None = None,
-        business_connection_id: str | None = None,
+        emoji: str | None = None,
         message_thread_id: int | None = None,
-        caption: str | None = None,
-        parse_mode: str | None = None,
-        caption_entities: list[MessageEntity] | None = None,
-        duration: int | None = None,
-        width: int | None = None,
-        height: int | None = None,
-        thumbnail: InputFile | str | None = None,
-        has_spoiler: bool | None = None,
-        supports_streaming: bool | None = None,
+        message_effect_id: str | None = None,
+        business_connection_id: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
         reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
@@ -2157,9 +2255,9 @@ async def reply_video(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_video()`, see the [documentation](https://core.telegram.org/bots/api#sendvideo)
 
-        Use this method to send a reply to a message with video files, Telegram clients support MPEG4 videos 
-        (other formats may be sent as Document). On success, the sent Message is 
-        returned. Bots can currently send video files of up to 50 MB in size, this 
+        Use this method to send a reply to a message with video files, Telegram clients support MPEG4 videos
+        (other formats may be sent as Document). On success, the sent Message is
+        returned. Bots can currently send video files of up to 50 MB in size, this
         limit may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -2171,6 +2269,9 @@ async def reply_video(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param video: Video to send. Pass a file_id as String to send a video that exists on the Telegram \
         servers (recommended), pass an HTTP URL as a String for Telegram to get a \
         video from the Internet, or upload a new video using multipart/form-data. \
@@ -2218,15 +2319,16 @@ async def reply_video(
     @shortcut(
         "send_video_note",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_video_note(
         self,
         video_note: InputFile | str,
         chat_id: int | str | None = None,
+        business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         duration: int | None = None,
         length: int | None = None,
-        business_connection_id: str | None = None,
         message_thread_id: int | None = None,
         thumbnail: InputFile | str | None = None,
         disable_notification: bool | None = None,
@@ -2237,8 +2339,8 @@ async def reply_video_note(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_video_note()`, see the [documentation](https://core.telegram.org/bots/api#sendvideonote)
 
-        As of v.4.0, Telegram clients support rounded square MPEG4 videos of up 
-        to 1 minute long. Use this method to send a reply to a message with video messages. On success, the 
+        As of v.4.0, Telegram clients support rounded square MPEG4 videos of up
+        to 1 minute long. Use this method to send a reply to a message with video messages. On success, the
         sent Message is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -2250,6 +2352,9 @@ async def reply_video_note(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param video_note: Video note to send. Pass a file_id as String to send a video note that exists \
         on the Telegram servers (recommended) or upload a new video using multipart/form-data. \
         More information on Sending Files: https://core.telegram.org/bots/api#sending-files. \
@@ -2282,14 +2387,15 @@ async def reply_video_note(
     @shortcut(
         "send_voice",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_voice(
         self,
         voice: InputFile | str,
         chat_id: int | str | None = None,
-        business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
@@ -2302,10 +2408,10 @@ async def reply_voice(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_voice()`, see the [documentation](https://core.telegram.org/bots/api#sendvoice)
 
-        Use this method to send a reply to a message with audio files, if you want Telegram clients to display 
-        the file as a playable voice message. For this to work, your audio must be 
-        in an .OGG file encoded with OPUS (other formats may be sent as Audio or Document). 
-        On success, the sent Message is returned. Bots can currently send voice 
+        Use this method to send a reply to a message with audio files, if you want Telegram clients to display
+        the file as a playable voice message. For this to work, your audio must be
+        in an .OGG file encoded with OPUS (other formats may be sent as Audio or Document).
+        On success, the sent Message is returned. Bots can currently send voice
         messages of up to 50 MB in size, this limit may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -2317,6 +2423,9 @@ async def reply_voice(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param voice: Audio file to send. Pass a file_id as String to send a file that exists on the \
         Telegram servers (recommended), pass an HTTP URL as a String for Telegram \
         to get a file from the Internet, or upload a new one using multipart/form-data. \
@@ -2347,7 +2456,7 @@ async def reply_voice(
     @shortcut(
         "send_poll",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_poll(
         self,
@@ -2356,11 +2465,13 @@ async def reply_poll(
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         question_parse_mode: str | None = None,
         question_entities: list[MessageEntity] | None = None,
         is_anonymous: bool | None = None,
         type: typing.Literal["quiz", "regular"] | None = None,
         allows_multiple_answers: bool | None = None,
+        show_caption_above_media: bool | None = None,
         correct_option_id: int | None = None,
         explanation: str | None = None,
         explanation_parse_mode: str | None = None,
@@ -2387,6 +2498,9 @@ async def reply_poll(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param question_parse_mode: Mode for parsing entities in the question. See formatting options for more \
         details. Currently, only custom emoji entities are allowed.
 
@@ -2429,6 +2543,8 @@ async def reply_poll(
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
         :param reply_parameters: Description of the message to reply to.
@@ -2442,7 +2558,7 @@ async def reply_poll(
     @shortcut(
         "send_venue",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_venue(
         self,
@@ -2453,6 +2569,7 @@ async def reply_venue(
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         foursquare_id: str | None = None,
         foursquare_type: str | None = None,
         google_place_id: str | None = None,
@@ -2465,7 +2582,7 @@ async def reply_venue(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_venue()`, see the [documentation](https://core.telegram.org/bots/api#sendvenue)
 
-        Use this method to send a reply to a message with information about a venue. On success, the sent Message 
+        Use this method to send a reply to a message with information about a venue. On success, the sent Message
         is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -2477,6 +2594,9 @@ async def reply_venue(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param latitude: Latitude of the venue.
 
         :param longitude: Longitude of the venue.
@@ -2509,14 +2629,15 @@ async def reply_venue(
     @shortcut(
         "send_dice",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_dice(
         self,
+        emoji: DiceEmoji | None = None,
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
-        emoji: DiceEmoji | None = None,
+        message_effect_id: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
         reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
@@ -2525,7 +2646,7 @@ async def reply_dice(
     ) -> Result[MessageCute, APIError]:
         """Shortcut `API.send_dice()`, see the [documentation](https://core.telegram.org/bots/api#senddice)
 
-        Use this method to send a reply to a message with an animated emoji that will display a random value. 
+        Use this method to send a reply to a message with an animated emoji that will display a random value.
         On success, the sent Message is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -2537,6 +2658,9 @@ async def reply_dice(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param emoji: 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 `🎲`. \
@@ -2556,14 +2680,15 @@ async def reply_dice(
     @shortcut(
         "send_game",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_game(
         self,
+        game_short_name: str,
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
-        game_short_name: str | None = None,
+        message_effect_id: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
         reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
@@ -2582,6 +2707,9 @@ async def reply_game(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param game_short_name: Short name of the game, serves as the unique identifier for the game. Set \
         up your games via @BotFather.
 
@@ -2599,19 +2727,20 @@ async def reply_game(
     @shortcut(
         "send_invoice",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_invoice(
         self,
         title: str,
         description: str,
         payload: str,
-        provider_token: str,
         currency: str,
         prices: list[LabeledPrice],
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        provider_token: str | None = None,
+        message_effect_id: str | None = None,
         max_tip_amount: int | None = None,
         suggested_tip_amounts: list[int] | None = None,
         start_parameter: str | None = None,
@@ -2646,6 +2775,9 @@ async def reply_invoice(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param title: Product name, 1-32 characters.
 
         :param description: Product description, 1-255 characters.
@@ -2721,6 +2853,7 @@ async def reply_invoice(
         custom_params={
             "media",
             "reply_parameters",
+            "chat_id",
             "message_thread_id",
             "caption",
             "caption_entities",
@@ -2733,6 +2866,7 @@ async def reply_media_group(
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         caption: str | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
@@ -2743,8 +2877,8 @@ async def reply_media_group(
     ) -> Result[list[MessageCute], APIError]:
         """Shortcut `API.send_media_group()`, see the [documentation](https://core.telegram.org/bots/api#sendmediagroup)
 
-        Use this method to send a reply to a message with a group of photos, videos, documents or audios as 
-        an album. Documents and audio files can be only grouped in an album with messages 
+        Use this method to send a reply to a message with 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 Messages that were sent is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -2756,6 +2890,9 @@ async def reply_media_group(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param media: A JSON-serialized array describing messages to be sent, must include 2-10 \
         items.
 
@@ -2780,15 +2917,16 @@ async def reply_media_group(
     @shortcut(
         "send_location",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_location(
         self,
         latitude: float,
         longitude: float,
         chat_id: int | str | None = None,
-        business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        business_connection_id: str | None = None,
+        message_effect_id: str | None = None,
         horizontal_accuracy: float | None = None,
         heading: int | None = None,
         live_period: int | None = None,
@@ -2812,6 +2950,9 @@ async def reply_location(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param latitude: Latitude of the location.
 
         :param longitude: Longitude of the location.
@@ -2842,7 +2983,7 @@ async def reply_location(
     @shortcut(
         "send_contact",
         executor=execute_method_reply,
-        custom_params={"reply_parameters", "message_thread_id"},
+        custom_params={"reply_parameters", "message_thread_id", "chat_id"},
     )
     async def reply_contact(
         self,
@@ -2853,6 +2994,7 @@ async def reply_contact(
         chat_id: int | str | None = None,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
+        message_effect_id: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
         reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
@@ -2872,6 +3014,9 @@ async def reply_contact(
         :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
         forum supergroups only.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param phone_number: Contact's phone number.
 
         :param first_name: Contact's first name.
@@ -2895,7 +3040,7 @@ async def reply_contact(
     @shortcut(
         "edit_message_live_location",
         executor=execute_method_edit,
-        custom_params={"message_thread_id"},
+        custom_params={"message_thread_id", "chat_id", "message_id"},
     )
     async def edit_live_location(
         self,
@@ -2914,9 +3059,9 @@ async def edit_live_location(
     ) -> Result[Variative[MessageCute, bool], APIError]:
         """Shortcut `API.edit_message_live_location()`, see the [documentation](https://core.telegram.org/bots/api#editmessagelivelocation)
 
-        Use this method to edit live location messages. A location can be edited 
-        until its live_period expires or editing is explicitly disabled by a call 
-        to stopMessageLiveLocation. On success, if the edited message is not an 
+        Use this method to edit live location messages. A location can be edited
+        until its live_period expires or editing is explicitly disabled by a call
+        to stopMessageLiveLocation. On success, if the edited message is not an
         inline message, the edited Message is returned, otherwise True is returned.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
@@ -2957,7 +3102,7 @@ async def edit_live_location(
     @shortcut(
         "edit_message_caption",
         executor=execute_method_edit,
-        custom_params={"message_thread_id"},
+        custom_params={"message_thread_id", "chat_id", "message_id"},
     )
     async def edit_caption(
         self,
@@ -2967,13 +3112,14 @@ async def edit_caption(
         message_thread_id: int | None = None,
         parse_mode: str | None = None,
         caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
         reply_markup: InlineKeyboardMarkup | None = None,
         **other: typing.Any,
     ) -> Result[Variative[MessageCute, bool], APIError]:
         """Shortcut `API.edit_message_caption()`, see the [documentation](https://core.telegram.org/bots/api#editmessagecaption)
 
-        Use this method to edit captions of messages. On success, if the edited message 
-        is not an inline message, the edited Message is returned, otherwise True 
+        Use this method to edit captions of messages. On success, if the edited message
+        is not an inline message, the edited Message is returned, otherwise True
         is returned.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
@@ -2993,6 +3139,8 @@ async def edit_caption(
         :param caption_entities: A JSON-serialized list of special entities that appear in the caption, \
         which can be specified instead of parse_mode.
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param reply_markup: A JSON-serialized object for an inline keyboard."""
 
         ...
@@ -3004,6 +3152,8 @@ async def edit_caption(
             "type",
             "message_thread_id",
             "caption",
+            "chat_id",
+            "message_id",
             "parse_mode",
             "caption_entities",
         },
@@ -3023,12 +3173,12 @@ async def edit_media(
     ) -> Result[Variative[MessageCute, bool], APIError]:
         """Shortcut `API.edit_message_media()`, see the [documentation](https://core.telegram.org/bots/api#editmessagemedia)
 
-        Use this method to edit animation, audio, document, photo, or video messages. 
-        If a message is part of a message album, then it can be edited only to an audio 
-        for audio albums, only to a document for document albums and to a photo or 
-        a video otherwise. When an inline message is edited, a new file can't be uploaded; 
-        use a previously uploaded file via its file_id or specify a URL. On success, 
-        if the edited message is not an inline message, the edited Message is returned, 
+        Use this method to edit animation, audio, document, photo, or video messages.
+        If a message is part of a message album, then it can be edited only to an audio
+        for audio albums, only to a document for document albums and to a photo or
+        a video otherwise. When an inline message is edited, a new file can't be uploaded;
+        use a previously uploaded file via its file_id or specify a URL. On success,
+        if the edited message is not an inline message, the edited Message is returned,
         otherwise True is returned.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
@@ -3049,7 +3199,7 @@ async def edit_media(
 
         :param caption_entities: A JSON-serialized list of special entities that appear in the caption, \
         which can be specified instead of parse_mode.
-        
+
         :param type: Required if media is not an `str | InputMedia` object. Type of the media, \
         must be one of `photo`, `video`, `animation`, `audio`, `document`.
 
@@ -3058,9 +3208,7 @@ async def edit_media(
         params = get_params(locals())
 
         if not isinstance(media, InputMedia):
-            assert (
-                type
-            ), "parameter 'type' is required, because 'media' is not an 'InputMedia' object."
+            assert type, "parameter 'type' is required, because 'media' is not an 'InputMedia' object."
             params["media"] = input_media(
                 params.pop("type"),
                 media,
@@ -3074,7 +3222,7 @@ async def edit_media(
     @shortcut(
         "edit_message_reply_markup",
         executor=execute_method_edit,
-        custom_params={"message_thread_id"},
+        custom_params={"message_thread_id", "chat_id", "message_id"},
     )
     async def edit_reply_markup(
         self,
@@ -3086,8 +3234,8 @@ async def edit_reply_markup(
     ) -> Result[Variative[MessageCute, bool], APIError]:
         """Shortcut `API.edit_message_reply_markup()`, see the [documentation](https://core.telegram.org/bots/api#editmessagereplymarkup)
 
-        Use this method to edit only the reply markup of messages. On success, if 
-        the edited message is not an inline message, the edited Message is returned, 
+        Use this method to edit only the reply markup of messages. On success, if
+        the edited message is not an inline message, the edited Message is returned,
         otherwise True is returned.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
diff --git a/telegrinder/bot/cute_types/update.py b/telegrinder/bot/cute_types/update.py
index 4a50c721..fb8d35ce 100755
--- a/telegrinder/bot/cute_types/update.py
+++ b/telegrinder/bot/cute_types/update.py
@@ -15,16 +15,12 @@ class UpdateCute(BaseCute[Update], Update, kw_only=True):
     api: ABCAPI
 
     @property
-    def incoming_update(self) -> Option[Model]:
-        return getattr(
-            self,
-            self.update_type.expect("Update object has no incoming update.").value,
-        )
+    def incoming_update(self) -> Model:
+        return getattr(self, self.update_type.value).unwrap()
 
     def get_event(self, event_model: type[ModelT]) -> Option[ModelT]:
-        match self.incoming_update:
-            case Some(event) if isinstance(event, event_model):
-                return Some(event)
+        if isinstance(self.incoming_update, event_model):
+            return Some(self.incoming_update)
         return Nothing()
 
 
diff --git a/telegrinder/bot/cute_types/utils.py b/telegrinder/bot/cute_types/utils.py
index 1cc9945b..d9c9b59e 100755
--- a/telegrinder/bot/cute_types/utils.py
+++ b/telegrinder/bot/cute_types/utils.py
@@ -78,11 +78,7 @@ def compose_reactions(
         (
             ReactionTypeEmoji("emoji", emoji)
             if isinstance(emoji, ReactionEmoji)
-            else (
-                ReactionTypeEmoji("emoji", ReactionEmoji(emoji))
-                if isinstance(emoji, str)
-                else emoji
-            )
+            else (ReactionTypeEmoji("emoji", ReactionEmoji(emoji)) if isinstance(emoji, str) else emoji)
         )
         for emoji in ([reactions] if isinstance(reactions, str) else reactions)
     ]
diff --git a/telegrinder/bot/dispatch/__init__.py b/telegrinder/bot/dispatch/__init__.py
index ef9f72b6..78252fe4 100755
--- a/telegrinder/bot/dispatch/__init__.py
+++ b/telegrinder/bot/dispatch/__init__.py
@@ -1,5 +1,4 @@
 from .abc import ABCDispatch
-from .composition import CompositionDispatch
 from .context import Context
 from .dispatch import ABCRule, Dispatch, TelegrinderCtx
 from .handler import ABCHandler, FuncHandler, MessageReplyHandler
@@ -27,7 +26,7 @@
     RawEventView,
     ViewBox,
 )
-from .waiter_machine import ShortState, WaiterMachine
+from .waiter_machine import ShortState, WaiterMachine, clear_wm_storage_worker
 
 __all__ = (
     "ABCDispatch",
@@ -44,7 +43,6 @@
     "CallbackQueryView",
     "ChatJoinRequestView",
     "ChatMemberView",
-    "CompositionDispatch",
     "Context",
     "Dispatch",
     "FuncHandler",
@@ -62,4 +60,5 @@
     "check_rule",
     "process_inner",
     "register_manager",
+    "clear_wm_storage_worker",
 )
diff --git a/telegrinder/bot/dispatch/abc.py b/telegrinder/bot/dispatch/abc.py
index 01923b4b..842843c2 100755
--- a/telegrinder/bot/dispatch/abc.py
+++ b/telegrinder/bot/dispatch/abc.py
@@ -17,5 +17,9 @@ async def feed(self, event: Update, api: ABCAPI) -> bool:
     def load(self, external: typing.Self) -> None:
         pass
 
+    def load_many(self, *externals: typing.Self) -> None:
+        for external in externals:
+            self.load(external)
+
 
 __all__ = ("ABCDispatch",)
diff --git a/telegrinder/bot/dispatch/composition.py b/telegrinder/bot/dispatch/composition.py
deleted file mode 100755
index 2514fd71..00000000
--- a/telegrinder/bot/dispatch/composition.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import inspect
-import typing
-
-from telegrinder.api.abc import ABCAPI
-from telegrinder.bot.cute_types import UpdateCute
-from telegrinder.bot.dispatch.abc import ABCDispatch
-from telegrinder.node import (
-    ComposeError,
-    ContainerNode,
-    Node,
-    NodeCollection,
-    NodeSession,
-    compose_node,
-)
-from telegrinder.tools import magic_bundle
-from telegrinder.types import Update
-
-
-class Composition:
-    nodes: dict[str, type[Node]]
-
-    def __init__(self, func: typing.Callable, is_blocking: bool) -> None:
-        self.func = func
-        self.nodes = {
-            name: parameter.annotation
-            for name, parameter in inspect.signature(func).parameters.items()
-        }
-        self.is_blocking = is_blocking
-
-    def __repr__(self) -> str:
-        return "<{}: for function={!r} with nodes={}>".format(
-            ("blocking " if self.is_blocking else "") + self.__class__.__name__,
-            self.func.__name__,
-            self.nodes,
-        )
-
-    async def compose_nodes(self, update: UpdateCute) -> NodeCollection | None:
-        nodes: dict[str, NodeSession] = {}
-        for name, node_t in self.nodes.items():
-            try:
-                nodes[name] = await compose_node(node_t, update)
-            except ComposeError:
-                await NodeCollection(nodes).close_all()
-                return None
-        return NodeCollection(nodes)
-
-    async def __call__(self, **kwargs: typing.Any) -> typing.Any:
-        return await self.func(**magic_bundle(self.func, kwargs, start_idx=0, bundle_ctx=False))  # type: ignore
-
-
-class CompositionDispatch(ABCDispatch):
-    def __init__(self) -> None:
-        self.compositions: list[Composition] = []
-
-    def __repr__(self) -> str:
-        return "<{}: with compositions={!r}>".format(
-            self.__class__.__name__,
-            self.compositions,
-        )
-
-    async def feed(self, event: Update, api: ABCAPI) -> bool:
-        update = UpdateCute(**event.to_dict(), api=api)
-        is_found = False
-        for composition in self.compositions:
-            nodes = await composition.compose_nodes(update)
-            if nodes is not None:
-                result = await composition(**nodes.values())
-                await nodes.close_all(with_value=result)
-                if composition.is_blocking:
-                    return True
-                is_found = True
-        return is_found
-
-    def load(self, external: typing.Self):
-        self.compositions.extend(external.compositions)
-
-    def __call__(self, *container_nodes: type[Node], is_blocking: bool = True):
-        def wrapper(func: typing.Callable):
-            composition = Composition(func, is_blocking)
-            if container_nodes:
-                composition.nodes["container"] = ContainerNode.link_nodes(list(container_nodes))
-            self.compositions.append(composition)
-            return func
-
-        return wrapper
-
-
-__all__ = ("Composition", "CompositionDispatch")
diff --git a/telegrinder/bot/dispatch/context.py b/telegrinder/bot/dispatch/context.py
index 48540193..e4cb9f96 100755
--- a/telegrinder/bot/dispatch/context.py
+++ b/telegrinder/bot/dispatch/context.py
@@ -15,7 +15,7 @@ class Context(dict[str, AnyValue]):
     """Context class for rules and middlewares.
     ```
     class MyRule(ABCRule[T]):
-        adapter: ABCAdapter[Update, T] = RawUpdateAdapter()
+        adapter = RawUpdateAdapter()
 
         async def check(self, event: T, ctx: Context) -> bool:
             ctx.set("value", (await event.ctx_api.get_me()).unwrap())
@@ -28,17 +28,17 @@ async def check(self, event: T, ctx: Context) -> bool:
     def __init__(self, **kwargs: AnyValue) -> None:
         cls_vars = vars(self.__class__)
         defaults = {}
+
         for k in self.__class__.__annotations__:
             if k in cls_vars:
                 defaults[k] = cls_vars[k]
                 delattr(self.__class__, k)
+
         dict.__init__(self, **defaults | kwargs)
 
     @recursive_repr()
     def __repr__(self) -> str:
-        return "{}({})".format(
-            self.__class__.__name__, ", ".join(f"{k}={v!r}" for k, v in self.items())
-        )
+        return "{}({})".format(self.__class__.__name__, ", ".join(f"{k}={v!r}" for k, v in self.items()))
 
     def __setitem__(self, __key: Key, __value: AnyValue) -> None:
         dict.__setitem__(self, self.key_to_str(__key), __value)
@@ -71,6 +71,11 @@ def set(self, key: Key, value: AnyValue) -> None:
     def get(self, key: Key, default: T | None = None) -> T | AnyValue:
         return dict.get(self, key, default)
 
+    def get_or_set(self, key: Key, default: T) -> T:
+        if key not in self:
+            self.set(key, default)
+        return self.get(key)
+
     def delete(self, key: Key) -> None:
         del self[key]
 
diff --git a/telegrinder/bot/dispatch/dispatch.py b/telegrinder/bot/dispatch/dispatch.py
index 54fc7bd1..17a3e430 100755
--- a/telegrinder/bot/dispatch/dispatch.py
+++ b/telegrinder/bot/dispatch/dispatch.py
@@ -6,6 +6,7 @@
 
 from telegrinder.api.abc import ABCAPI
 from telegrinder.bot.cute_types.base import BaseCute
+from telegrinder.bot.cute_types.update import UpdateCute
 from telegrinder.bot.dispatch.context import Context
 from telegrinder.bot.rules import ABCRule
 from telegrinder.modules import logger
@@ -17,21 +18,19 @@
 from .handler import ABCHandler, FuncHandler
 from .handler.func import ErrorHandlerT
 from .view.box import (
-    CallbackQueryViewT,
-    ChatJoinRequestViewT,
-    ChatMemberViewT,
-    InlineQueryViewT,
-    MessageViewT,
-    RawEventViewT,
+    CallbackQueryView,
+    ChatJoinRequestView,
+    ChatMemberView,
+    InlineQueryView,
+    MessageView,
+    RawEventView,
     ViewBox,
 )
 
 T = typing.TypeVar("T")
 R = typing.TypeVar("R")
 P = typing.ParamSpec("P")
-Handler = typing.Callable[
-    typing.Concatenate[T, ...], typing.Coroutine[typing.Any, typing.Any, typing.Any]
-]
+Handler = typing.Callable[typing.Concatenate[T, ...], typing.Coroutine[typing.Any, typing.Any, typing.Any]]
 Event = typing.TypeVar("Event", bound=BaseCute)
 
 DEFAULT_DATACLASS: typing.Final[type[Update]] = Update
@@ -41,12 +40,12 @@
 class Dispatch(
     ABCDispatch,
     ViewBox[
-        CallbackQueryViewT,
-        ChatJoinRequestViewT,
-        ChatMemberViewT,
-        InlineQueryViewT,
-        MessageViewT,
-        RawEventViewT,
+        CallbackQueryView,
+        ChatJoinRequestView,
+        ChatMemberView,
+        InlineQueryView,
+        MessageView,
+        RawEventView,
     ],
 ):
     global_context: TelegrinderCtx = dataclasses.field(
@@ -63,55 +62,56 @@ def __repr__(self) -> str:
 
     @property
     def patcher(self) -> Patcher:
-        """Alias `patcher` to get `vbml.Patcher` from the global context"""
+        """Alias `patcher` to get `vbml.Patcher` from the global context."""
+
         return self.global_context.vbml_patcher
 
     @typing.overload
     def handle(
         self,
-        *rules: ABCRule[Event],
-    ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
+        *rules: ABCRule,
+    ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandler]]: ...
 
     @typing.overload
     def handle(
         self,
-        *rules: ABCRule[Event],
+        *rules: ABCRule,
         is_blocking: bool = True,
-    ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
+    ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandler]]: ...
 
     @typing.overload
     def handle(
         self,
-        *rules: ABCRule[Event],
+        *rules: ABCRule,
         dataclass: type[T],
         is_blocking: bool = True,
-    ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
+    ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandler]]: ...
 
     @typing.overload
     def handle(  # type: ignore
         self,
-        *rules: ABCRule[Event],
+        *rules: ABCRule,
         error_handler: ErrorHandlerT,
         is_blocking: bool = True,
-    ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandlerT]]: ...
+    ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandlerT]]: ...
 
     @typing.overload
     def handle(
         self,
-        *rules: ABCRule[Event],
+        *rules: ABCRule,
         dataclass: type[T],
         error_handler: ErrorHandlerT,
         is_blocking: bool = True,
-    ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandlerT]]: ...
+    ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandlerT]]: ...
 
     @typing.overload
     def handle(
         self,
-        *rules: ABCRule[Event],
+        *rules: ABCRule,
         dataclass: type[T] = DEFAULT_DATACLASS,
         error_handler: typing.Literal[None] = None,
         is_blocking: bool = True,
-    ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
+    ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandler]]: ...
 
     def handle(  # type: ignore
         self,
@@ -135,20 +135,20 @@ def wrapper(func: typing.Callable):
 
     async def feed(self, event: Update, api: ABCAPI) -> bool:
         logger.debug("Processing update (update_id={})", event.update_id)
-        loop = asyncio.get_running_loop()
-        loop.create_task(self.raw_event.process(event, api))
-        
+        await self.raw_event.process(event, api)
+
         for view in self.get_views().values():
             if await view.check(event):
                 logger.debug(
-                    "Update (update_id={}) matched view {!r}",
+                    "Update (update_id={}) matched view {!r}.",
                     event.update_id,
                     view.__class__.__name__,
                 )
-                loop.create_task(view.process(event, api))
+                await view.process(event, api)
                 return True
 
         ctx = Context()
+        loop = asyncio.get_running_loop()
         found = False
         for handler in self.default_handlers:
             if await handler.check(api, event, ctx):
@@ -165,5 +165,7 @@ def load(self, external: typing.Self) -> None:
             view.load(view_external[name])
             setattr(external, name, view)
 
+    __call__ = handle
+
 
 __all__ = ("Dispatch",)
diff --git a/telegrinder/bot/dispatch/handler/func.py b/telegrinder/bot/dispatch/handler/func.py
index 490b952c..f57c3cec 100755
--- a/telegrinder/bot/dispatch/handler/func.py
+++ b/telegrinder/bot/dispatch/handler/func.py
@@ -3,11 +3,13 @@
 import typing_extensions as typing
 
 from telegrinder.api.abc import ABCAPI
-from telegrinder.bot.cute_types import BaseCute
+from telegrinder.bot.cute_types import BaseCute, UpdateCute
 from telegrinder.bot.dispatch.context import Context
 from telegrinder.bot.dispatch.process import check_rule
 from telegrinder.modules import logger
+from telegrinder.node import Node, compose_nodes, is_node
 from telegrinder.tools.error_handler import ABCErrorHandler, ErrorHandler
+from telegrinder.tools.magic import get_annotations
 from telegrinder.types import Update
 from telegrinder.types.enums import UpdateType
 
@@ -20,22 +22,22 @@
     "F",
     bound=typing.Callable[typing.Concatenate[typing.Any, ...], typing.Awaitable[typing.Any]],
 )
-EventT = typing.TypeVar("EventT", bound=BaseCute)
+Event = typing.TypeVar("Event", bound=BaseCute)
 ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ABCErrorHandler, default=ErrorHandler)
 
 
 @dataclasses.dataclass(repr=False)
-class FuncHandler(ABCHandler[EventT], typing.Generic[EventT, F, ErrorHandlerT]):
+class FuncHandler(ABCHandler[Event], typing.Generic[Event, F, ErrorHandlerT]):
     func: F
-    rules: list["ABCRule[EventT]"]
-    _: dataclasses.KW_ONLY
-    is_blocking: bool = dataclasses.field(default=True)
-    dataclass: type[typing.Any] | None = dataclasses.field(default=dict)
+    rules: list["ABCRule"]
+    is_blocking: bool = dataclasses.field(default=True, kw_only=True)
+    dataclass: type[typing.Any] | None = dataclasses.field(default=dict, kw_only=True)
     error_handler: ErrorHandlerT = dataclasses.field(
         default_factory=lambda: typing.cast(ErrorHandlerT, ErrorHandler()),
+        kw_only=True,
     )
-    preset_context: Context = dataclasses.field(default_factory=lambda: Context())
-    update_type: UpdateType | None = dataclasses.field(default=None)
+    preset_context: Context = dataclasses.field(default_factory=lambda: Context(), kw_only=True)
+    update_type: UpdateType | None = dataclasses.field(default=None, kw_only=True)
 
     def __repr__(self) -> str:
         return "<{}: {}={!r} with rules={!r}, dataclass={!r}, error_handler={!r}>".format(
@@ -46,24 +48,39 @@ def __repr__(self) -> str:
             self.dataclass,
             self.error_handler,
         )
-    
+
+    def get_required_nodes(self) -> dict[str, type[Node]]:
+        return {k: v for k, v in get_annotations(self.func).items() if is_node(v)}
+
     async def check(self, api: ABCAPI, event: Update, ctx: Context | None = None) -> bool:
-        if self.update_type is not None and self.update_type != event.update_type.unwrap_or_none():
+        if self.update_type is not None and self.update_type != event.update_type:
             return False
+
         ctx = ctx or Context()
         temp_ctx = ctx.copy()
         temp_ctx |= self.preset_context
 
+        nodes = self.get_required_nodes()
+        node_col = None
+
+        if nodes:
+            node_col = await compose_nodes(nodes, UpdateCute.from_update(event, api), ctx)
+            if node_col is None:
+                return False
+            temp_ctx |= node_col.values()
+
         for rule in self.rules:
             if not await check_rule(api, rule, event, temp_ctx):
                 logger.debug("Rule {!r} failed!", rule)
                 return False
 
+        temp_ctx["node_col"] = node_col
         ctx |= temp_ctx
         return True
 
-    async def run(self, event: EventT, ctx: Context) -> typing.Any:
+    async def run(self, event: Event, ctx: Context) -> typing.Any:
         api = event.api
+
         if self.dataclass is not None:
             if self.update_type is not None:
                 update = event.to_dict()[self.update_type.value].unwrap()
@@ -74,7 +91,11 @@ async def run(self, event: EventT, ctx: Context) -> typing.Any:
                 )
             else:
                 event = self.dataclass(**event.to_dict())
-        return (await self.error_handler.run(self.func, event, api, ctx)).unwrap()
+
+        result = (await self.error_handler.run(self.func, event, api, ctx)).unwrap()
+        if node_col := ctx.node_col:
+            await node_col.close_all()
+        return result
 
 
 __all__ = ("FuncHandler",)
diff --git a/telegrinder/bot/dispatch/handler/message_reply.py b/telegrinder/bot/dispatch/handler/message_reply.py
index 4a096309..881a1ce0 100755
--- a/telegrinder/bot/dispatch/handler/message_reply.py
+++ b/telegrinder/bot/dispatch/handler/message_reply.py
@@ -6,7 +6,6 @@
 from telegrinder.bot.dispatch.process import check_rule
 from telegrinder.bot.rules.abc import ABCRule
 from telegrinder.modules import logger
-from telegrinder.msgspec_utils import Nothing
 from telegrinder.types.objects import ReplyParameters, Update
 
 from .abc import ABCHandler
@@ -16,15 +15,18 @@ class MessageReplyHandler(ABCHandler[MessageCute]):
     def __init__(
         self,
         text: str,
-        *rules: ABCRule[MessageCute],
+        *rules: ABCRule,
         is_blocking: bool = True,
         as_reply: bool = False,
+        preset_context: Context | None = None,
+        **default_params: typing.Any,
     ) -> None:
         self.text = text
         self.rules = list(rules)
         self.as_reply = as_reply
         self.is_blocking = is_blocking
-        self.preset_context = Context()
+        self.default_params = default_params
+        self.preset_context = preset_context or Context()
 
     def __repr__(self) -> str:
         return "<{}: with rules={!r}, {}: {!r}>".format(
@@ -51,6 +53,7 @@ async def run(self, event: MessageCute, _: Context) -> typing.Any:
         await event.answer(
             text=self.text,
             reply_parameters=ReplyParameters(event.message_id) if self.as_reply else None,
+            **self.default_params,
         )
 
 
diff --git a/telegrinder/bot/dispatch/middleware/abc.py b/telegrinder/bot/dispatch/middleware/abc.py
index af8841be..b392fd8d 100755
--- a/telegrinder/bot/dispatch/middleware/abc.py
+++ b/telegrinder/bot/dispatch/middleware/abc.py
@@ -4,13 +4,13 @@
 from telegrinder.bot.cute_types.base import BaseCute
 from telegrinder.bot.dispatch.context import Context
 
-T = typing.TypeVar("T", bound=BaseCute)
+Event = typing.TypeVar("Event", bound=BaseCute)
 
 
-class ABCMiddleware(ABC, typing.Generic[T]):
-    async def pre(self, event: T, ctx: Context) -> bool: ...
+class ABCMiddleware(ABC, typing.Generic[Event]):
+    async def pre(self, event: Event, ctx: Context) -> bool: ...
 
-    async def post(self, event: T, responses: list[typing.Any], ctx: Context) -> None: ...
+    async def post(self, event: Event, responses: list[typing.Any], ctx: Context) -> None: ...
 
 
 __all__ = ("ABCMiddleware",)
diff --git a/telegrinder/bot/dispatch/process.py b/telegrinder/bot/dispatch/process.py
index 07f83302..2047fcf5 100755
--- a/telegrinder/bot/dispatch/process.py
+++ b/telegrinder/bot/dispatch/process.py
@@ -1,11 +1,13 @@
 import typing
 
-from fntypes.result import Error
+from fntypes.result import Error, Ok
 
 from telegrinder.api.abc import ABCAPI
 from telegrinder.bot.cute_types import BaseCute
+from telegrinder.bot.cute_types.update import UpdateCute
 from telegrinder.bot.dispatch.context import Context
 from telegrinder.modules import logger
+from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, NodeScope, compose_nodes
 from telegrinder.tools.i18n.base import I18nEnum
 from telegrinder.types import Update
 
@@ -16,19 +18,20 @@
     from telegrinder.bot.dispatch.handler.abc import ABCHandler
     from telegrinder.bot.rules.abc import ABCRule
 
-T = typing.TypeVar("T", bound=BaseCute)
+Event = typing.TypeVar("Event", bound=BaseCute)
 _: typing.TypeAlias = typing.Any
 
 
 async def process_inner(
-    event: T,
+    event: Event,
     raw_event: Update,
-    middlewares: list[ABCMiddleware[T]],
-    handlers: list["ABCHandler[T]"],
-    return_manager: ABCReturnManager[T] | None = None,
+    middlewares: list[ABCMiddleware[Event]],
+    handlers: list["ABCHandler[Event]"],
+    return_manager: ABCReturnManager[Event] | None = None,
 ) -> bool:
     logger.debug("Processing {!r}...", event.__class__.__name__)
     ctx = Context(raw_update=raw_event)
+    ctx[CONTEXT_STORE_NODES_KEY] = {}  # for per-event shared nodes
 
     for middleware in middlewares:
         if await middleware.pre(event, ctx) is False:
@@ -42,45 +45,69 @@ async def process_inner(
         if await handler.check(event.api, raw_event, ctx):
             found = True
             response = await handler.run(event, ctx)
+            logger.debug("Handler {!r} returned: {!r}", handler, response)
             responses.append(response)
             if return_manager is not None:
                 await return_manager.run(response, event, ctx)
             if handler.is_blocking:
                 break
-            ctx = ctx_copy
+
+        ctx = ctx_copy
 
     for middleware in middlewares:
         await middleware.post(event, responses, ctx)
 
+    for session in ctx.get(CONTEXT_STORE_NODES_KEY, {}).values():
+        await session.close(scopes=(NodeScope.PER_EVENT,))
+
     return found
 
 
 async def check_rule(
     api: ABCAPI,
-    rule: "ABCRule[T]",
+    rule: "ABCRule",
     update: Update,
     ctx: Context,
 ) -> bool:
     """Checks requirements, adapts update.
     Returns check result."""
 
-    cute_model = await rule.adapter.adapt(api, update)
-    match cute_model:
+    # Running adapter
+    match await rule.adapter.adapt(api, update):
+        case Ok(value):
+            adapted_value = value
         case Error(err):
             logger.debug("Adapter failed with error message: {!r}", str(err))
             return False
 
+    # Running subrules to fetch requirements
     ctx_copy = ctx.copy()
     for requirement in rule.requires:
         if not await check_rule(api, requirement, update, ctx_copy):
             return False
 
-    ctx |= ctx_copy
-
+    # Translating translatable rules
     if I18nEnum.I18N in ctx:
         rule = await rule.translate(ctx.get(I18nEnum.I18N))
 
-    return await rule.check(cute_model.unwrap(), ctx)
+    ctx |= ctx_copy
+
+    # Composing required nodes
+    nodes = rule.get_required_nodes()
+    node_col = None
+    if nodes:
+        node_col = await compose_nodes(nodes, UpdateCute.from_update(update, api), ctx)
+        if node_col is None:
+            return False
+
+    # Running check
+    result = await rule.bounding_check(adapted_value, ctx, node_col)
+
+    # Closing node sessions if there are any
+    if node_col is not None:
+        await node_col.close_all()
+
+    return result
 
 
 __all__ = ("check_rule", "process_inner")
diff --git a/telegrinder/bot/dispatch/return_manager/abc.py b/telegrinder/bot/dispatch/return_manager/abc.py
index 92585a10..3ad7030b 100755
--- a/telegrinder/bot/dispatch/return_manager/abc.py
+++ b/telegrinder/bot/dispatch/return_manager/abc.py
@@ -8,7 +8,7 @@
 from telegrinder.modules import logger
 
 T = typing.TypeVar("T")
-EventT = typing.TypeVar("EventT", bound=BaseCute, contravariant=True)
+Event = typing.TypeVar("Event", bound=BaseCute, contravariant=True)
 
 
 def get_union_types(t: types.UnionType) -> tuple[type, ...] | None:
@@ -36,13 +36,13 @@ async def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
             logger.exception(ex)
 
 
-class ABCReturnManager(ABC, typing.Generic[EventT]):
+class ABCReturnManager(ABC, typing.Generic[Event]):
     @abstractmethod
-    async def run(self, response: typing.Any, event: EventT, ctx: Context) -> None:
+    async def run(self, response: typing.Any, event: Event, ctx: Context) -> None:
         pass
 
 
-class BaseReturnManager(ABCReturnManager[EventT]):
+class BaseReturnManager(ABCReturnManager[Event]):
     def __repr__(self) -> str:
         return "<{}: {}>".format(
             self.__class__.__name__,
@@ -59,29 +59,29 @@ def managers(self) -> list[Manager]:
 
     @register_manager(Context)
     @staticmethod
-    async def ctx_manager(value: Context, event: EventT, ctx: Context) -> None:
+    async def ctx_manager(value: Context, event: Event, ctx: Context) -> None:
         """Basic manager for returning context from handler."""
 
         ctx.update(value)
 
-    async def run(self, response: typing.Any, event: EventT, ctx: Context) -> None:
+    async def run(self, response: typing.Any, event: Event, ctx: Context) -> None:
+        logger.debug("Run return manager for response: {!r}", response)
         for manager in self.managers:
             if typing.Any in manager.types or any(type(response) is x for x in manager.types):
+                logger.debug("Run manager {!r}...", manager.callback.__name__)
                 await manager(response, event, ctx)
 
     @typing.overload
     def register_manager(
         self, return_type: type[T]
-    ) -> typing.Callable[
-        [typing.Callable[[T, EventT, Context], typing.Awaitable[typing.Any]]], Manager
-    ]: ...
+    ) -> typing.Callable[[typing.Callable[[T, Event, Context], typing.Awaitable[typing.Any]]], Manager]: ...
 
     @typing.overload
     def register_manager(
         self,
         return_type: tuple[type[T], ...],
     ) -> typing.Callable[
-        [typing.Callable[[tuple[T, ...], EventT, Context], typing.Awaitable[typing.Any]]],
+        [typing.Callable[[tuple[T, ...], Event, Context], typing.Awaitable[typing.Any]]],
         Manager,
     ]: ...
 
@@ -89,10 +89,10 @@ def register_manager(
         self,
         return_type: type[T] | tuple[type[T], ...],
     ) -> typing.Callable[
-        [typing.Callable[[T | tuple[T, ...], EventT, Context], typing.Awaitable[typing.Any]]],
+        [typing.Callable[[T | tuple[T, ...], Event, Context], typing.Awaitable[typing.Any]]],
         Manager,
     ]:
-        def wrapper(func: typing.Callable[[T, EventT, Context], typing.Awaitable]) -> Manager:
+        def wrapper(func: typing.Callable[[T, Event, Context], typing.Awaitable]) -> Manager:
             manager = Manager(get_union_types(return_type) or (return_type,), func)  # type: ignore
             setattr(self.__class__, func.__name__, manager)
             return manager
diff --git a/telegrinder/bot/dispatch/return_manager/callback_query.py b/telegrinder/bot/dispatch/return_manager/callback_query.py
index 757ebb96..06d3a06f 100755
--- a/telegrinder/bot/dispatch/return_manager/callback_query.py
+++ b/telegrinder/bot/dispatch/return_manager/callback_query.py
@@ -14,9 +14,7 @@ async def str_manager(value: str, event: CallbackQueryCute, ctx: Context) -> Non
 
     @register_manager(dict)
     @staticmethod
-    async def dict_manager(
-        value: dict[str, typing.Any], event: CallbackQueryCute, ctx: Context
-    ) -> None:
+    async def dict_manager(value: dict[str, typing.Any], event: CallbackQueryCute, ctx: Context) -> None:
         await event.answer(**value)
 
 
diff --git a/telegrinder/bot/dispatch/return_manager/inline_query.py b/telegrinder/bot/dispatch/return_manager/inline_query.py
index 4c1df1e6..44f2609d 100755
--- a/telegrinder/bot/dispatch/return_manager/inline_query.py
+++ b/telegrinder/bot/dispatch/return_manager/inline_query.py
@@ -9,9 +9,7 @@
 class InlineQueryReturnManager(BaseReturnManager[InlineQueryCute]):
     @register_manager(dict)
     @staticmethod
-    async def dict_manager(
-        value: dict[str, typing.Any], event: InlineQueryCute, ctx: Context
-    ) -> None:
+    async def dict_manager(value: dict[str, typing.Any], event: InlineQueryCute, ctx: Context) -> None:
         await event.answer(**value)
 
 
diff --git a/telegrinder/bot/dispatch/view/abc.py b/telegrinder/bot/dispatch/view/abc.py
index 6d8bf8e2..2876a426 100755
--- a/telegrinder/bot/dispatch/view/abc.py
+++ b/telegrinder/bot/dispatch/view/abc.py
@@ -16,12 +16,12 @@
 from telegrinder.tools.error_handler.error_handler import ABCErrorHandler, ErrorHandler
 from telegrinder.types.objects import Update
 
-EventType = typing.TypeVar("EventType", bound=BaseCute)
+Event = typing.TypeVar("Event", bound=BaseCute)
 ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ABCErrorHandler)
 MiddlewareT = typing.TypeVar("MiddlewareT", bound=ABCMiddleware)
 
 FuncType: typing.TypeAlias = typing.Callable[
-    typing.Concatenate[EventType, ...],
+    typing.Concatenate[Event, ...],
     typing.Coroutine[typing.Any, typing.Any, typing.Any],
 ]
 
@@ -46,20 +46,24 @@ def load(self, external: typing.Self) -> None:
         pass
 
 
-class ABCStateView(ABCView, typing.Generic[EventType]):
+class ABCStateView(ABCView, typing.Generic[Event]):
     @abstractmethod
-    def get_state_key(self, event: EventType) -> int | None:
+    def get_state_key(self, event: Event) -> int | None:
         pass
 
 
-class BaseView(ABCView, typing.Generic[EventType]):
-    auto_rules: list[ABCRule[EventType]]
-    handlers: list[ABCHandler[EventType]]
-    middlewares: list[ABCMiddleware[EventType]]
-    return_manager: ABCReturnManager[EventType] | None
+class BaseView(ABCView, typing.Generic[Event]):
+    auto_rules: list[ABCRule]
+    handlers: list[ABCHandler[Event]]
+    middlewares: list[ABCMiddleware[Event]]
+    return_manager: ABCReturnManager[Event] | None = None
+
+    @staticmethod
+    def get_raw_event(update: Update) -> Option[Model]:
+        return getattr(update, update.update_type.value)
 
     @classmethod
-    def get_event_type(cls) -> Option[type[EventType]]:
+    def get_event_type(cls) -> Option[type[Event]]:
         for base in cls.__dict__.get("__orig_bases__", ()):
             if issubclass(typing.get_origin(base) or base, ABCView):
                 for generic_type in typing.get_args(base):
@@ -67,55 +71,45 @@ def get_event_type(cls) -> Option[type[EventType]]:
                         return Some(generic_type)
         return Nothing()
 
-    @staticmethod
-    def get_raw_event(update: Update) -> Option[Model]:
-        match update.update_type:
-            case Some(update_type):
-                return getattr(update, update_type.value)
-            case _:
-                return Nothing()
-    
     @typing.overload
     @classmethod
     def to_handler(
         cls,
-        *rules: ABCRule[EventType],
+        *rules: ABCRule,
     ) -> typing.Callable[
-        [FuncType[EventType]],
-        FuncHandler[EventType, FuncType[EventType], ErrorHandler[EventType]],
+        [FuncType[Event]],
+        FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
     ]: ...
 
     @typing.overload
     @classmethod
     def to_handler(
         cls,
-        *rules: ABCRule[EventType],
+        *rules: ABCRule,
         error_handler: ErrorHandlerT,
         is_blocking: bool = True,
-    ) -> typing.Callable[
-        [FuncType[EventType]], FuncHandler[EventType, FuncType[EventType], ErrorHandlerT]
-    ]: ...
+    ) -> typing.Callable[[FuncType[Event]], FuncHandler[Event, FuncType[Event], ErrorHandlerT]]: ...
 
     @typing.overload
     @classmethod
     def to_handler(
         cls,
-        *rules: ABCRule[EventType],
+        *rules: ABCRule,
         error_handler: typing.Literal[None] = None,
         is_blocking: bool = True,
     ) -> typing.Callable[
-        [FuncType[EventType]],
-        FuncHandler[EventType, FuncType[EventType], ErrorHandler[EventType]],
+        [FuncType[Event]],
+        FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
     ]: ...
 
     @classmethod
     def to_handler(  # type: ignore
         cls,
-        *rules: ABCRule[EventType],
+        *rules: ABCRule,
         error_handler: ABCErrorHandler | None = None,
         is_blocking: bool = True,
     ):
-        def wrapper(func: FuncType[EventType]):
+        def wrapper(func: FuncType[Event]):
             return FuncHandler(
                 func,
                 list(rules),
@@ -129,40 +123,38 @@ def wrapper(func: FuncType[EventType]):
     @typing.overload
     def __call__(
         self,
-        *rules: ABCRule[EventType],
+        *rules: ABCRule,
     ) -> typing.Callable[
-        [FuncType[EventType]],
-        FuncHandler[EventType, FuncType[EventType], ErrorHandler[EventType]],
+        [FuncType[Event]],
+        FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
     ]: ...
 
     @typing.overload
-    def __call__(
+    def __call__(  # type: ignore
         self,
-        *rules: ABCRule[EventType],
+        *rules: ABCRule,
         error_handler: ErrorHandlerT,
         is_blocking: bool = True,
-    ) -> typing.Callable[
-        [FuncType[EventType]], FuncHandler[EventType, FuncType[EventType], ErrorHandlerT]
-    ]: ...
+    ) -> typing.Callable[[FuncType[Event]], FuncHandler[Event, FuncType[Event], ErrorHandlerT]]: ...
 
     @typing.overload
     def __call__(
         self,
-        *rules: ABCRule[EventType],
+        *rules: ABCRule,
         error_handler: typing.Literal[None] = None,
         is_blocking: bool = True,
     ) -> typing.Callable[
-        [FuncType[EventType]],
-        FuncHandler[EventType, FuncType[EventType], ErrorHandler[EventType]],
+        [FuncType[Event]],
+        FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
     ]: ...
 
     def __call__(  # type: ignore
         self,
-        *rules: ABCRule[EventType],
+        *rules: ABCRule,
         error_handler: ABCErrorHandler | None = None,
         is_blocking: bool = True,
     ):
-        def wrapper(func: FuncType[EventType]):
+        def wrapper(func: FuncType[Event]):
             func_handler = FuncHandler(
                 func,
                 [*self.auto_rules, *rules],
@@ -214,9 +206,9 @@ def load(self, external: typing.Self) -> None:
         self.middlewares.extend(external.middlewares)
 
 
-class BaseStateView(ABCStateView[EventType], BaseView[EventType], ABC, typing.Generic[EventType]):
+class BaseStateView(ABCStateView[Event], BaseView[Event], ABC, typing.Generic[Event]):
     @abstractmethod
-    def get_state_key(self, event: EventType) -> int | None:
+    def get_state_key(self, event: Event) -> int | None:
         pass
 
 
diff --git a/telegrinder/bot/dispatch/view/box.py b/telegrinder/bot/dispatch/view/box.py
index 4b9c6720..8f9ead21 100755
--- a/telegrinder/bot/dispatch/view/box.py
+++ b/telegrinder/bot/dispatch/view/box.py
@@ -2,95 +2,111 @@
 
 import typing_extensions as typing
 
+from telegrinder.bot.dispatch.view import (
+    callback_query,
+    chat_join_request,
+    chat_member,
+    inline_query,
+    message,
+    raw,
+)
+from telegrinder.bot.dispatch.view.abc import ABCView
 from telegrinder.types.enums import UpdateType
 
-from .abc import ABCView
-from .callback_query import CallbackQueryView
-from .chat_join_request import ChatJoinRequestView
-from .chat_member import ChatMemberView
-from .inline_query import InlineQueryView
-from .message import MessageView
-from .raw import RawEventView
-
-CallbackQueryViewT = typing.TypeVar("CallbackQueryViewT", bound=ABCView, default=CallbackQueryView)
-ChatJoinRequestViewT = typing.TypeVar(
-    "ChatJoinRequestViewT", bound=ABCView, default=ChatJoinRequestView
+CallbackQueryView = typing.TypeVar(
+    "CallbackQueryView", bound=ABCView, default=callback_query.CallbackQueryView
 )
-ChatMemberViewT = typing.TypeVar("ChatMemberViewT", bound=ABCView, default=ChatMemberView)
-InlineQueryViewT = typing.TypeVar("InlineQueryViewT", bound=ABCView, default=InlineQueryView)
-MessageViewT = typing.TypeVar("MessageViewT", bound=ABCView, default=MessageView)
-RawEventViewT = typing.TypeVar("RawEventViewT", bound=ABCView, default=RawEventView)
+ChatJoinRequestView = typing.TypeVar(
+    "ChatJoinRequestView", bound=ABCView, default=chat_join_request.ChatJoinRequestView
+)
+ChatMemberView = typing.TypeVar("ChatMemberView", bound=ABCView, default=chat_member.ChatMemberView)
+InlineQueryView = typing.TypeVar("InlineQueryView", bound=ABCView, default=inline_query.InlineQueryView)
+MessageView = typing.TypeVar("MessageView", bound=ABCView, default=message.MessageView)
+RawEventView = typing.TypeVar("RawEventView", bound=ABCView, default=raw.RawEventView)
 
 
 @dataclasses.dataclass(kw_only=True)
 class ViewBox(
     typing.Generic[
-        CallbackQueryViewT,
-        ChatJoinRequestViewT,
-        ChatMemberViewT,
-        InlineQueryViewT,
-        MessageViewT,
-        RawEventViewT,
+        CallbackQueryView,
+        ChatJoinRequestView,
+        ChatMemberView,
+        InlineQueryView,
+        MessageView,
+        RawEventView,
     ],
 ):
-    callback_query: CallbackQueryViewT = dataclasses.field(
-        default_factory=lambda: typing.cast(CallbackQueryViewT, CallbackQueryView()),
+    callback_query: CallbackQueryView = dataclasses.field(
+        default_factory=lambda: typing.cast(
+            CallbackQueryView,
+            callback_query.CallbackQueryView(),
+        ),
     )
-    chat_join_request: ChatJoinRequestViewT = dataclasses.field(
-        default_factory=lambda: typing.cast(ChatJoinRequestViewT, ChatJoinRequestView()),
+    chat_join_request: ChatJoinRequestView = dataclasses.field(
+        default_factory=lambda: typing.cast(
+            ChatJoinRequestView,
+            chat_join_request.ChatJoinRequestView(),
+        ),
     )
-    chat_member: ChatMemberViewT = dataclasses.field(
+    chat_member: ChatMemberView = dataclasses.field(
         default_factory=lambda: typing.cast(
-            ChatMemberViewT, ChatMemberView(update_type=UpdateType.CHAT_MEMBER)
+            ChatMemberView,
+            chat_member.ChatMemberView(update_type=UpdateType.CHAT_MEMBER),
         ),
     )
-    my_chat_member: ChatMemberViewT = dataclasses.field(
+    my_chat_member: ChatMemberView = dataclasses.field(
         default_factory=lambda: typing.cast(
-            ChatMemberViewT, ChatMemberView(update_type=UpdateType.MY_CHAT_MEMBER)
+            ChatMemberView,
+            chat_member.ChatMemberView(update_type=UpdateType.MY_CHAT_MEMBER),
         ),
     )
-    inline_query: InlineQueryViewT = dataclasses.field(
-        default_factory=lambda: typing.cast(InlineQueryViewT, InlineQueryView()),
+    inline_query: InlineQueryView = dataclasses.field(
+        default_factory=lambda: typing.cast(InlineQueryView, inline_query.InlineQueryView()),
     )
-    message: MessageViewT = dataclasses.field(
+    message: MessageView = dataclasses.field(
         default_factory=lambda: typing.cast(
-            MessageViewT, MessageView(update_type=UpdateType.MESSAGE)
+            MessageView,
+            message.MessageView(update_type=UpdateType.MESSAGE),
         ),
     )
-    business_message: MessageViewT = dataclasses.field(
+    business_message: MessageView = dataclasses.field(
         default_factory=lambda: typing.cast(
-            MessageViewT, MessageView(update_type=UpdateType.BUSINESS_MESSAGE)
+            MessageView,
+            message.MessageView(update_type=UpdateType.BUSINESS_MESSAGE),
         ),
     )
-    channel_post: MessageViewT = dataclasses.field(
+    channel_post: MessageView = dataclasses.field(
         default_factory=lambda: typing.cast(
-            MessageViewT, MessageView(update_type=UpdateType.CHANNEL_POST)
+            MessageView,
+            message.MessageView(update_type=UpdateType.CHANNEL_POST),
         ),
     )
-    edited_message: MessageViewT = dataclasses.field(
+    edited_message: MessageView = dataclasses.field(
         default_factory=lambda: typing.cast(
-            MessageViewT, MessageView(update_type=UpdateType.EDITED_MESSAGE)
+            MessageView,
+            message.MessageView(update_type=UpdateType.EDITED_MESSAGE),
         ),
     )
-    edited_business_message: MessageViewT = dataclasses.field(
+    edited_business_message: MessageView = dataclasses.field(
         default_factory=lambda: typing.cast(
-            MessageViewT,
-            MessageView(update_type=UpdateType.EDITED_BUSINESS_MESSAGE),
+            MessageView,
+            message.MessageView(update_type=UpdateType.EDITED_BUSINESS_MESSAGE),
         ),
     )
-    edited_channel_post: MessageViewT = dataclasses.field(
+    edited_channel_post: MessageView = dataclasses.field(
         default_factory=lambda: typing.cast(
-            MessageViewT, MessageView(update_type=UpdateType.EDITED_CHANNEL_POST)
+            MessageView,
+            message.MessageView(update_type=UpdateType.EDITED_CHANNEL_POST),
         ),
     )
-    any_message: MessageViewT = dataclasses.field(
-        default_factory=lambda: typing.cast(MessageViewT, MessageView()),
+    any_message: MessageView = dataclasses.field(
+        default_factory=lambda: typing.cast(MessageView, message.MessageView()),
     )
-    chat_member_updated: ChatMemberViewT = dataclasses.field(
-        default_factory=lambda: typing.cast(ChatMemberViewT, ChatMemberView()),
+    chat_member_updated: ChatMemberView = dataclasses.field(
+        default_factory=lambda: typing.cast(ChatMemberView, chat_member.ChatMemberView()),
     )
-    raw_event: RawEventViewT = dataclasses.field(
-        default_factory=lambda: typing.cast(RawEventViewT, RawEventView()),
+    raw_event: RawEventView = dataclasses.field(
+        default_factory=lambda: typing.cast(RawEventView, raw.RawEventView()),
     )
 
     def get_views(self) -> dict[str, ABCView]:
diff --git a/telegrinder/bot/dispatch/view/message.py b/telegrinder/bot/dispatch/view/message.py
index f056f830..f961f31e 100755
--- a/telegrinder/bot/dispatch/view/message.py
+++ b/telegrinder/bot/dispatch/view/message.py
@@ -30,11 +30,7 @@ def get_state_key(self, event: MessageCute) -> int | None:
     async def check(self, event: Update) -> bool:
         if not await super().check(event):
             return False
-        return (
-            True
-            if self.update_type is None
-            else self.update_type == event.update_type.unwrap_or_none()
-        )
+        return True if self.update_type is None else self.update_type == event.update_type
 
 
 __all__ = ("MessageView",)
diff --git a/telegrinder/bot/dispatch/view/raw.py b/telegrinder/bot/dispatch/view/raw.py
index 0c0e7f83..b2b5d3b3 100644
--- a/telegrinder/bot/dispatch/view/raw.py
+++ b/telegrinder/bot/dispatch/view/raw.py
@@ -29,7 +29,7 @@ def __init__(self) -> None:
     def __call__(
         self,
         update_type: UpdateType,
-        *rules: ABCRule[UpdateCute],
+        *rules: ABCRule,
     ) -> typing.Callable[
         [FuncType[UpdateCute]],
         FuncHandler[UpdateCute, FuncType[UpdateCute], ErrorHandler[UpdateCute]],
@@ -39,7 +39,7 @@ def __call__(
     def __call__(
         self,
         update_type: UpdateType,
-        *rules: ABCRule[UpdateCute],
+        *rules: ABCRule,
         dataclass: type[T],
     ) -> typing.Callable[[FuncType[T]], FuncHandler[UpdateCute, FuncType[T], ErrorHandler[T]]]: ...
 
@@ -47,7 +47,7 @@ def __call__(
     def __call__(
         self,
         update_type: UpdateType,
-        *rules: ABCRule[UpdateCute],
+        *rules: ABCRule,
         error_handler: ErrorHandlerT,
     ) -> typing.Callable[
         [FuncType[UpdateCute]],
@@ -58,7 +58,7 @@ def __call__(
     def __call__(
         self,
         update_type: UpdateType,
-        *rules: ABCRule[UpdateCute],
+        *rules: ABCRule,
         dataclass: type[T],
         error_handler: ErrorHandlerT,
         is_blocking: bool = True,
@@ -68,7 +68,7 @@ def __call__(
     def __call__(
         self,
         update_type: UpdateType,
-        *rules: ABCRule[UpdateCute],
+        *rules: ABCRule,
         dataclass: typing.Literal[None] = None,
         error_handler: typing.Literal[None] = None,
         is_blocking: bool = True,
@@ -80,7 +80,7 @@ def __call__(
     def __call__(  # type: ignore
         self,
         update_type: UpdateType,
-        *rules: ABCRule[typing.Any],
+        *rules: ABCRule,
         dataclass: type[typing.Any] | None = None,
         error_handler: ABCErrorHandler | None = None,
         is_blocking: bool = True,
diff --git a/telegrinder/bot/dispatch/waiter_machine/__init__.py b/telegrinder/bot/dispatch/waiter_machine/__init__.py
index 32b12bfa..753c5672 100755
--- a/telegrinder/bot/dispatch/waiter_machine/__init__.py
+++ b/telegrinder/bot/dispatch/waiter_machine/__init__.py
@@ -1,4 +1,4 @@
-from .machine import WaiterMachine
+from .machine import WaiterMachine, clear_wm_storage_worker
 from .middleware import WaiterMiddleware
 from .short_state import ShortState
 
@@ -6,4 +6,5 @@
     "ShortState",
     "WaiterMachine",
     "WaiterMiddleware",
+    "clear_wm_storage_worker",
 )
diff --git a/telegrinder/bot/dispatch/waiter_machine/machine.py b/telegrinder/bot/dispatch/waiter_machine/machine.py
index b8971843..188a7447 100755
--- a/telegrinder/bot/dispatch/waiter_machine/machine.py
+++ b/telegrinder/bot/dispatch/waiter_machine/machine.py
@@ -4,36 +4,46 @@
 
 from telegrinder.api.abc import ABCAPI
 from telegrinder.bot.dispatch.context import Context
+from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
 from telegrinder.bot.rules.abc import ABCRule
 from telegrinder.tools.limited_dict import LimitedDict
 from telegrinder.types import Update
 
 from .middleware import WaiterMiddleware
-from .short_state import Behaviour, EventModel, ShortState
+from .short_state import Behaviour, EventModel, ShortState, ShortStateContext
 
 if typing.TYPE_CHECKING:
-    from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
+    from telegrinder.bot.dispatch import Dispatch
 
 T = typing.TypeVar("T")
 
 Identificator: typing.TypeAlias = str | int
 Storage: typing.TypeAlias = dict[str, LimitedDict[Identificator, ShortState[EventModel]]]
 
+WEEK: typing.Final[datetime.timedelta] = datetime.timedelta(days=7)
+
 
 class WaiterMachine:
-    def __init__(self) -> None:
+    def __init__(self, *, max_storage_size: int = 1000) -> None:
+        self.max_storage_size = max_storage_size
         self.storage: Storage = {}
 
     def __repr__(self) -> str:
-        return "<{}: storage={!r}>".format(
+        return "<{}: max_storage_size={}, {}>".format(
             self.__class__.__name__,
-            self.storage,
+            self.max_storage_size,
+            ", ".join(
+                f"{view_name}: {len(self.storage[view_name].values())} shortstates"
+                for view_name in self.storage
+            )
+            or "empty",
         )
 
     async def drop(
         self,
         state_view: "ABCStateView[EventModel]",
         id: Identificator,
+        event: EventModel,
         update: Update,
         **context: typing.Any,
     ) -> None:
@@ -43,16 +53,12 @@ async def drop(
 
         short_state = self.storage[view_name].pop(id, None)
         if short_state is None:
-            raise LookupError(
-                "Waiter with identificator {} is not found for view {!r}".format(
-                    id, view_name
-                )
-            )
+            raise LookupError("Waiter with identificator {} is not found for view {!r}".format(id, view_name))
 
         short_state.cancel()
         await self.call_behaviour(
             state_view,
-            short_state.event,
+            event,
             update,
             behaviour=short_state.on_drop_behaviour,
             **context,
@@ -62,27 +68,24 @@ async def wait(
         self,
         state_view: "BaseStateView[EventModel]",
         linked: EventModel | tuple[ABCAPI, Identificator],
-        *rules: ABCRule[EventModel],
+        *rules: ABCRule,
         default: Behaviour[EventModel] | None = None,
         on_drop: Behaviour[EventModel] | None = None,
+        exit: Behaviour[EventModel] | None = None,
         expiration: datetime.timedelta | float | None = None,
-    ) -> tuple[EventModel, Context]:
+    ) -> ShortStateContext[EventModel]:
         if isinstance(expiration, int | float):
             expiration = datetime.timedelta(seconds=expiration)
 
         api: ABCAPI
         key: Identificator
-        api, key = (
-            linked
-            if isinstance(linked, tuple)
-            else (linked.ctx_api, state_view.get_state_key(linked))
-        )  # type: ignore
+        api, key = linked if isinstance(linked, tuple) else (linked.ctx_api, state_view.get_state_key(linked))  # type: ignore
         if not key:
             raise RuntimeError("Unable to get state key.")
 
         view_name = state_view.__class__.__name__
         event = asyncio.Event()
-        short_state = ShortState(
+        short_state = ShortState[EventModel](
             key,
             api,
             event,
@@ -90,42 +93,81 @@ async def wait(
             expiration=expiration,
             default_behaviour=default,
             on_drop_behaviour=on_drop,
+            exit_behaviour=exit,
         )
-        
+
         if view_name not in self.storage:
             state_view.middlewares.insert(0, WaiterMiddleware(self, state_view))
-            self.storage[view_name] = LimitedDict()
+            self.storage[view_name] = LimitedDict(maxlimit=self.max_storage_size)
 
         if (deleted_short_state := self.storage[view_name].set(key, short_state)) is not None:
             deleted_short_state.cancel()
-        
+
         await event.wait()
         self.storage[view_name].pop(key, None)
-        return getattr(event, "context")
+        assert short_state.context is not None
+        return short_state.context
 
     async def call_behaviour(
         self,
         view: "ABCStateView[EventModel]",
-        event: asyncio.Event | EventModel,
+        event: EventModel,
         update: Update,
         behaviour: Behaviour[EventModel] | None = None,
         **context: typing.Any,
-    ) -> None:
-        # TODO: support param view as a behaviour
+    ) -> bool:
+        # TODO: support view as a behaviour
 
         if behaviour is None:
-            return
+            return False
 
         ctx = Context(**context)
-        if isinstance(event, asyncio.Event):
-            event, preset_ctx = typing.cast(
-                tuple[EventModel, Context],
-                getattr(event, "context"),
-            )
-            ctx.update(preset_ctx)
-
         if await behaviour.check(event.api, update, ctx):
             await behaviour.run(event, ctx)
-        
+            return True
+
+        return False
+
+    async def clear_storage(
+        self,
+        views: typing.Iterable[ABCStateView[EventModel]],
+        absolutely_dead_time: datetime.timedelta = WEEK,
+    ):
+        """Clears storage.
+
+        :param absolutely_dead_time: timedelta when state can be forgotten.
+        """
+
+        for view in views:
+            view_name = view.__class__.__name__
+            now = datetime.datetime.now()
+            for ident, short_state in self.storage.get(view_name, {}).copy().items():
+                if short_state.expiration_date is not None and now > short_state.expiration_date:
+                    assert short_state.context
+                    await self.drop(
+                        view,
+                        ident,
+                        event=short_state.context.event,
+                        update=short_state.context.context.raw_update,
+                        force=True,
+                    )
+                elif short_state.creation_date + absolutely_dead_time < now:
+                    short_state.cancel()
+                    del self.storage[view_name][short_state.key]
+
+
+async def clear_wm_storage_worker(
+    wm: WaiterMachine,
+    dp: "Dispatch",
+    interval_seconds: int = 60,
+    absolutely_dead_time: datetime.timedelta = WEEK,
+) -> typing.NoReturn:
+    while True:
+        await wm.clear_storage(
+            views=[view for view in dp.get_views().values() if isinstance(view, ABCStateView)],
+            absolutely_dead_time=absolutely_dead_time,
+        )
+        await asyncio.sleep(interval_seconds)
+
 
 __all__ = ("WaiterMachine",)
diff --git a/telegrinder/bot/dispatch/waiter_machine/middleware.py b/telegrinder/bot/dispatch/waiter_machine/middleware.py
index 84e9a151..13f8241d 100755
--- a/telegrinder/bot/dispatch/waiter_machine/middleware.py
+++ b/telegrinder/bot/dispatch/waiter_machine/middleware.py
@@ -6,6 +6,7 @@
 from telegrinder.bot.dispatch.handler.func import FuncHandler
 from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
 from telegrinder.bot.dispatch.view.abc import ABCStateView
+from telegrinder.bot.dispatch.waiter_machine.short_state import ShortStateContext
 
 if typing.TYPE_CHECKING:
     from .machine import WaiterMachine
@@ -27,7 +28,7 @@ async def pre(self, event: EventType, ctx: Context) -> bool:
         if not self.view or not hasattr(self.view, "get_state_key"):
             raise RuntimeError(
                 "WaiterMiddleware cannot be used inside a view which doesn't "
-                "provide get_state_key (ABCStateView Protocol)."
+                "provide get_state_key (ABCStateView interface)."
             )
 
         view_name = self.view.__class__.__name__
@@ -41,13 +42,36 @@ async def pre(self, event: EventType, ctx: Context) -> bool:
         short_state: "ShortState[EventType] | None" = self.machine.storage[view_name].get(key)
         if not short_state:
             return True
-        
+
         preset_context = Context(short_state=short_state)
-        if (
-            short_state.expiration_date is not None
-            and datetime.datetime.now() >= short_state.expiration_date
+        if short_state.context is not None:
+            preset_context.update(short_state.context.context)
+
+        if short_state.expiration_date is not None and datetime.datetime.now() >= short_state.expiration_date:
+            await self.machine.drop(
+                self.view,
+                short_state.key,
+                event,
+                ctx.raw_update,
+                **preset_context.copy(),
+            )
+            return True
+
+        # before running the handler we check if the user wants to exit waiting
+        if short_state.exit_behaviour is not None and await self.machine.call_behaviour(
+            self.view,
+            event,
+            ctx.raw_update,
+            behaviour=short_state.exit_behaviour,
+            **preset_context,
         ):
-            await self.machine.drop(self.view, short_state.key, ctx.raw_update, **preset_context.copy())
+            await self.machine.drop(
+                self.view,
+                short_state.key,
+                event,
+                ctx.raw_update,
+                **preset_context.copy(),
+            )
             return True
 
         handler = FuncHandler(
@@ -78,7 +102,7 @@ async def pass_runtime(
         short_state: "ShortState[EventType]",
         ctx: Context,
     ) -> None:
-        setattr(short_state.event, "context", (event, ctx))
+        short_state.context = ShortStateContext(event, ctx)
         short_state.event.set()
 
 
diff --git a/telegrinder/bot/dispatch/waiter_machine/short_state.py b/telegrinder/bot/dispatch/waiter_machine/short_state.py
index bebffef1..03756b86 100755
--- a/telegrinder/bot/dispatch/waiter_machine/short_state.py
+++ b/telegrinder/bot/dispatch/waiter_machine/short_state.py
@@ -5,6 +5,7 @@
 
 from telegrinder.api import ABCAPI
 from telegrinder.bot.cute_types import BaseCute
+from telegrinder.bot.dispatch.context import Context
 from telegrinder.bot.dispatch.handler.abc import ABCHandler
 from telegrinder.bot.rules.abc import ABCRule
 from telegrinder.model import Model
@@ -18,23 +19,31 @@
 Behaviour: typing.TypeAlias = ABCHandler[T] | None
 
 
+class ShortStateContext(typing.Generic[EventModel], typing.NamedTuple):
+    event: EventModel
+    context: Context
+
+
 @dataclasses.dataclass
 class ShortState(typing.Generic[EventModel]):
     key: "Identificator"
     ctx_api: ABCAPI
     event: asyncio.Event
-    rules: tuple[ABCRule[EventModel], ...]
-    _: dataclasses.KW_ONLY
+    rules: tuple[ABCRule, ...]
     expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
         default=None,
+        kw_only=True,
     )
-    default_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
-    on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
-    expiration_date: datetime.datetime | None = dataclasses.field(init=False)
+    default_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
+    on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
+    exit_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
+    expiration_date: datetime.datetime | None = dataclasses.field(init=False, kw_only=True)
+    context: ShortStateContext[EventModel] | None = dataclasses.field(default=None, init=False, kw_only=True)
 
     def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
-        self.expiration_date = (datetime.datetime.now() - expiration) if expiration is not None else None
-    
+        self.creation_date = datetime.datetime.now()
+        self.expiration_date = (self.creation_date + expiration) if expiration is not None else None
+
     def cancel(self) -> None:
         """Cancel schedule waiters."""
 
@@ -46,4 +55,4 @@ def cancel(self) -> None:
             future.cancel()
 
 
-__all__ = ("ShortState",)
+__all__ = ("ShortState", "ShortStateContext")
diff --git a/telegrinder/bot/polling/polling.py b/telegrinder/bot/polling/polling.py
index d0221719..04de50f4 100755
--- a/telegrinder/bot/polling/polling.py
+++ b/telegrinder/bot/polling/polling.py
@@ -97,7 +97,7 @@ async def listen(self) -> typing.AsyncGenerator[list[Update], None]:
             except InvalidTokenError as e:
                 logger.error(e)
                 self.stop()
-                exit(6)
+                exit(3)
             except asyncio.CancelledError:
                 logger.info("Caught cancel, polling stopping...")
                 self.stop()
@@ -108,14 +108,14 @@ async def listen(self) -> typing.AsyncGenerator[list[Update], None]:
                         self.max_reconnetions,
                     )
                     self.stop()
-                    exit(9)
+                    exit(6)
                 else:
                     logger.warning(
-                        "Server disconnected, waiting 5 seconds to reconnetion...",
+                        "Server disconnected, waiting 5 seconds to reconnet...",
                     )
                     reconn_counter += 1
                     await asyncio.sleep(self.reconnection_timeout)
-            except aiohttp.ClientConnectorError:
+            except (aiohttp.ClientConnectorError, aiohttp.ClientOSError):
                 logger.error("Client connection failed, attempted to reconnect...")
                 await asyncio.sleep(self.reconnection_timeout)
             except BaseException as e:
diff --git a/telegrinder/bot/rules/__init__.py b/telegrinder/bot/rules/__init__.py
index 122b3fb8..83260841 100755
--- a/telegrinder/bot/rules/__init__.py
+++ b/telegrinder/bot/rules/__init__.py
@@ -26,11 +26,12 @@
     InlineQueryRule,
     InlineQueryText,
 )
-from .integer import Integer, IntegerInRange
+from .integer import IntegerInRange, IsInteger
 from .is_from import (
     IsBot,
     IsChat,
     IsChatId,
+    IsDice,
     IsDiceEmoji,
     IsForum,
     IsForward,
@@ -48,11 +49,12 @@
 from .mention import HasMention
 from .message import MessageRule
 from .message_entities import HasEntities, MessageEntities
+from .node import NodeRule
 from .regex import Regex
 from .rule_enum import RuleEnum
 from .start import StartCommand
-from .text import HasText, Text, TextMessageRule
-from .update import IsUpdate
+from .text import HasText, Text
+from .update import IsUpdateType
 
 __all__ = (
     "ABCRule",
@@ -80,13 +82,14 @@
     "InlineQueryMarkup",
     "InlineQueryRule",
     "InlineQueryText",
-    "Integer",
+    "IsInteger",
     "IntegerInRange",
     "InviteLinkByCreator",
     "InviteLinkName",
     "IsBot",
     "IsChat",
     "IsChatId",
+    "IsDice",
     "IsDiceEmoji",
     "IsForum",
     "IsForward",
@@ -97,7 +100,7 @@
     "IsPrivate",
     "IsReply",
     "IsSuperGroup",
-    "IsUpdate",
+    "IsUpdateType",
     "IsUser",
     "IsUserId",
     "Markup",
@@ -109,5 +112,5 @@
     "RuleEnum",
     "StartCommand",
     "Text",
-    "TextMessageRule",
+    "NodeRule",
 )
diff --git a/telegrinder/bot/rules/abc.py b/telegrinder/bot/rules/abc.py
index f722ec8b..dafaa5fc 100755
--- a/telegrinder/bot/rules/abc.py
+++ b/telegrinder/bot/rules/abc.py
@@ -1,25 +1,29 @@
 import inspect
-import typing
 from abc import ABC, abstractmethod
 
-from telegrinder.bot.cute_types import BaseCute, MessageCute, UpdateCute
+import typing_extensions as typing
+
+from telegrinder.bot.cute_types import MessageCute, UpdateCute
 from telegrinder.bot.dispatch.context import Context
 from telegrinder.bot.dispatch.process import check_rule
-from telegrinder.bot.rules.adapter import ABCAdapter, EventAdapter, RawUpdateAdapter
+from telegrinder.bot.rules.adapter import ABCAdapter, RawUpdateAdapter
+from telegrinder.bot.rules.adapter.node import Event
+from telegrinder.node.base import Node, is_node
+from telegrinder.node.composer import NodeCollection
 from telegrinder.tools.i18n.base import ABCTranslator
-from telegrinder.tools.magic import cache_translation, get_cached_translation
+from telegrinder.tools.magic import cache_translation, get_annotations, get_cached_translation
 from telegrinder.types.objects import Update as UpdateObject
 
-T = typing.TypeVar("T", bound=BaseCute)
+AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any)
 
 Message: typing.TypeAlias = MessageCute
 Update: typing.TypeAlias = UpdateCute
 
 
-def with_caching_translations(func):
+def with_caching_translations(func: typing.Callable[..., typing.Any]):
     """Should be used as decorator for .translate method. Caches rule translations."""
 
-    async def wrapper(self: "ABCRule[typing.Any]", translator: ABCTranslator):
+    async def wrapper(self: "ABCRule", translator: ABCTranslator):
         if translation := get_cached_translation(self, translator.locale):
             return translation
         translation = await func(self, translator)
@@ -29,15 +33,52 @@ async def wrapper(self: "ABCRule[typing.Any]", translator: ABCTranslator):
     return wrapper
 
 
-class ABCRule(ABC, typing.Generic[T]):
-    adapter: ABCAdapter[UpdateObject, T] = RawUpdateAdapter()  # type: ignore
-    requires: list["ABCRule[T]"] = []
+class ABCRule(ABC, typing.Generic[AdaptTo]):
+    adapter: ABCAdapter[UpdateObject, AdaptTo] = RawUpdateAdapter()  # type: ignore
+    requires: list["ABCRule"] = []
 
     @abstractmethod
-    async def check(self, event: T, ctx: Context) -> bool:
+    async def check(self, event: AdaptTo, *, ctx: Context) -> bool:
         pass
 
-    def __init_subclass__(cls, requires: list["ABCRule[T]"] | None = None):
+    def get_required_nodes(self) -> dict[str, type[Node]]:
+        return {k: v for k, v in get_annotations(self.check).items() if is_node(v)}
+
+    async def bounding_check(
+        self,
+        adapted_value: AdaptTo,
+        ctx: Context,
+        node_col: NodeCollection | None = None,
+    ) -> bool:
+        kw = {}
+        node_col_values = node_col.values() if node_col is not None else {}
+
+        for i, (k, v) in enumerate(get_annotations(self.check).items()):
+            if (isinstance(adapted_value, Event) and not i) or (
+                isinstance(v, type) and isinstance(adapted_value, v)
+            ):
+                kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
+            elif is_node(v):
+                assert k in node_col_values, "Node is undefined, error while bounding."
+                kw[k] = node_col_values[k]
+            elif k in ctx:
+                kw[k] = ctx[k]
+            elif v is Context:
+                kw[k] = ctx
+            else:
+                raise LookupError(
+                    f"Cannot bound {k!r} of type {v!r} to '{self.__class__.__name__}.check()', because it cannot be resolved."
+                )
+
+        return await self.check(**kw)
+
+    def optional(self) -> "ABCRule":
+        return self | Always()
+
+    def should_fail(self) -> "ABCRule":
+        return self & Never()
+
+    def __init_subclass__(cls, requires: list["ABCRule"] | None = None) -> None:
         """Merges requirements from inherited classes and rule-specific requirements."""
 
         requirements = []
@@ -48,17 +89,41 @@ def __init_subclass__(cls, requires: list["ABCRule[T]"] | None = None):
         requirements.extend(requires or ())
         cls.requires = list(dict.fromkeys(requirements))
 
-    def __and__(self, other: "ABCRule[T]"):
+    def __and__(self, other: "ABCRule") -> "AndRule":
+        """And Rule.
+
+        ```python
+        rule = HasText() & HasCaption()
+        rule #> AndRule(HasText(), HasCaption()) -> True if all rules in an AndRule are True, otherwise False.
+        ```
+        """
+
         return AndRule(self, other)
 
-    def __or__(self, other: "ABCRule[T]"):
+    def __or__(self, other: "ABCRule") -> "OrRule":
+        """Or Rule.
+
+        ```python
+        rule = HasText() | HasCaption()
+        rule #> OrRule(HasText(), HasCaption()) -> True if any rule in an OrRule are True, otherwise False.
+        ```
+        """
+
         return OrRule(self, other)
 
-    def __neg__(self) -> "ABCRule[T]":
+    def __invert__(self) -> "NotRule":
+        """Not Rule.
+
+        ```python
+        rule = ~HasText()
+        rule # NotRule(HasText()) -> True if rule returned False, otherwise False.
+        ```
+        """
+
         return NotRule(self)
 
     def __repr__(self) -> str:
-        return "<rule: {!r}, adapter: {!r}>".format(
+        return "<{}: adapter={!r}>".format(
             self.__class__.__name__,
             self.adapter,
         )
@@ -67,8 +132,8 @@ async def translate(self, translator: ABCTranslator) -> typing.Self:
         return self
 
 
-class AndRule(ABCRule[T]):
-    def __init__(self, *rules: ABCRule[T]):
+class AndRule(ABCRule):
+    def __init__(self, *rules: ABCRule[AdaptTo]) -> None:
         self.rules = rules
 
     async def check(self, event: Update, ctx: Context) -> bool:
@@ -80,8 +145,8 @@ async def check(self, event: Update, ctx: Context) -> bool:
         return True
 
 
-class OrRule(ABCRule[T]):
-    def __init__(self, *rules: ABCRule[T]):
+class OrRule(ABCRule):
+    def __init__(self, *rules: ABCRule) -> None:
         self.rules = rules
 
     async def check(self, event: Update, ctx: Context) -> bool:
@@ -93,8 +158,8 @@ async def check(self, event: Update, ctx: Context) -> bool:
         return False
 
 
-class NotRule(ABCRule[T]):
-    def __init__(self, rule: ABCRule[T]):
+class NotRule(ABCRule):
+    def __init__(self, rule: ABCRule) -> None:
         self.rule = rule
 
     async def check(self, event: Update, ctx: Context) -> bool:
@@ -102,10 +167,22 @@ async def check(self, event: Update, ctx: Context) -> bool:
         return not await check_rule(event.ctx_api, self.rule, event, ctx_copy)
 
 
+class Never(ABCRule):
+    async def check(self) -> typing.Literal[False]:
+        return False
+
+
+class Always(ABCRule):
+    async def check(self) -> typing.Literal[True]:
+        return True
+
+
 __all__ = (
     "ABCRule",
     "AndRule",
     "NotRule",
     "OrRule",
     "with_caching_translations",
+    "Never",
+    "Always",
 )
diff --git a/telegrinder/bot/rules/adapter/__init__.py b/telegrinder/bot/rules/adapter/__init__.py
index 2d8b5e68..c9cf3030 100755
--- a/telegrinder/bot/rules/adapter/__init__.py
+++ b/telegrinder/bot/rules/adapter/__init__.py
@@ -1,11 +1,14 @@
-from .abc import ABCAdapter
+from .abc import ABCAdapter, Event
 from .errors import AdapterError
 from .event import EventAdapter
+from .node import NodeAdapter
 from .raw_update import RawUpdateAdapter
 
 __all__ = (
     "ABCAdapter",
     "AdapterError",
     "EventAdapter",
+    "NodeAdapter",
     "RawUpdateAdapter",
+    "Event",
 )
diff --git a/telegrinder/bot/rules/adapter/abc.py b/telegrinder/bot/rules/adapter/abc.py
index f4d52a54..dbc0e6d0 100755
--- a/telegrinder/bot/rules/adapter/abc.py
+++ b/telegrinder/bot/rules/adapter/abc.py
@@ -1,21 +1,26 @@
 import abc
+import dataclasses
 import typing
 
 from fntypes.result import Result
 
 from telegrinder.api.abc import ABCAPI
-from telegrinder.bot.cute_types import BaseCute
 from telegrinder.bot.rules.adapter.errors import AdapterError
 from telegrinder.model import Model
 
-UpdateT = typing.TypeVar("UpdateT", bound=Model)
-CuteT = typing.TypeVar("CuteT", bound=BaseCute)
+From = typing.TypeVar("From", bound=Model)
+To = typing.TypeVar("To")
 
 
-class ABCAdapter(abc.ABC, typing.Generic[UpdateT, CuteT]):
+class ABCAdapter(abc.ABC, typing.Generic[From, To]):
     @abc.abstractmethod
-    async def adapt(self, api: ABCAPI, update: UpdateT) -> Result[CuteT, AdapterError]:
+    async def adapt(self, api: ABCAPI, update: From) -> Result[To, AdapterError]:
         pass
 
 
-__all__ = ("ABCAdapter",)
+@dataclasses.dataclass
+class Event(typing.Generic[To]):
+    obj: To
+
+
+__all__ = ("ABCAdapter", "Event")
diff --git a/telegrinder/bot/rules/adapter/errors.py b/telegrinder/bot/rules/adapter/errors.py
index 05063aea..fcbfd475 100755
--- a/telegrinder/bot/rules/adapter/errors.py
+++ b/telegrinder/bot/rules/adapter/errors.py
@@ -1,5 +1,4 @@
-class AdapterError(RuntimeError):
-    pass
+class AdapterError(RuntimeError): ...
 
 
 __all__ = ("AdapterError",)
diff --git a/telegrinder/bot/rules/adapter/event.py b/telegrinder/bot/rules/adapter/event.py
index 7735faed..c8c2bd3e 100755
--- a/telegrinder/bot/rules/adapter/event.py
+++ b/telegrinder/bot/rules/adapter/event.py
@@ -7,13 +7,14 @@
 from telegrinder.bot.rules.adapter.abc import ABCAdapter
 from telegrinder.bot.rules.adapter.errors import AdapterError
 from telegrinder.msgspec_utils import Nothing
+from telegrinder.types.enums import UpdateType
 from telegrinder.types.objects import Model, Update
 
-CuteT = typing.TypeVar("CuteT", bound=BaseCute)
+ToCute = typing.TypeVar("ToCute", bound=BaseCute)
 
 
-class EventAdapter(ABCAdapter[Update, CuteT]):
-    def __init__(self, event: str | type[Model], cute_model: type[CuteT]) -> None:
+class EventAdapter(ABCAdapter[Update, ToCute]):
+    def __init__(self, event: UpdateType | type[Model], cute_model: type[ToCute]) -> None:
         self.event = event
         self.cute_model = cute_model
 
@@ -27,27 +28,31 @@ def __repr__(self) -> str:
             )
         else:
             raw_update_type = self.event.__name__
+
         return "<{}: adapt {} -> {}>".format(
             self.__class__.__name__,
             raw_update_type,
             self.cute_model.__name__,
         )
 
-    async def adapt(self, api: ABCAPI, update: Update) -> Result[CuteT, AdapterError]:
+    async def adapt(self, api: ABCAPI, update: Update) -> Result[ToCute, AdapterError]:
         update_dct = update.to_dict()
-        if isinstance(self.event, str):
-            if self.event not in update_dct:
+        if isinstance(self.event, UpdateType):
+            if update.update_type != self.event:
                 return Error(
                     AdapterError(f"Update is not of event type {self.event!r}."),
                 )
-            if update_dct[self.event] is Nothing:
+
+            if update_dct[self.event.value] is Nothing:
                 return Error(
                     AdapterError(f"Update is not an {self.event!r}."),
                 )
+
             return Ok(
-                self.cute_model.from_update(update_dct[self.event].unwrap(), bound_api=api),
+                self.cute_model.from_update(update_dct[self.event.value].unwrap(), bound_api=api),
             )
-        event = update_dct[update.update_type.unwrap()].unwrap()
+
+        event = update_dct[update.update_type.value].unwrap()
         if not update.update_type or not issubclass(event.__class__, self.event):
             return Error(AdapterError(f"Update is not an {self.event.__name__!r}."))
         return Ok(self.cute_model.from_update(event, bound_api=api))
diff --git a/telegrinder/bot/rules/adapter/node.py b/telegrinder/bot/rules/adapter/node.py
new file mode 100644
index 00000000..8e9f6db8
--- /dev/null
+++ b/telegrinder/bot/rules/adapter/node.py
@@ -0,0 +1,42 @@
+import typing
+
+from fntypes.result import Error, Ok, Result
+
+from telegrinder.api.abc import ABCAPI
+from telegrinder.bot.cute_types.update import UpdateCute
+from telegrinder.bot.dispatch.context import Context
+from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
+from telegrinder.bot.rules.adapter.errors import AdapterError
+from telegrinder.node.base import ComposeError
+from telegrinder.node.composer import NodeSession, compose_node
+from telegrinder.types.objects import Update
+
+Ts = typing.TypeVarTuple("Ts")
+
+
+class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
+    def __init__(self, *nodes: *Ts) -> None:
+        self.nodes = nodes
+
+    def __repr__(self) -> str:
+        return "<{}: adapt Update -> {}>".format(
+            self.__class__.__name__,
+            Update.__name__,
+            ", ".join(node.__name__ for node in self.nodes),  # type: ignore
+        )
+
+    async def adapt(self, api: ABCAPI, update: Update) -> Result[Event[tuple[*Ts]], AdapterError]:
+        update_cute = UpdateCute.from_update(update, api)
+        node_sessions: list[NodeSession] = []
+        for node_t in self.nodes:
+            try:
+                # FIXME: adapters should have context
+                node_sessions.append(await compose_node(node_t, update_cute, Context()))  # type: ignore
+            except ComposeError:
+                for session in node_sessions:
+                    await session.close(with_value=None)
+                return Error(AdapterError(f"Couldn't compose nodes, error on {node_t}"))
+        return Ok(Event(tuple(node_sessions)))  # type: ignore
+
+
+__all__ = ("NodeAdapter",)
diff --git a/telegrinder/bot/rules/callback_data.py b/telegrinder/bot/rules/callback_data.py
index 790102ec..7ab31869 100755
--- a/telegrinder/bot/rules/callback_data.py
+++ b/telegrinder/bot/rules/callback_data.py
@@ -10,6 +10,7 @@
 from telegrinder.bot.rules.adapter import EventAdapter
 from telegrinder.model import decoder
 from telegrinder.tools.buttons import DataclassInstance
+from telegrinder.types.enums import UpdateType
 
 from .abc import ABCRule
 from .markup import Markup, PatternLike, check_string
@@ -22,7 +23,7 @@
 
 
 class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
-    adapter = EventAdapter("callback_query", CallbackQuery)
+    adapter: EventAdapter[CallbackQuery] = EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery)
 
     @abc.abstractmethod
     async def check(self, event: CallbackQuery, ctx: Context) -> bool:
@@ -31,7 +32,7 @@ async def check(self, event: CallbackQuery, ctx: Context) -> bool:
 
 class HasData(CallbackQueryRule):
     async def check(self, event: CallbackQuery, ctx: Context) -> bool:
-        return bool(event.data or event.data.unwrap())
+        return bool(event.data.unwrap_or_none())
 
 
 class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
@@ -98,8 +99,7 @@ async def match(cls, callback_data: dict[str, typing.Any], callback_map: Callbac
 
             if isinstance(validator, list):
                 if not (
-                    isinstance(callback_data[key], dict)
-                    and await cls.match(callback_data[key], validator)
+                    isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)
                 ):
                     return False
 
diff --git a/telegrinder/bot/rules/chat_join.py b/telegrinder/bot/rules/chat_join.py
index ad6c84ac..ea419a77 100644
--- a/telegrinder/bot/rules/chat_join.py
+++ b/telegrinder/bot/rules/chat_join.py
@@ -4,14 +4,15 @@
 from telegrinder.bot.cute_types import ChatJoinRequestCute
 from telegrinder.bot.dispatch.context import Context
 from telegrinder.bot.rules.adapter import EventAdapter
+from telegrinder.types.enums import UpdateType
 
 from .abc import ABCRule
 
 ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
 
 
-class ChatJoinRequestRule(ABCRule[ChatJoinRequestCute], requires=[]):
-    adapter = EventAdapter("chat_join_request", ChatJoinRequest)
+class ChatJoinRequestRule(ABCRule[ChatJoinRequest], requires=[]):
+    adapter: EventAdapter[ChatJoinRequest] = EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest)
 
     @abc.abstractmethod
     async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
diff --git a/telegrinder/bot/rules/command.py b/telegrinder/bot/rules/command.py
index 2c57d534..d3664bf9 100755
--- a/telegrinder/bot/rules/command.py
+++ b/telegrinder/bot/rules/command.py
@@ -2,24 +2,24 @@
 import typing
 
 from telegrinder.bot.dispatch.context import Context
+from telegrinder.node import Source
+from telegrinder.node.command import CommandInfo, single_split
+from telegrinder.node.me import Me
 
-from .abc import Message
-from .text import TextMessageRule
+from ...types import ChatType
+from .abc import ABCRule
 
 Validator = typing.Callable[[str], typing.Any | None]
 
 
-def single_split(s: str, separator: str) -> tuple[str, str]:
-    left, *right = s.split(separator, 1)
-    return left, (right[0] if right else "")
-
-
 @dataclasses.dataclass(frozen=True)
 class Argument:
     name: str
     validators: list[Validator] = dataclasses.field(default_factory=lambda: [])
     optional: bool = dataclasses.field(default=False, kw_only=True)
 
+    # NOTE: add optional param `description`
+
     def check(self, data: str) -> typing.Any | None:
         for validator in self.validators:
             data = validator(data)  # type: ignore
@@ -28,7 +28,7 @@ def check(self, data: str) -> typing.Any | None:
         return data
 
 
-class Command(TextMessageRule):
+class Command(ABCRule):
     def __init__(
         self,
         names: str | typing.Iterable[str],
@@ -36,12 +36,18 @@ def __init__(
         prefixes: tuple[str, ...] = ("/",),
         separator: str = " ",
         lazy: bool = False,
+        validate_mention: bool = True,
+        mention_needed_in_chat: bool = False,
     ) -> None:
         self.names = [names] if isinstance(names, str) else names
         self.arguments = arguments
         self.prefixes = prefixes
         self.separator = separator
         self.lazy = lazy
+        self.validate_mention = validate_mention
+
+        # if true then we'll check for mention when message is from a group
+        self.mention_needed_in_chat = mention_needed_in_chat
 
     def remove_prefix(self, text: str) -> str | None:
         for prefix in self.prefixes:
@@ -93,19 +99,25 @@ def parse_arguments(self, arguments: list[Argument], s: str) -> dict | None:
 
         return None
 
-    async def check(self, message: Message, ctx: Context) -> bool:
-        text = self.remove_prefix(message.text.unwrap())
-        if text is None:
+    async def check(self, command: CommandInfo, me: Me, src: Source, ctx: Context) -> bool:
+        name = self.remove_prefix(command.name)
+        if name is None:
             return False
 
-        name, arguments = single_split(text, self.separator)
         if name not in self.names:
             return False
 
+        if not command.mention and self.mention_needed_in_chat and src.chat.type is not ChatType.PRIVATE:
+            return False
+
+        if command.mention and self.validate_mention:  # noqa
+            if command.mention.unwrap().lower() != me.username.unwrap().lower():
+                return False
+
         if not self.arguments:
-            return not arguments
+            return not command.arguments
 
-        result = self.parse_arguments(list(self.arguments), arguments)
+        result = self.parse_arguments(list(self.arguments), command.arguments)
         if result is None:
             return False
 
diff --git a/telegrinder/bot/rules/enum_text.py b/telegrinder/bot/rules/enum_text.py
index 66f484b9..6766469e 100755
--- a/telegrinder/bot/rules/enum_text.py
+++ b/telegrinder/bot/rules/enum_text.py
@@ -2,14 +2,14 @@
 import typing
 
 from telegrinder.bot.dispatch.context import Context
+from telegrinder.node.text import Text
 
-from .abc import Message
-from .text import TextMessageRule
+from .abc import ABCRule
 
 T = typing.TypeVar("T", bound=enum.Enum)
 
 
-class EnumTextRule(TextMessageRule, typing.Generic[T]):
+class EnumTextRule(ABCRule, typing.Generic[T]):
     def __init__(self, enum_t: type[T], *, lower_case: bool = True) -> None:
         self.enum_t = enum_t
         self.texts = list(
@@ -25,8 +25,8 @@ def find(self, s: str) -> T:
                 return enumeration
         raise KeyError("Enumeration is undefined.")
 
-    async def check(self, message: Message, ctx: Context) -> bool:
-        text = message.text.unwrap().lower()
+    async def check(self, text: Text, ctx: Context) -> bool:
+        text = text.lower()  # type: ignore
         if text not in self.texts:
             return False
         ctx.enum_text = self.find(text)
diff --git a/telegrinder/bot/rules/func.py b/telegrinder/bot/rules/func.py
index f6c06e2b..90e8c18b 100755
--- a/telegrinder/bot/rules/func.py
+++ b/telegrinder/bot/rules/func.py
@@ -4,19 +4,19 @@
 from telegrinder.bot.dispatch.context import Context
 from telegrinder.types import Update
 
-from .abc import ABCAdapter, ABCRule, RawUpdateAdapter, T
+from .abc import ABCAdapter, ABCRule, AdaptTo, RawUpdateAdapter
 
 
-class FuncRule(ABCRule, typing.Generic[T]):
+class FuncRule(ABCRule, typing.Generic[AdaptTo]):
     def __init__(
         self,
-        func: typing.Callable[[T, Context], typing.Awaitable[bool] | bool],
-        adapter: ABCAdapter[Update, T] | None = None,
+        func: typing.Callable[[AdaptTo, Context], typing.Awaitable[bool] | bool],
+        adapter: ABCAdapter[Update, AdaptTo] | None = None,
     ):
         self.func = func
-        self.adapter = adapter or RawUpdateAdapter()
+        self.adapter = adapter or RawUpdateAdapter()  # type: ignore
 
-    async def check(self, event: T, ctx: Context) -> bool:
+    async def check(self, event: AdaptTo, ctx: Context) -> bool:
         result = self.func(event, ctx)
         if inspect.isawaitable(result):
             return await result
diff --git a/telegrinder/bot/rules/fuzzy.py b/telegrinder/bot/rules/fuzzy.py
index e8d0bff2..75045c09 100755
--- a/telegrinder/bot/rules/fuzzy.py
+++ b/telegrinder/bot/rules/fuzzy.py
@@ -1,22 +1,20 @@
 import difflib
 
 from telegrinder.bot.dispatch.context import Context
+from telegrinder.node.text import Text
 
-from .abc import Message
-from .text import TextMessageRule
+from .abc import ABCRule
 
 
-class FuzzyText(TextMessageRule):
+class FuzzyText(ABCRule):
     def __init__(self, texts: str | list[str], min_ratio: float = 0.7):
         if isinstance(texts, str):
             texts = [texts]
         self.texts = texts
         self.min_ratio = min_ratio
 
-    async def check(self, message: Message, ctx: Context) -> bool:
-        match = max(
-            difflib.SequenceMatcher(a=message.text.unwrap(), b=text).ratio() for text in self.texts
-        )
+    async def check(self, message_text: Text, ctx: Context) -> bool:
+        match = max(difflib.SequenceMatcher(a=message_text, b=text).ratio() for text in self.texts)
         if match < self.min_ratio:
             return False
         ctx.fuzzy_ratio = match
diff --git a/telegrinder/bot/rules/inline.py b/telegrinder/bot/rules/inline.py
index 64b67853..81da5f32 100755
--- a/telegrinder/bot/rules/inline.py
+++ b/telegrinder/bot/rules/inline.py
@@ -5,7 +5,7 @@
 from telegrinder.bot.dispatch.context import Context
 from telegrinder.bot.rules.abc import ABCRule
 from telegrinder.bot.rules.adapter import EventAdapter
-from telegrinder.types.enums import ChatType
+from telegrinder.types.enums import ChatType, UpdateType
 
 from .markup import Markup, PatternLike, check_string
 
@@ -13,11 +13,11 @@
 
 
 class InlineQueryRule(ABCRule[InlineQuery], abc.ABC):
-    adapter = EventAdapter("inline_query", InlineQuery)
+    adapter: EventAdapter[InlineQuery] = EventAdapter(UpdateType.INLINE_QUERY, InlineQuery)
 
     @abc.abstractmethod
     async def check(self, query: InlineQuery, ctx: Context) -> bool:
-        pass
+        ...
 
 
 class HasLocation(InlineQueryRule):
@@ -36,8 +36,7 @@ async def check(self, query: InlineQuery, ctx: Context) -> bool:
 class InlineQueryText(InlineQueryRule):
     def __init__(self, texts: str | list[str], *, lower_case: bool = False) -> None:
         self.texts = [
-            text.lower() if lower_case else text
-            for text in ([texts] if isinstance(texts, str) else texts)
+            text.lower() if lower_case else text for text in ([texts] if isinstance(texts, str) else texts)
         ]
         self.lower_case = lower_case
 
diff --git a/telegrinder/bot/rules/integer.py b/telegrinder/bot/rules/integer.py
index 37211f56..2f4143cb 100755
--- a/telegrinder/bot/rules/integer.py
+++ b/telegrinder/bot/rules/integer.py
@@ -1,19 +1,21 @@
 from telegrinder.bot.dispatch.context import Context
+from telegrinder.node.text import TextInteger
 
-from .text import Message, TextMessageRule
+from .abc import ABCRule
+from .node import NodeRule
 
 
-class Integer(TextMessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return message.text.unwrap().isdigit()
+class IsInteger(NodeRule):
+    def __init__(self) -> None:
+        super().__init__(TextInteger)
 
 
-class IntegerInRange(TextMessageRule, requires=[Integer()]):
+class IntegerInRange(ABCRule):
     def __init__(self, rng: range):
         self.rng = rng
 
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return int(message.text.unwrap()) in self.rng
+    async def check(self, integer: TextInteger) -> bool:
+        return integer in self.rng
 
 
-__all__ = ("Integer", "IntegerInRange")
+__all__ = ("IsInteger", "IntegerInRange")
diff --git a/telegrinder/bot/rules/is_from.py b/telegrinder/bot/rules/is_from.py
index a77e618a..a0ba7012 100755
--- a/telegrinder/bot/rules/is_from.py
+++ b/telegrinder/bot/rules/is_from.py
@@ -1,145 +1,117 @@
 import typing
 
-from fntypes.co import Nothing, Some
-
-from telegrinder.bot.cute_types.base import BaseCute
-from telegrinder.bot.cute_types.update import UpdateCute
-from telegrinder.bot.dispatch.context import Context
-from telegrinder.msgspec_utils import Option
+from telegrinder.node.source import ChatSource, UserSource
 from telegrinder.types.enums import ChatType, DiceEmoji
-from telegrinder.types.objects import User
 
 from .abc import ABCRule, Message
 from .message import MessageRule
 
-T = typing.TypeVar("T", bound=BaseCute)
-
-
-def get_from_user(obj: typing.Any) -> User:
-    assert isinstance(obj, FromUserProto)
-    return obj.from_.unwrap() if isinstance(obj.from_, Some | Nothing) else obj.from_
-
-
-@typing.runtime_checkable
-class FromUserProto(typing.Protocol):
-    from_: User | Option[User]
-
 
-class HasFrom(ABCRule[T]):
-    async def check(self, event: UpdateCute, ctx: Context) -> bool:
-        event_model = event.incoming_update.unwrap()
-        return isinstance(event_model, FromUserProto) and bool(event_model.from_)
+class IsBot(ABCRule):
+    async def check(self, user: UserSource) -> bool:
+        return user.is_bot
 
 
-class HasDice(MessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return bool(message.dice)
-
+class IsUser(ABCRule):
+    async def check(self, user: UserSource) -> bool:
+        return not user.is_bot
 
-class IsForward(MessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return bool(message.forward_origin)
 
+class IsPremium(ABCRule):
+    async def check(self, user: UserSource) -> bool:
+        return user.is_premium.unwrap_or(False)
 
-class IsForwardType(MessageRule, requires=[IsForward()]):
-    def __init__(
-        self, fwd_type: typing.Literal["user", "hidden_user", "chat", "channel"], /
-    ) -> None:
-        self.fwd_type = fwd_type
-
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return message.forward_origin.unwrap().v.type == self.fwd_type
 
+class IsLanguageCode(ABCRule):
+    def __init__(self, lang_codes: str | list[str], /) -> None:
+        self.lang_codes = [lang_codes] if isinstance(lang_codes, str) else lang_codes
 
-class IsReply(MessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return bool(message.reply_to_message)
+    async def check(self, user: UserSource) -> bool:
+        return user.language_code.unwrap_or_none() in self.lang_codes
 
 
-class IsSticker(MessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return bool(message.sticker)
+class IsUserId(ABCRule):
+    def __init__(self, user_ids: int | list[int], /) -> None:
+        self.user_ids = [user_ids] if isinstance(user_ids, int) else user_ids
 
+    async def check(self, user: UserSource) -> bool:
+        return user.id in self.user_ids
 
-class IsBot(ABCRule[T], requires=[HasFrom()]):
-    async def check(self, event: UpdateCute, ctx: Context) -> bool:
-        return get_from_user(event.incoming_update.unwrap()).is_bot
 
+class IsForum(ABCRule):
+    async def check(self, chat: ChatSource) -> bool:
+        return chat.is_forum.unwrap_or(False)
 
-class IsUser(ABCRule[T], requires=[HasFrom()]):
-    async def check(self, event: UpdateCute, ctx: Context) -> bool:
-        return not get_from_user(event.incoming_update.unwrap()).is_bot
 
+class IsChatId(ABCRule):
+    def __init__(self, chat_ids: int | list[int], /) -> None:
+        self.chat_ids = [chat_ids] if isinstance(chat_ids, int) else chat_ids
 
-class IsPremium(ABCRule[T], requires=[HasFrom()]):
-    async def check(self, event: UpdateCute, ctx: Context) -> bool:
-        return get_from_user(event.incoming_update.unwrap()).is_premium.unwrap_or(False)
+    async def check(self, chat: ChatSource) -> bool:
+        return chat.id in self.chat_ids
 
 
-class IsLanguageCode(ABCRule[T], requires=[HasFrom()]):
-    def __init__(self, lang_codes: str | list[str], /) -> None:
-        self.lang_codes = [lang_codes] if isinstance(lang_codes, str) else lang_codes
+class IsPrivate(ABCRule):
+    async def check(self, chat: ChatSource) -> bool:
+        return chat.type == ChatType.PRIVATE
 
-    async def check(self, event: UpdateCute, ctx: Context) -> bool:
-        return (
-            get_from_user(event.incoming_update.unwrap()).language_code.unwrap_or_none()
-            in self.lang_codes
-        )
 
+class IsGroup(ABCRule):
+    async def check(self, chat: ChatSource) -> bool:
+        return chat.type == ChatType.GROUP
 
-class IsForum(MessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return message.chat.is_forum.unwrap_or(False)
 
+class IsSuperGroup(ABCRule):
+    async def check(self, chat: ChatSource) -> bool:
+        return chat.type == ChatType.SUPERGROUP
 
-class IsUserId(ABCRule[T], requires=[HasFrom()]):
-    def __init__(self, user_ids: int | list[int], /) -> None:
-        self.user_ids = [user_ids] if isinstance(user_ids, int) else user_ids
 
-    async def check(self, event: UpdateCute, ctx: Context) -> bool:
-        return get_from_user(event.incoming_update.unwrap()).id in self.user_ids
+class IsChat(ABCRule):
+    async def check(self, chat: ChatSource) -> bool:
+        return chat.type in (ChatType.GROUP, ChatType.SUPERGROUP)
 
 
-class IsChatId(MessageRule):
-    def __init__(self, chat_ids: int | list[int], /) -> None:
-        self.chat_ids = [chat_ids] if isinstance(chat_ids, int) else chat_ids
+class IsDice(MessageRule):
+    async def check(self, message: Message) -> bool:
+        return bool(message.dice)
 
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return message.chat.id in self.chat_ids
 
+class IsDiceEmoji(MessageRule, requires=[IsDice()]):
+    def __init__(self, dice_emoji: DiceEmoji, /) -> None:
+        self.dice_emoji = dice_emoji
 
-class IsPrivate(MessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return message.chat.type == ChatType.PRIVATE
+    async def check(self, message: Message) -> bool:
+        return message.dice.unwrap().emoji == self.dice_emoji
 
 
-class IsGroup(MessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return message.chat.type == ChatType.GROUP
+class IsForward(MessageRule):
+    async def check(self, message: Message) -> bool:
+        return bool(message.forward_origin)
 
 
-class IsSuperGroup(MessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return message.chat.type == ChatType.SUPERGROUP
+class IsForwardType(MessageRule, requires=[IsForward()]):
+    def __init__(self, fwd_type: typing.Literal["user", "hidden_user", "chat", "channel"], /) -> None:
+        self.fwd_type = fwd_type
 
+    async def check(self, message: Message) -> bool:
+        return message.forward_origin.unwrap().v.type == self.fwd_type
 
-class IsChat(MessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return message.chat.type in (ChatType.GROUP, ChatType.SUPERGROUP)
 
+class IsReply(MessageRule):
+    async def check(self, message: Message) -> bool:
+        return bool(message.reply_to_message)
 
-class IsDiceEmoji(MessageRule, requires=[HasDice()]):
-    def __init__(self, dice_emoji: DiceEmoji, /) -> None:
-        self.dice_emoji = dice_emoji
 
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return message.dice.unwrap().emoji == self.dice_emoji
+class IsSticker(MessageRule):
+    async def check(self, message: Message) -> bool:
+        return bool(message.sticker)
 
 
 __all__ = (
     "IsBot",
     "IsChat",
     "IsChatId",
+    "IsDice",
     "IsDiceEmoji",
     "IsForum",
     "IsForward",
diff --git a/telegrinder/bot/rules/markup.py b/telegrinder/bot/rules/markup.py
index 6396376a..47e89396 100755
--- a/telegrinder/bot/rules/markup.py
+++ b/telegrinder/bot/rules/markup.py
@@ -3,10 +3,10 @@
 import vbml
 
 from telegrinder.bot.dispatch.context import Context
+from telegrinder.node.text import Text
 from telegrinder.tools.global_context import TelegrinderCtx
 
-from .abc import Message
-from .text import TextMessageRule
+from .abc import ABCRule
 
 PatternLike: typing.TypeAlias = str | vbml.Pattern
 global_ctx = TelegrinderCtx()
@@ -23,7 +23,7 @@ def check_string(patterns: list[vbml.Pattern], s: str, ctx: Context) -> bool:
     return False
 
 
-class Markup(TextMessageRule):
+class Markup(ABCRule):
     def __init__(self, patterns: PatternLike | list[PatternLike], /):
         if not isinstance(patterns, list):
             patterns = [patterns]
@@ -31,8 +31,8 @@ def __init__(self, patterns: PatternLike | list[PatternLike], /):
             vbml.Pattern(pattern) if isinstance(pattern, str) else pattern for pattern in patterns
         ]
 
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return check_string(self.patterns, message.text.unwrap(), ctx)
+    async def check(self, text: Text, ctx: Context) -> bool:
+        return check_string(self.patterns, text, ctx)
 
 
 __all__ = ("Markup", "check_string")
diff --git a/telegrinder/bot/rules/mention.py b/telegrinder/bot/rules/mention.py
index cbd3fd90..8b0a201f 100755
--- a/telegrinder/bot/rules/mention.py
+++ b/telegrinder/bot/rules/mention.py
@@ -1,11 +1,11 @@
-from telegrinder.bot.dispatch.context import Context
 from telegrinder.types.enums import MessageEntityType
 
-from .text import Message, TextMessageRule
+from .message import Message, MessageRule
+from .text import HasText
 
 
-class HasMention(TextMessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
+class HasMention(MessageRule, requires=[HasText()]):
+    async def check(self, message: Message) -> bool:
         if not message.entities.unwrap_or_none():
             return False
         return any(entity.type == MessageEntityType.MENTION for entity in message.entities.unwrap())
diff --git a/telegrinder/bot/rules/message.py b/telegrinder/bot/rules/message.py
index b01bc5e9..80111a8c 100644
--- a/telegrinder/bot/rules/message.py
+++ b/telegrinder/bot/rules/message.py
@@ -8,7 +8,7 @@
 
 
 class MessageRule(ABCRule[Message], abc.ABC):
-    adapter = EventAdapter(MessageEvent, Message)
+    adapter: EventAdapter[Message] = EventAdapter(MessageEvent, Message)
 
     @abc.abstractmethod
     async def check(self, message: Message, ctx: Context) -> bool: ...
diff --git a/telegrinder/bot/rules/node.py b/telegrinder/bot/rules/node.py
new file mode 100644
index 00000000..d4b11149
--- /dev/null
+++ b/telegrinder/bot/rules/node.py
@@ -0,0 +1,27 @@
+import typing
+
+from telegrinder.bot.dispatch.context import Context
+from telegrinder.node import Node
+
+from .abc import ABCRule
+from .adapter.node import NodeAdapter
+
+
+class NodeRule(ABCRule[tuple[Node, ...]]):
+    def __init__(self, *nodes: type[Node] | tuple[str, type[Node]]) -> None:
+        bindings = [binding if isinstance(binding, tuple) else (None, binding) for binding in nodes]
+        self.nodes = [binding[1] for binding in bindings]
+        self.node_keys = [binding[0] for binding in bindings]
+
+    @property
+    def adapter(self) -> NodeAdapter:
+        return NodeAdapter(*self.nodes)
+
+    async def check(self, resolved_nodes: tuple[Node, ...], ctx: Context) -> typing.Literal[True]:
+        for i, node in enumerate(resolved_nodes):
+            if key := self.node_keys[i]:
+                ctx[key] = node
+        return True
+
+
+__all__ = ("NodeRule",)
diff --git a/telegrinder/bot/rules/regex.py b/telegrinder/bot/rules/regex.py
index 1a903bb3..53c5f01d 100755
--- a/telegrinder/bot/rules/regex.py
+++ b/telegrinder/bot/rules/regex.py
@@ -2,14 +2,14 @@
 import typing
 
 from telegrinder.bot.dispatch.context import Context
+from telegrinder.node.text import Text
 
-from .abc import Message
-from .text import TextMessageRule
+from .abc import ABCRule
 
 PatternLike: typing.TypeAlias = str | typing.Pattern[str]
 
 
-class Regex(TextMessageRule):
+class Regex(ABCRule):
     def __init__(self, regexp: PatternLike | list[PatternLike]):
         self.regexp: list[re.Pattern[str]] = []
         match regexp:
@@ -22,9 +22,9 @@ def __init__(self, regexp: PatternLike | list[PatternLike]):
                     re.compile(regexp) if isinstance(regexp, str) else regexp for regexp in regexp
                 )
 
-    async def check(self, message: Message, ctx: Context) -> bool:
+    async def check(self, text: Text, ctx: Context) -> bool:
         for regexp in self.regexp:
-            response = re.match(regexp, message.text.unwrap())
+            response = re.match(regexp, text)
             if response is not None:
                 if matches := response.groupdict():
                     ctx |= matches
diff --git a/telegrinder/bot/rules/rule_enum.py b/telegrinder/bot/rules/rule_enum.py
index 4c96a407..630a9736 100755
--- a/telegrinder/bot/rules/rule_enum.py
+++ b/telegrinder/bot/rules/rule_enum.py
@@ -3,7 +3,7 @@
 
 from telegrinder.bot.dispatch.context import Context
 
-from .abc import ABCRule, T, Update, check_rule
+from .abc import ABCRule, Update, check_rule
 from .func import FuncRule
 
 
@@ -17,10 +17,10 @@ def __eq__(self, other: typing.Self) -> bool:
         return self.cls == other.cls and self.name == other.name
 
 
-class RuleEnum(ABCRule[T]):
+class RuleEnum(ABCRule):
     __enum__: list[RuleEnumState]
 
-    def __init_subclass__(cls, *args, **kwargs):
+    def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
         new_attributes = set(cls.__dict__) - set(RuleEnum.__dict__) - {"__enum__", "__init__"}
         enum_lst: list[RuleEnumState] = []
 
@@ -34,7 +34,7 @@ def __init_subclass__(cls, *args, **kwargs):
             setattr(
                 self,
                 attribute.name,
-                self & FuncRule(lambda _, ctx: self.must_be_state(ctx, attribute)),
+                self & FuncRule(lambda _, ctx: self.must_be_state(ctx, attribute)),  # type: ignore
             )
             enum_lst.append(attribute)
 
diff --git a/telegrinder/bot/rules/start.py b/telegrinder/bot/rules/start.py
index e4359344..1737f85e 100755
--- a/telegrinder/bot/rules/start.py
+++ b/telegrinder/bot/rules/start.py
@@ -4,7 +4,7 @@
 from telegrinder.types.enums import MessageEntityType
 
 from .is_from import IsPrivate
-from .markup import Markup, Message
+from .markup import Markup
 from .message import MessageRule
 from .message_entities import MessageEntities
 
@@ -12,9 +12,9 @@
 class StartCommand(
     MessageRule,
     requires=[
-        IsPrivate()
-        & MessageEntities(MessageEntityType.BOT_COMMAND)
-        & Markup(["/start <param>", "/start"]),
+        IsPrivate(), 
+        MessageEntities(MessageEntityType.BOT_COMMAND),
+        Markup(["/start <param>", "/start"]),
     ],
 ):
     def __init__(
@@ -28,7 +28,7 @@ def __init__(
         self.validator = validator
         self.alias = alias
 
-    async def check(self, _: Message, ctx: Context) -> bool:
+    async def check(self, ctx: Context) -> bool:
         param: str | None = ctx.pop("param", None)
         validated_param = self.validator(param) if self.validator and param is not None else param
 
diff --git a/telegrinder/bot/rules/text.py b/telegrinder/bot/rules/text.py
index 32e3f081..2f52226d 100755
--- a/telegrinder/bot/rules/text.py
+++ b/telegrinder/bot/rules/text.py
@@ -1,28 +1,24 @@
+from telegrinder import node
 from telegrinder.bot.dispatch.context import Context
 from telegrinder.tools.i18n.base import ABCTranslator
 
-from .abc import ABC, Message, with_caching_translations
-from .message import MessageRule
+from .abc import ABCRule, with_caching_translations
+from .node import NodeRule
 
 
-class HasText(MessageRule):
-    async def check(self, message: Message, ctx: Context) -> bool:
-        return bool(message.text)
+class HasText(NodeRule):
+    def __init__(self) -> None:
+        super().__init__(node.text.Text)
 
 
-class TextMessageRule(MessageRule, ABC, requires=[HasText()]):
-    pass
-
-
-class Text(TextMessageRule):
+class Text(ABCRule):
     def __init__(self, texts: str | list[str], *, ignore_case: bool = False) -> None:
         if not isinstance(texts, list):
             texts = [texts]
         self.texts = texts if not ignore_case else list(map(str.lower, texts))
         self.ignore_case = ignore_case
 
-    async def check(self, message: Message, ctx: Context) -> bool:
-        text = message.text.unwrap()
+    async def check(self, text: node.text.Text, ctx: Context) -> bool:
         return (text if not self.ignore_case else text.lower()) in self.texts
 
     @with_caching_translations
@@ -33,4 +29,4 @@ async def translate(self, translator: ABCTranslator) -> "Text":
         )
 
 
-__all__ = ("HasText", "Text", "TextMessageRule")
+__all__ = ("HasText", "Text")
diff --git a/telegrinder/bot/rules/update.py b/telegrinder/bot/rules/update.py
index 38f4ef26..2978aeca 100644
--- a/telegrinder/bot/rules/update.py
+++ b/telegrinder/bot/rules/update.py
@@ -2,15 +2,15 @@
 from telegrinder.bot.dispatch.context import Context
 from telegrinder.types.enums import UpdateType
 
-from .abc import ABCRule, T
+from .abc import ABCRule
 
 
-class IsUpdate(ABCRule[T]):
+class IsUpdateType(ABCRule):
     def __init__(self, update_type: UpdateType, /) -> None:
         self.update_type = update_type
 
     async def check(self, event: UpdateCute, ctx: Context) -> bool:
-        return event.update_type.unwrap_or_none() == self.update_type
+        return event.update_type == self.update_type
 
 
-__all__ = ("IsUpdate",)
+__all__ = ("IsUpdateType",)
diff --git a/telegrinder/bot/scenario/__init__.py b/telegrinder/bot/scenario/__init__.py
index a54ead29..6422d287 100755
--- a/telegrinder/bot/scenario/__init__.py
+++ b/telegrinder/bot/scenario/__init__.py
@@ -1,5 +1,5 @@
 from .abc import ABCScenario
-from .checkbox import Checkbox, Choice
-from .choice import SingleChoice
+from .checkbox import Checkbox
+from .choice import Choice
 
-__all__ = ("ABCScenario", "Checkbox", "Choice", "SingleChoice")
+__all__ = ("ABCScenario", "Checkbox", "Choice")
diff --git a/telegrinder/bot/scenario/choice.py b/telegrinder/bot/scenario/choice.py
index d4f50a23..91d8c5b0 100755
--- a/telegrinder/bot/scenario/choice.py
+++ b/telegrinder/bot/scenario/choice.py
@@ -1,7 +1,6 @@
 import typing
 
 from telegrinder.bot.cute_types import CallbackQueryCute
-from telegrinder.bot.dispatch.waiter_machine import WaiterMachine
 
 from .checkbox import Checkbox
 
@@ -10,7 +9,7 @@
     from telegrinder.bot.dispatch.view.abc import BaseStateView
 
 
-class SingleChoice(Checkbox):
+class Choice(Checkbox):
     async def handle(self, cb: CallbackQueryCute) -> bool:
         code = cb.data.unwrap().replace(self.random_code + "/", "", 1)
         if code == "ready":
@@ -43,4 +42,4 @@ async def wait(
         return tuple(choices.keys())[tuple(choices.values()).index(True)], m_id
 
 
-__all__ = ("SingleChoice",)
+__all__ = ("Choice",)
diff --git a/telegrinder/model.py b/telegrinder/model.py
index ed6d23cd..b724fcd8 100755
--- a/telegrinder/model.py
+++ b/telegrinder/model.py
@@ -1,5 +1,6 @@
 import dataclasses
 import enum
+import keyword
 import secrets
 import typing
 from datetime import datetime
@@ -19,7 +20,7 @@
 MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
     "omit_defaults": True,
     "dict": True,
-    "rename": {"from_": "from"},
+    "rename": {kw + "_": kw for kw in keyword.kwlist},
 }
 
 
@@ -45,14 +46,17 @@ def full_result(
 
 
 def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
-    return {
-        k: v.unwrap() if isinstance(v, Some) else v
-        for k, v in (
-            *params.pop("other", {}).items(),
-            *params.items(),
-        )
-        if k != "self" and type(v) not in (NoneType, Nothing)
-    }
+    validated_params = {}
+    for k, v in (
+        *params.pop("other", {}).items(),
+        *params.items(),
+    ):
+        if isinstance(v, Proxy):
+            v = v.get()
+        if k == "self" or type(v) in (NoneType, Nothing):
+            continue
+        validated_params[k] = v.unwrap() if isinstance(v, Some) else v
+    return validated_params
 
 
 class Model(msgspec.Struct, **MODEL_CONFIG):
@@ -69,9 +73,7 @@ def to_dict(
         if "model_as_dict" not in self.__dict__:
             self.__dict__["model_as_dict"] = msgspec.structs.asdict(self)
         return {
-            key: value
-            for key, value in self.__dict__["model_as_dict"].items()
-            if key not in exclude_fields
+            key: value for key, value in self.__dict__["model_as_dict"].items() if key not in exclude_fields
         }
 
 
@@ -82,7 +84,7 @@ class DataConverter:
     def __repr__(self) -> str:
         return "<{}: {}>".format(
             self.__class__.__name__,
-            ", ".join(f"{k}={v!r}" for k, v in self.converters),
+            ", ".join(f"{k}={v.__name__!r}" for k, v in self.converters.items()),
         )
 
     @property
@@ -129,9 +131,7 @@ def convert_dct(
         serialize: bool = True,
     ) -> dict[str, typing.Any]:
         return {
-            k: self(v, serialize=serialize)
-            for k, v in data.items()
-            if type(v) not in (NoneType, Nothing)
+            k: self(v, serialize=serialize) for k, v in data.items() if type(v) not in (NoneType, Nothing)
         }
 
     def convert_lst(
@@ -159,8 +159,43 @@ def convert_tpl(
         return data
 
 
+class Proxy:
+    def __init__(self, cfg: "_ProxiedDict", key: str):
+        self.key = key
+        self.cfg = cfg
+
+    def get(self) -> typing.Any | None:
+        return self.cfg._defaults.get(self.key)
+
+
+class _ProxiedDict(typing.Generic[T]):
+    def __init__(self, tp: type[T]) -> None:
+        self.type = tp
+        self._defaults = {}
+
+    def __setattribute__(self, name: str, value: typing.Any, /) -> None:
+        self._defaults[name] = value
+
+    def __getitem__(self, key: str, /) -> None:
+        return Proxy(self, key)  # type: ignore
+
+    def __setitem__(self, key: str, value: typing.Any, /) -> None:
+        self._defaults[key] = value
+
+
+if typing.TYPE_CHECKING:
+
+    def ProxiedDict(typed_dct: type[T]) -> T | _ProxiedDict[T]:  # noqa: N802
+        ...
+
+else:
+    ProxiedDict = _ProxiedDict
+
+
 __all__ = (
+    "Proxy",
     "DataConverter",
+    "ProxiedDict",
     "MODEL_CONFIG",
     "Model",
     "full_result",
diff --git a/telegrinder/modules.py b/telegrinder/modules.py
index 14726e27..a5a445bb 100755
--- a/telegrinder/modules.py
+++ b/telegrinder/modules.py
@@ -40,10 +40,13 @@ def set_level(
 
 logger: LoggerModule
 json: JSONModule = choice_in_order(
-    ["orjson", "ujson", "hyperjson"], do_import=True, default="telegrinder.msgspec_json"
+    ["orjson", "ujson", "hyperjson"],
+    default="telegrinder.msgspec_json",
+    do_import=True,
 )
-logging_module = choice_in_order(["loguru"], default="logging")
 logging_level = os.getenv("LOGGER_LEVEL", default="DEBUG").upper()
+logging_module = choice_in_order(["loguru"], default="logging")
+asyncio_module = choice_in_order(["uvloop"], default="asyncio")
 
 if logging_module == "loguru":
     import os
@@ -201,9 +204,7 @@ def proc(
             kwargs: dict[str, object],
         ) -> tuple[LogMessage | object, tuple[object, ...], dict[str, object]]:
             log_kwargs = {
-                key: kwargs[key]
-                for key in inspect.getfullargspec(self.logger._log).args[1:]
-                if key in kwargs
+                key: kwargs[key] for key in inspect.getfullargspec(self.logger._log).args[1:] if key in kwargs
             }
 
             if isinstance(msg, str):
@@ -214,10 +215,17 @@ def proc(
     handler = logging.StreamHandler(sys.stderr)
     handler.setFormatter(TelegrinderLoggingFormatter())
     logger = logging.getLogger("telegrinder")  # type: ignore
-    logger.setLevel(logging.getLevelName(logging_level))  # type: ignore
+    logger.setLevel(logging_level)  # type: ignore
     logger.addHandler(handler)  # type: ignore
     logger = TelegrinderLoggingStyleAdapter(logger)  # type: ignore
 
+if asyncio_module == "uvloop":
+    import asyncio
+
+    import uvloop  # type: ignore
+
+    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())  # type: ignore
+
 
 def _set_logger_level(level):
     level = level.upper()
diff --git a/telegrinder/msgspec_utils.py b/telegrinder/msgspec_utils.py
index be1333ff..f26f315f 100755
--- a/telegrinder/msgspec_utils.py
+++ b/telegrinder/msgspec_utils.py
@@ -1,6 +1,7 @@
 import typing
 
 import fntypes.option
+import fntypes.result
 import msgspec
 from fntypes.co import Error, Ok, Result, Variative
 
@@ -8,10 +9,12 @@
     from datetime import datetime
 
     from fntypes.option import Option
+    from fntypes.result import Result
 else:
     from datetime import datetime as dt
 
     Value = typing.TypeVar("Value")
+    Err = typing.TypeVar("Err")
 
     datetime = type("datetime", (dt,), {})
 
@@ -19,14 +22,22 @@ class OptionMeta(type):
         def __instancecheck__(cls, __instance: typing.Any) -> bool:
             return isinstance(__instance, fntypes.option.Some | fntypes.option.Nothing)
 
+    class ResultMeta(type):
+        def __instancecheck__(cls, __instance: typing.Any) -> bool:
+            return isinstance(__instance, fntypes.result.Ok | fntypes.result.Error)
+
     class Option(typing.Generic[Value], metaclass=OptionMeta):
         pass
 
+    class Result(typing.Generic[Value, Err], metaclass=ResultMeta):
+        pass
+
 
 T = typing.TypeVar("T")
+Type = typing.TypeVar("Type", bound=type | typing.Any)
 Ts = typing.TypeVarTuple("Ts")
 
-DecHook: typing.TypeAlias = typing.Callable[[type[T], typing.Any], object]
+DecHook: typing.TypeAlias = typing.Callable[[type[T], typing.Any], typing.Any]
 EncHook: typing.TypeAlias = typing.Callable[[T], typing.Any]
 
 Nothing: typing.Final[fntypes.option.Nothing] = fntypes.option.Nothing()
@@ -40,18 +51,52 @@ def repr_type(t: type) -> str:
     return getattr(t, "__name__", repr(get_origin(t)))
 
 
-def msgspec_convert(obj: typing.Any, t: type[T]) -> Result[T, msgspec.ValidationError]:
+def msgspec_convert(obj: typing.Any, t: type[T]) -> Result[T, str]:
     try:
         return Ok(decoder.convert(obj, type=t, strict=True))
-    except msgspec.ValidationError as exc:
-        return Error(exc)
+    except msgspec.ValidationError:
+        return Error(
+            "Expected object of type `{}`, got `{}`.".format(
+                repr_type(t),
+                repr_type(type(obj)),
+            )
+        )
 
 
 def option_dec_hook(tp: type[Option[typing.Any]], obj: typing.Any) -> Option[typing.Any]:
-    if obj is None:
-        return Nothing
+    orig_type = get_origin(tp)
     (value_type,) = typing.get_args(tp) or (typing.Any,)
-    return msgspec_convert({"value": obj}, fntypes.option.Some[value_type]).unwrap()
+
+    if obj is None and orig_type in (fntypes.option.Nothing, Option):
+        return fntypes.option.Nothing()
+    return fntypes.option.Some(msgspec_convert(obj, value_type).unwrap())
+
+
+def result_dec_hook(
+    tp: type[Result[typing.Any, typing.Any]], obj: typing.Any
+) -> Result[typing.Any, typing.Any]:
+    if not isinstance(obj, dict):
+        raise TypeError(f"Cannot parse to Result object of type `{repr_type(type(obj))}`.")
+
+    orig_type = get_origin(tp)
+    (first_type, second_type) = (
+        typing.get_args(tp) + (typing.Any,) if len(typing.get_args(tp)) == 1 else typing.get_args(tp)
+    ) or (typing.Any, typing.Any)
+
+    if orig_type is Ok and "ok" in obj:
+        return Ok(msgspec_convert(obj["ok"], first_type).unwrap())
+
+    if orig_type is Error and "error" in obj:
+        return Error(msgspec_convert(obj["error"], first_type).unwrap())
+
+    if orig_type is Result:
+        match obj:
+            case {"ok": ok}:
+                return Ok(msgspec_convert(ok, first_type).unwrap())
+            case {"error": error}:
+                return Error(msgspec_convert(error, second_type).unwrap())
+
+    raise msgspec.ValidationError(f"Cannot parse object `{obj!r}` to Result.")
 
 
 def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
@@ -67,9 +112,7 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
         reverse = False
 
         if len(set(struct_fields_match_sums.values())) != len(struct_fields_match_sums.values()):
-            struct_fields_match_sums = {
-                m: len(m.__struct_fields__) for m in struct_fields_match_sums
-            }
+            struct_fields_match_sums = {m: len(m.__struct_fields__) for m in struct_fields_match_sums}
             reverse = True
 
         union_types = (
@@ -123,9 +166,14 @@ class Digit(enum.IntEnum):
 
     def __init__(self) -> None:
         self.dec_hooks: dict[typing.Any, DecHook[typing.Any]] = {
+            Result: result_dec_hook,
             Option: option_dec_hook,
             Variative: variative_dec_hook,
             datetime: lambda t, obj: t.fromtimestamp(obj),
+            fntypes.result.Error: result_dec_hook,
+            fntypes.result.Ok: result_dec_hook,
+            fntypes.option.Some: option_dec_hook,
+            fntypes.option.Nothing: option_dec_hook,
         }
 
     def __repr__(self) -> str:
@@ -144,8 +192,7 @@ def dec_hook(self, tp: type[typing.Any], obj: object) -> object:
         origin_type = t if isinstance((t := get_origin(tp)), type) else type(t)
         if origin_type not in self.dec_hooks:
             raise TypeError(
-                f"Unknown type `{repr_type(origin_type)}`. "
-                "You can implement decode hook for this type."
+                f"Unknown type `{repr_type(origin_type)}`. You can implement decode hook for this type."
             )
         return self.dec_hooks[origin_type](tp, obj)
 
@@ -173,7 +220,7 @@ def convert(
     def decode(self, buf: str | bytes) -> typing.Any: ...
 
     @typing.overload
-    def decode(self, buf: str | bytes, *, strict: bool = True) -> typing.Any: ...
+    def decode(self, buf: str | bytes, *, type: type[T]) -> T: ...
 
     @typing.overload
     def decode(
@@ -184,16 +231,10 @@ def decode(
         strict: bool = True,
     ) -> T: ...
 
-    def decode(
-        self,
-        buf: str | bytes,
-        *,
-        type: type[T] = typing.Any,  # type: ignore
-        strict: bool = True,
-    ) -> T:
+    def decode(self, buf, *, type=object, strict=True):
         return msgspec.json.decode(
             buf,
-            type=type,
+            type=typing.Any if type is object else type,
             strict=strict,
             dec_hook=self.dec_hook,
         )
@@ -219,6 +260,10 @@ def __init__(self) -> None:
         self.enc_hooks: dict[typing.Any, EncHook[typing.Any]] = {
             fntypes.option.Some: lambda opt: opt.value,
             fntypes.option.Nothing: lambda _: None,
+            fntypes.result.Ok: lambda ok: {"ok": ok.value},
+            fntypes.result.Error: lambda err: {
+                "error": (str(err.error) if isinstance(err.error, BaseException) else err.error)
+            },
             Variative: lambda variative: variative.v,
             datetime: lambda date: int(date.timestamp()),
         }
@@ -240,8 +285,7 @@ def enc_hook(self, obj: object) -> object:
         origin_type = get_origin(obj.__class__)
         if origin_type not in self.enc_hooks:
             raise NotImplementedError(
-                "Not implemented encode hook for "
-                f"object of type `{repr_type(origin_type)}`."
+                f"Not implemented encode hook for object of type `{repr_type(origin_type)}`."
             )
         return self.enc_hooks[origin_type](obj)
 
diff --git a/telegrinder/node/__init__.py b/telegrinder/node/__init__.py
index 48835109..0ae43c0e 100755
--- a/telegrinder/node/__init__.py
+++ b/telegrinder/node/__init__.py
@@ -1,17 +1,21 @@
 from .attachment import Attachment, Audio, Photo, Video
-from .base import ComposeError, DataNode, Node, ScalarNode
-from .composer import NodeCollection, NodeSession, compose_node
+from .base import ComposeError, DataNode, Node, ScalarNode, is_node
+from .command import CommandInfo
+from .composer import Composition, NodeCollection, NodeSession, compose_node, compose_nodes
 from .container import ContainerNode
+from .me import Me
 from .message import MessageNode
-from .rule import RuleContext
-from .source import Source
-from .text import Text
-from .tools import generate
+from .rule import RuleChain
+from .scope import GLOBAL, PER_CALL, PER_EVENT, NodeScope, global_node, per_call, per_event
+from .source import ChatSource, Source, UserSource
+from .text import Text, TextInteger
+from .tools import generate_node
 from .update import UpdateNode
 
 __all__ = (
     "Attachment",
     "Audio",
+    "ChatSource",
     "ComposeError",
     "ContainerNode",
     "DataNode",
@@ -20,12 +24,26 @@
     "NodeCollection",
     "NodeSession",
     "Photo",
-    "RuleContext",
+    "RuleChain",
     "ScalarNode",
     "Source",
     "Text",
+    "TextInteger",
+    "UserSource",
     "UpdateNode",
     "Video",
     "compose_node",
-    "generate",
+    "generate_node",
+    "Composition",
+    "is_node",
+    "compose_nodes",
+    "NodeScope",
+    "PER_CALL",
+    "PER_EVENT",
+    "per_call",
+    "per_event",
+    "CommandInfo",
+    "GLOBAL",
+    "global_node",
+    "Me",
 )
diff --git a/telegrinder/node/attachment.py b/telegrinder/node/attachment.py
index f3367c9d..ef9e7a88 100755
--- a/telegrinder/node/attachment.py
+++ b/telegrinder/node/attachment.py
@@ -1,10 +1,10 @@
 import dataclasses
 import typing
 
+from fntypes import Option, Some
 from fntypes.option import Nothing
 
 import telegrinder.types
-from telegrinder.msgspec_utils import Option
 
 from .base import ComposeError, DataNode, ScalarNode
 from .message import MessageNode
@@ -13,22 +13,26 @@
 @dataclasses.dataclass
 class Attachment(DataNode):
     attachment_type: typing.Literal["audio", "document", "photo", "poll", "video"]
-    _: dataclasses.KW_ONLY
-    audio: Option[telegrinder.types.Audio] = dataclasses.field(default_factory=lambda: Nothing())
+    audio: Option[telegrinder.types.Audio] = dataclasses.field(
+        default_factory=lambda: Nothing(), kw_only=True
+    )
     document: Option[telegrinder.types.Document] = dataclasses.field(
-        default_factory=lambda: Nothing()
+        default_factory=lambda: Nothing(), kw_only=True
     )
     photo: Option[list[telegrinder.types.PhotoSize]] = dataclasses.field(
-        default_factory=lambda: Nothing()
+        default_factory=lambda: Nothing(), kw_only=True
+    )
+    poll: Option[telegrinder.types.Poll] = dataclasses.field(default_factory=lambda: Nothing(), kw_only=True)
+    video: Option[telegrinder.types.Video] = dataclasses.field(
+        default_factory=lambda: Nothing(), kw_only=True
     )
-    poll: Option[telegrinder.types.Poll] = dataclasses.field(default_factory=lambda: Nothing())
-    video: Option[telegrinder.types.Video] = dataclasses.field(default_factory=lambda: Nothing())
 
     @classmethod
     async def compose(cls, message: MessageNode) -> "Attachment":
         for attachment_type in ("audio", "document", "photo", "poll", "video"):
-            if (attachment := getattr(message, attachment_type, None)) is not None:
-                return cls(attachment_type, **{attachment_type: attachment})
+            match getattr(message, attachment_type, Nothing()):
+                case Some(attachment):
+                    return cls(attachment_type, **{attachment_type: Some(attachment)})
         return cls.compose_error("No attachment found in message")
 
 
diff --git a/telegrinder/node/base.py b/telegrinder/node/base.py
index 17a9e74d..aef666d2 100755
--- a/telegrinder/node/base.py
+++ b/telegrinder/node/base.py
@@ -2,8 +2,14 @@
 import inspect
 import typing
 
+from telegrinder.tools.magic import get_annotations
+
+from .scope import NodeScope
+
 ComposeResult: typing.TypeAlias = (
-    typing.Coroutine[typing.Any, typing.Any, typing.Any] | typing.AsyncGenerator[typing.Any, None]
+    typing.Coroutine[typing.Any, typing.Any, typing.Any]
+    | typing.AsyncGenerator[typing.Any, None]
+    | typing.Any
 )
 
 
@@ -12,10 +18,11 @@ class ComposeError(BaseException): ...
 
 class Node(abc.ABC):
     node: str = "node"
+    scope: NodeScope = NodeScope.PER_EVENT
 
     @classmethod
     @abc.abstractmethod
-    def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
+    def compose(cls, *args, **kwargs) -> ComposeResult:
         pass
 
     @classmethod
@@ -24,15 +31,7 @@ def compose_error(cls, error: str | None = None) -> typing.NoReturn:
 
     @classmethod
     def get_sub_nodes(cls) -> dict[str, type[typing.Self]]:
-        parameters = inspect.signature(cls.compose).parameters
-
-        sub_nodes = {}
-        for name, param in parameters.items():
-            if param.annotation is inspect._empty:
-                continue
-            node = param.annotation
-            sub_nodes[name] = node
-        return sub_nodes
+        return get_annotations(cls.compose)
 
     @classmethod
     def as_node(cls) -> type[typing.Self]:
@@ -49,14 +48,14 @@ class DataNode(Node, abc.ABC):
     @typing.dataclass_transform()
     @classmethod
     @abc.abstractmethod
-    async def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
+    async def compose(cls, *args, **kwargs) -> ComposeResult:
         pass
 
 
 class ScalarNodeProto(Node, abc.ABC):
     @classmethod
     @abc.abstractmethod
-    async def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
+    async def compose(cls, *args, **kwargs) -> ComposeResult:
         pass
 
 
@@ -78,17 +77,31 @@ def create_class(name, bases, dct):
         return type(
             "Scalar",
             (SCALAR_NODE,),
-            {"as_node": classmethod(lambda cls: create_node(cls, bases, dct))},
+            {
+                "as_node": classmethod(lambda cls: create_node(cls, bases, dct)),
+                "scope": Node.scope,
+            },
         )
 
     class ScalarNode(ScalarNodeProto, abc.ABC, metaclass=create_class):
         pass
 
 
+def is_node(maybe_node: type[typing.Any]) -> typing.TypeGuard[type[Node]]:
+    maybe_node = typing.get_origin(maybe_node) or maybe_node
+    return (
+        isinstance(maybe_node, type)
+        and issubclass(maybe_node, Node)
+        or isinstance(maybe_node, Node)
+        or hasattr(maybe_node, "as_node")
+    )
+
+
 __all__ = (
     "ComposeError",
     "DataNode",
     "Node",
     "SCALAR_NODE",
     "ScalarNode",
+    "is_node",
 )
diff --git a/telegrinder/node/callback_query.py b/telegrinder/node/callback_query.py
new file mode 100644
index 00000000..fa8a65cb
--- /dev/null
+++ b/telegrinder/node/callback_query.py
@@ -0,0 +1,18 @@
+from telegrinder.bot.cute_types import CallbackQueryCute
+
+from .base import ComposeError, ScalarNode
+from .update import UpdateNode
+
+
+class CallbackQueryNode(ScalarNode, CallbackQueryCute):
+    @classmethod
+    async def compose(cls, update: UpdateNode) -> CallbackQueryCute:
+        if not update.callback_query:
+            raise ComposeError
+        return CallbackQueryCute(
+            **update.callback_query.unwrap().to_dict(),
+            api=update.api,
+        )
+
+
+__all__ = ("CallbackQueryNode",)
diff --git a/telegrinder/node/command.py b/telegrinder/node/command.py
new file mode 100644
index 00000000..7ef4c6ab
--- /dev/null
+++ b/telegrinder/node/command.py
@@ -0,0 +1,29 @@
+from dataclasses import dataclass, field
+
+from fntypes import Nothing, Option, Some
+
+from .base import DataNode
+from .text import Text
+
+
+def single_split(s: str, separator: str) -> tuple[str, str]:
+    left, *right = s.split(separator, 1)
+    return left, (right[0] if right else "")
+
+
+def cut_mention(text: str) -> tuple[str, Option[str]]:
+    left, right = single_split(text, "@")
+    return left, Some(right) if right else Nothing()
+
+
+@dataclass
+class CommandInfo(DataNode):
+    name: str
+    arguments: str
+    mention: Option[str] = field(default_factory=Nothing)
+
+    @classmethod
+    async def compose(cls, text: Text):
+        name, arguments = single_split(text, separator=" ")
+        name, mention = cut_mention(name)
+        return cls(name, arguments, mention)
diff --git a/telegrinder/node/composer.py b/telegrinder/node/composer.py
index b28e26e6..aa9aba05 100755
--- a/telegrinder/node/composer.py
+++ b/telegrinder/node/composer.py
@@ -1,23 +1,112 @@
 import typing
 
+from telegrinder.api.api import API
 from telegrinder.bot.cute_types import UpdateCute
-from telegrinder.node import Node
+from telegrinder.bot.dispatch.context import Context
+from telegrinder.node.base import ComposeError, Node
+from telegrinder.node.scope import NodeScope
+from telegrinder.tools.magic import get_annotations, magic_bundle
+
+CONTEXT_STORE_NODES_KEY = "node_ctx"
+
+
+async def compose_node(
+    _node: type[Node],
+    update: UpdateCute,
+    ctx: Context,
+) -> "NodeSession":
+    node = _node.as_node()
+    context = NodeCollection({})
+    node_ctx: dict[type[Node], "NodeSession"] = ctx.get_or_set(CONTEXT_STORE_NODES_KEY, {})
+
+    for name, subnode in node.get_sub_nodes().items():
+        if subnode in node_ctx:
+            context.sessions[name] = node_ctx[subnode]
+        elif subnode is UpdateCute:
+            context.sessions[name] = NodeSession(None, update, {})
+        elif subnode is API:
+            context.sessions[name] = NodeSession(None, update.ctx_api, {})
+        elif subnode is Context:
+            context.sessions[name] = NodeSession(None, ctx, {})
+        else:
+            context.sessions[name] = await compose_node(subnode, update, ctx)
+
+            if getattr(subnode, "scope", None) is NodeScope.PER_EVENT:
+                node_ctx[subnode] = context.sessions[name]
+
+    generator: typing.AsyncGenerator | None
+
+    if node.is_generator():
+        generator = typing.cast(typing.AsyncGenerator, node.compose(**context.values()))
+        value = await generator.asend(None)
+    else:
+        generator = None
+        value = await node.compose(**context.values())  # type: ignore
+
+    return NodeSession(_node, value, context.sessions, generator)
+
+
+async def compose_nodes(
+    node_types: dict[str, type[Node]],
+    update: UpdateCute,
+    ctx: Context,
+) -> typing.Optional["NodeCollection"]:
+    nodes: dict[str, NodeSession] = {}
+    node_ctx: dict[type[Node], "NodeSession"] = ctx.get_or_set(CONTEXT_STORE_NODES_KEY, {})
+
+    for name, node_t in node_types.items():
+        scope = getattr(node_t, "scope", None)
+        try:
+            if scope is NodeScope.PER_EVENT and node_t in node_ctx:
+                nodes[name] = node_ctx[node_t]
+                continue
+            elif scope is NodeScope.GLOBAL and hasattr(node_t, "_value"):
+                nodes[name] = getattr(node_t, "_value")
+                continue
+
+            nodes[name] = await compose_node(
+                node_t,
+                update,
+                ctx,
+            )
+
+            if scope is NodeScope.PER_EVENT:
+                node_ctx[node_t] = nodes[name]
+            elif scope is NodeScope.GLOBAL:
+                setattr(node_t, "_value", nodes[name])
+
+        except ComposeError:
+            await NodeCollection(nodes).close_all()
+            return None
+    return NodeCollection(nodes)
 
 
 class NodeSession:
     def __init__(
         self,
+        node_type: type[Node] | None,
         value: typing.Any,
         subnodes: dict[str, typing.Self],
         generator: typing.AsyncGenerator[typing.Any, None] | None = None,
     ):
+        self.node_type = node_type
         self.value = value
         self.subnodes = subnodes
         self.generator = generator
 
-    async def close(self, with_value: typing.Any | None = None) -> None:
+    def __repr__(self) -> str:
+        return f"<{self.__class__.__name__}: {self.value}" + ("ACTIVE>" if self.generator else ">")
+
+    async def close(
+        self,
+        with_value: typing.Any | None = None,
+        scopes: tuple[NodeScope, ...] = (NodeScope.PER_CALL,),
+    ) -> None:
+        if self.node_type and getattr(self.node_type, "scope", None) not in scopes:
+            return
+
         for subnode in self.subnodes.values():
-            await subnode.close()
+            await subnode.close(scopes=scopes)
 
         if self.generator is None:
             return
@@ -26,46 +115,46 @@ async def close(self, with_value: typing.Any | None = None) -> None:
         except StopAsyncIteration:
             self.generator = None
 
-    def __repr__(self) -> str:
-        return f"<{self.__class__.__name__}: {self.value}" + ("ACTIVE>" if self.generator else ">")
-
 
 class NodeCollection:
     def __init__(self, sessions: dict[str, NodeSession]) -> None:
         self.sessions = sessions
 
+    def __repr__(self) -> str:
+        return "<{}: sessions={}>".format(self.__class__.__name__, self.sessions)
+
     def values(self) -> dict[str, typing.Any]:
         return {name: session.value for name, session in self.sessions.items()}
 
-    async def close_all(self, with_value: typing.Any | None = None) -> None:
+    async def close_all(
+        self,
+        with_value: typing.Any | None = None,
+        scopes: tuple[NodeScope, ...] = (NodeScope.PER_CALL,),
+    ) -> None:
         for session in self.sessions.values():
-            await session.close(with_value)
+            await session.close(with_value, scopes=scopes)
 
 
-async def compose_node(
-    node: type[Node],
-    update: UpdateCute,
-    ready_context: dict[str, NodeSession] | None = None,
-) -> NodeSession:
-    _node = node.as_node()
-    context = NodeCollection(ready_context.copy() if ready_context else {})
-
-    for name, subnode in _node.get_sub_nodes().items():
-        if subnode is UpdateCute:
-            context.sessions[name] = NodeSession(update, {})
-        else:
-            context.sessions[name] = await compose_node(subnode, update)
+class Composition:
+    nodes: dict[str, type[Node]]
 
-    generator: typing.AsyncGenerator | None
+    def __init__(self, func: typing.Callable[..., typing.Any], is_blocking: bool) -> None:
+        self.func = func
+        self.nodes = get_annotations(func)
+        self.is_blocking = is_blocking
 
-    if _node.is_generator():
-        generator = typing.cast(typing.AsyncGenerator, _node.compose(**context.values()))
-        value = await generator.asend(None)
-    else:
-        generator = None
-        value = await _node.compose(**context.values())  # type: ignore
+    def __repr__(self) -> str:
+        return "<{}: for function={!r} with nodes={}>".format(
+            ("blocking " if self.is_blocking else "") + self.__class__.__name__,
+            self.func.__name__,
+            self.nodes,
+        )
+
+    async def compose_nodes(self, update: UpdateCute, context: Context) -> NodeCollection | None:
+        return await compose_nodes(self.nodes, update, context)
 
-    return NodeSession(value, context.sessions, generator)
+    async def __call__(self, **kwargs: typing.Any) -> typing.Any:
+        return await self.func(**magic_bundle(self.func, kwargs, start_idx=0, bundle_ctx=False))  # type: ignore
 
 
-__all__ = ("NodeCollection", "NodeSession", "compose_node")
+__all__ = ("NodeCollection", "NodeSession", "Composition", "compose_node", "compose_nodes")
diff --git a/telegrinder/node/me.py b/telegrinder/node/me.py
new file mode 100644
index 00000000..6d432d12
--- /dev/null
+++ b/telegrinder/node/me.py
@@ -0,0 +1,14 @@
+from telegrinder.api.api import API
+from telegrinder.types import User
+
+from .base import ComposeError, ScalarNode
+from .scope import GLOBAL
+
+
+class Me(ScalarNode, User):
+    scope = GLOBAL
+
+    @classmethod
+    async def compose(cls, api: API) -> User:
+        me = await api.get_me()
+        return me.expect(ComposeError("Can't complete get_me request"))
diff --git a/telegrinder/node/message.py b/telegrinder/node/message.py
index 7b445048..d1f73311 100755
--- a/telegrinder/node/message.py
+++ b/telegrinder/node/message.py
@@ -1,5 +1,3 @@
-import typing
-
 from telegrinder.bot.cute_types import MessageCute
 
 from .base import ComposeError, ScalarNode
@@ -8,10 +6,10 @@
 
 class MessageNode(ScalarNode, MessageCute):
     @classmethod
-    async def compose(cls, update: UpdateNode) -> typing.Self:
+    async def compose(cls, update: UpdateNode) -> "MessageNode":
         if not update.message:
             raise ComposeError
-        return cls(
+        return MessageNode(
             **update.message.unwrap().to_dict(),
             api=update.api,
         )
diff --git a/telegrinder/node/polymorphic.py b/telegrinder/node/polymorphic.py
new file mode 100644
index 00000000..874bba2a
--- /dev/null
+++ b/telegrinder/node/polymorphic.py
@@ -0,0 +1,44 @@
+import inspect
+import typing
+
+from telegrinder.bot.dispatch.context import Context
+from telegrinder.tools.magic import get_impls, impl
+
+from .base import ComposeError
+from .composer import CONTEXT_STORE_NODES_KEY, Composition, NodeSession
+from .scope import NodeScope
+from .update import UpdateNode
+
+
+class Polymorphic:
+    @classmethod
+    async def compose(cls, update: UpdateNode, context: Context) -> typing.Any:
+        scope = getattr(cls, "scope", None)
+        node_ctx = context.get_or_set(CONTEXT_STORE_NODES_KEY, {})
+
+        for i, impl in enumerate(get_impls(cls)):
+            composition = Composition(impl, True)
+            node_collection = await composition.compose_nodes(update, context)
+            if node_collection is None:
+                continue
+
+            # To determine whether this is a right morph, all subnodes must be resolved
+            if scope is NodeScope.PER_EVENT and (cls, i) in node_ctx:
+                result: NodeSession = node_ctx[(cls, i)]
+                await node_collection.close_all()
+                return result.value
+
+            result = composition.func(cls, **node_collection.values())
+            if inspect.isawaitable(result):
+                result = await result
+
+            if scope is NodeScope.PER_EVENT:
+                node_ctx[(cls, i)] = NodeSession(cls, result, {})  # type: ignore
+
+            await node_collection.close_all(with_value=result)
+            return result
+
+        raise ComposeError("No implementation found.")
+
+
+__all__ = ("Polymorphic", "impl")
diff --git a/telegrinder/node/rule.py b/telegrinder/node/rule.py
index 5c5b4bdb..9e675a26 100755
--- a/telegrinder/node/rule.py
+++ b/telegrinder/node/rule.py
@@ -2,18 +2,39 @@
 import typing
 
 from telegrinder.bot.dispatch.context import Context
-from telegrinder.bot.dispatch.process import check_rule
-from telegrinder.bot.rules.abc import ABCRule
 from telegrinder.node.base import ComposeError, Node
 from telegrinder.node.update import UpdateNode
 
+if typing.TYPE_CHECKING:
+    from telegrinder.bot.rules.abc import ABCRule
 
-class RuleContext(dict):
+
+class RuleChain(dict[str, typing.Any]):
     dataclass = dict
-    rules: tuple[ABCRule, ...] = ()
+    rules: tuple["ABCRule", ...] = ()
+
+    def __init_subclass__(cls) -> None:
+        if cls.__name__ == "_RuleNode":
+            return
+        cls.dataclass = cls.generate_node_dataclass(cls)
+
+    def __new__(cls, *rules: "ABCRule") -> type[Node]:
+        return type("_RuleNode", (cls,), {"dataclass": dict, "rules": rules})  # type: ignore
+
+    def __class_getitem__(cls, items: typing.Union[tuple["ABCRule", ...], "ABCRule"]) -> typing.Self:
+        if not isinstance(items, tuple):
+            items = (items,)
+        assert all(isinstance(rule, "ABCRule") for rule in items), "All items must be instances of 'ABCRule'."
+        return cls(*items)
+
+    @staticmethod
+    def generate_node_dataclass(cls_: type["RuleChain"]):  # noqa: ANN205
+        return dataclasses.dataclass(type(cls_.__name__, (object,), dict(cls_.__dict__)))
 
     @classmethod
     async def compose(cls, update: UpdateNode):
+        from telegrinder.bot.dispatch.process import check_rule
+
         ctx = Context()
         for rule in cls.rules:
             if not await check_rule(update.api, rule, update, ctx):
@@ -35,22 +56,5 @@ def get_sub_nodes(cls) -> dict:
     def is_generator(cls) -> typing.Literal[False]:
         return False
 
-    def __new__(cls, *rules: ABCRule) -> type[Node]:
-        return type("_RuleNode", (cls,), {"dataclass": dict, "rules": rules})  # type: ignore
-
-    def __class_getitem__(cls, item: tuple[ABCRule, ...]) -> typing.Self:
-        if not isinstance(item, tuple):
-            item = (item,)
-        return cls(*item)
-
-    @staticmethod
-    def generate_dataclass(cls_: type["RuleContext"]):  # noqa: ANN205
-        return dataclasses.dataclass(type(cls_.__name__, (object,), dict(cls_.__dict__)))
-
-    def __init_subclass__(cls) -> None:
-        if cls.__name__ == "_RuleNode":
-            return
-        cls.dataclass = cls.generate_dataclass(cls)
-
 
-__all__ = ("RuleContext",)
+__all__ = ("RuleChain",)
diff --git a/telegrinder/node/scope.py b/telegrinder/node/scope.py
new file mode 100644
index 00000000..da69858e
--- /dev/null
+++ b/telegrinder/node/scope.py
@@ -0,0 +1,36 @@
+import enum
+import typing
+
+if typing.TYPE_CHECKING:
+    from .base import Node
+
+T = typing.TypeVar("T", bound=type["Node"])
+
+
+class NodeScope(enum.Enum):
+    GLOBAL = enum.auto()
+    PER_EVENT = enum.auto()
+    PER_CALL = enum.auto()
+
+
+PER_EVENT = NodeScope.PER_EVENT
+PER_CALL = NodeScope.PER_CALL
+GLOBAL = NodeScope.GLOBAL
+
+
+def per_call(node: T) -> T:
+    setattr(node, "scope", PER_CALL)
+    return node
+
+
+def per_event(node: T) -> T:
+    setattr(node, "scope", PER_EVENT)
+    return node
+
+
+def global_node(node: T) -> T:
+    setattr(node, "scope", GLOBAL)
+    return node
+
+
+__all__ = ("NodeScope", "PER_EVENT", "PER_CALL", "per_call", "per_event", "global_node", "GLOBAL")
diff --git a/telegrinder/node/source.py b/telegrinder/node/source.py
index 1b8478bb..d241101a 100755
--- a/telegrinder/node/source.py
+++ b/telegrinder/node/source.py
@@ -1,34 +1,61 @@
 import dataclasses
 import typing
 
+from fntypes import Nothing, Option
+
 from telegrinder.api import API
-from telegrinder.types import Chat, Message
+from telegrinder.types import Chat, Message, User
 
-from .base import DataNode
+from .base import ComposeError, DataNode, ScalarNode
+from .callback_query import CallbackQueryNode
 from .message import MessageNode
+from .polymorphic import Polymorphic, impl
 
 
-@dataclasses.dataclass
-class Source(DataNode):
+@dataclasses.dataclass(kw_only=True)
+class Source(Polymorphic, DataNode):
     api: API
     chat: Chat
-    thread_id: int | None = None
+    from_user: User
+    thread_id: Option[int] = dataclasses.field(default_factory=lambda: Nothing())
 
-    @classmethod
-    async def compose(cls, message: MessageNode) -> typing.Self:
+    @impl
+    async def compose_message(cls, message: MessageNode) -> typing.Self:
         return cls(
             api=message.ctx_api,
             chat=message.chat,
-            thread_id=message.message_thread_id.unwrap_or_none(),
+            from_user=message.from_user,
+            thread_id=message.message_thread_id,
+        )
+
+    @impl
+    async def compose_callback_query(cls, callback_query: CallbackQueryNode) -> typing.Self:
+        return cls(
+            api=callback_query.ctx_api,
+            chat=callback_query.chat.expect(ComposeError),
+            from_user=callback_query.from_user,
+            thread_id=callback_query.message_thread_id,
         )
 
     async def send(self, text: str) -> Message:
         result = await self.api.send_message(
             chat_id=self.chat.id,
-            message_thread_id=self.thread_id,
+            message_thread_id=self.thread_id.unwrap_or_none(),
             text=text,
         )
         return result.unwrap()
 
 
-__all__ = ("Source",)
+class ChatSource(ScalarNode, Chat):
+    @classmethod
+    async def compose(cls, source: Source) -> Chat:
+        return source.chat
+
+
+class UserSource(ScalarNode, User):
+    @classmethod
+    async def compose(cls, source: Source) -> User:
+        return source.from_user
+
+
+__all__ = ("Source", "ChatSource", "UserSource")
diff --git a/telegrinder/node/text.py b/telegrinder/node/text.py
index 47e652f7..db1026f0 100755
--- a/telegrinder/node/text.py
+++ b/telegrinder/node/text.py
@@ -1,15 +1,21 @@
-import typing
-
 from .base import ComposeError, ScalarNode
 from .message import MessageNode
 
 
 class Text(ScalarNode, str):
     @classmethod
-    async def compose(cls, message: MessageNode) -> typing.Self:
+    async def compose(cls, message: MessageNode) -> str:
         if not message.text:
             raise ComposeError("Message has no text")
-        return cls(message.text.unwrap())
+        return message.text.unwrap()
+
+
+class TextInteger(ScalarNode, int):
+    @classmethod
+    async def compose(cls, text: Text) -> int:
+        if not text.isdigit():
+            raise ComposeError("Text is not digit")
+        return int(text)
 
 
-__all__ = ("Text",)
+__all__ = ("Text", "TextInteger")
diff --git a/telegrinder/node/tools/__init__.py b/telegrinder/node/tools/__init__.py
index a8ec5504..e7d9a4f4 100755
--- a/telegrinder/node/tools/__init__.py
+++ b/telegrinder/node/tools/__init__.py
@@ -1,3 +1,3 @@
-from .generator import generate
+from .generator import generate_node
 
-__all__ = ("generate",)
+__all__ = ("generate_node",)
diff --git a/telegrinder/node/tools/generator.py b/telegrinder/node/tools/generator.py
index 31754e0d..a2fc0e34 100755
--- a/telegrinder/node/tools/generator.py
+++ b/telegrinder/node/tools/generator.py
@@ -19,11 +19,11 @@ def error_on_none(value: T | None) -> T:
     return value
 
 
-def generate(
-    subnodes: tuple[type[Node], ...],
-    func: typing.Callable[..., typing.Any],
-    casts: tuple[typing.Callable, ...] = (cast_false_to_none, error_on_none),
-) -> type[ContainerNode]:
+def generate_node(
+    subnodes: tuple[type["Node"], ...],
+    func: typing.Callable[..., T],
+    casts: tuple[typing.Callable[[typing.Any], typing.Any], ...] = (cast_false_to_none, error_on_none),
+) -> type["Node"]:
     async def compose(**kw: typing.Any) -> typing.Any:
         args = await ContainerNode.compose(**kw)
         result = func(*args)
@@ -37,4 +37,4 @@ async def compose(**kw: typing.Any) -> typing.Any:
     return type("_ContainerNode", (container,), {"compose": compose})
 
 
-__all__ = ("generate",)
+__all__ = ("generate_node",)
diff --git a/telegrinder/tools/__init__.py b/telegrinder/tools/__init__.py
index 2ba1b705..1f0dbb39 100755
--- a/telegrinder/tools/__init__.py
+++ b/telegrinder/tools/__init__.py
@@ -14,7 +14,6 @@
     StartBotLink,
     StartGroupLink,
     TgEmoji,
-    UserOpenMessage,
     block_quote,
     bold,
     channel_boost_link,
@@ -38,8 +37,6 @@
     strike,
     tg_emoji,
     underline,
-    user_open_message,
-    user_open_message_link,
 )
 from .global_context import (
     ABCGlobalContext,
@@ -68,7 +65,7 @@
 )
 from .limited_dict import LimitedDict
 from .loop_wrapper import ABCLoopWrapper, DelayedTask, Lifespan, LoopWrapper
-from .magic import magic_bundle, resolve_arg_names
+from .magic import impl, magic_bundle, resolve_arg_names
 from .parse_mode import ParseMode
 
 __all__ = (
@@ -115,12 +112,6 @@
     "StartGroupLink",
     "TelegrinderCtx",
     "TgEmoji",
-    "UserOpenMessage",
-    "block_quote",
-    "bold",
-    "channel_boost_link",
-    "code_inline",
-    "ctx_var",
     "escape",
     "get_channel_boost_link",
     "get_invite_chat_link",
@@ -134,7 +125,6 @@
     "magic_bundle",
     "mention",
     "pre_code",
-    "resolve_arg_names",
     "resolve_domain",
     "spoiler",
     "start_bot_link",
@@ -142,6 +132,11 @@
     "strike",
     "tg_emoji",
     "underline",
-    "user_open_message",
-    "user_open_message_link",
+    "bold",
+    "channel_boost_link",
+    "code_inline",
+    "ctx_var",
+    "block_quote",
+    "impl",
+    "resolve_arg_names",
 )
diff --git a/telegrinder/tools/buttons.py b/telegrinder/tools/buttons.py
index 03888d22..bab235fe 100755
--- a/telegrinder/tools/buttons.py
+++ b/telegrinder/tools/buttons.py
@@ -47,30 +47,36 @@ def get_data(self) -> list[dict[str, typing.Any]]:
 @dataclasses.dataclass
 class Button(BaseButton):
     text: str
-    _: dataclasses.KW_ONLY
-    request_contact: bool = False
-    request_location: bool = False
-    request_chat: dict[str, typing.Any] | KeyboardButtonRequestChat | None = None
-    request_user: dict[str, typing.Any] | KeyboardButtonRequestUsers | None = None
-    request_poll: dict[str, typing.Any] | KeyboardButtonPollType | None = None
-    web_app: dict[str, typing.Any] | WebAppInfo | None = None
+    request_contact: bool = dataclasses.field(default=False, kw_only=True)
+    request_location: bool = dataclasses.field(default=False, kw_only=True)
+    request_chat: dict[str, typing.Any] | KeyboardButtonRequestChat | None = dataclasses.field(
+        default=None, kw_only=True
+    )
+    request_user: dict[str, typing.Any] | KeyboardButtonRequestUsers | None = dataclasses.field(
+        default=None, kw_only=True
+    )
+    request_poll: dict[str, typing.Any] | KeyboardButtonPollType | None = dataclasses.field(
+        default=None, kw_only=True
+    )
+    web_app: dict[str, typing.Any] | WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
 
 
 @dataclasses.dataclass
 class InlineButton(BaseButton):
     text: str
-    _: dataclasses.KW_ONLY
-    url: str | None = None
-    login_url: dict[str, typing.Any] | LoginUrl | None = None
-    pay: bool | None = None
-    callback_data: str | dict[str, typing.Any] | DataclassInstance | msgspec.Struct | None = None
-    callback_game: dict[str, typing.Any] | CallbackGame | None = None
-    switch_inline_query: str | None = None
-    switch_inline_query_current_chat: str | None = None
+    url: str | None = dataclasses.field(default=None, kw_only=True)
+    login_url: dict[str, typing.Any] | LoginUrl | None = dataclasses.field(default=None, kw_only=True)
+    pay: bool | None = dataclasses.field(default=None, kw_only=True)
+    callback_data: str | dict[str, typing.Any] | DataclassInstance | msgspec.Struct | None = (
+        dataclasses.field(default=None, kw_only=True)
+    )
+    callback_game: dict[str, typing.Any] | CallbackGame | None = dataclasses.field(default=None, kw_only=True)
+    switch_inline_query: str | None = dataclasses.field(default=None, kw_only=True)
+    switch_inline_query_current_chat: str | None = dataclasses.field(default=None, kw_only=True)
     switch_inline_query_chosen_chat: dict[str, typing.Any] | SwitchInlineQueryChosenChat | None = (
-        None
+        dataclasses.field(default=None, kw_only=True)
     )
-    web_app: dict[str, typing.Any] | WebAppInfo | None = None
+    web_app: dict[str, typing.Any] | WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
 
 
 __all__ = (
diff --git a/telegrinder/tools/error_handler/error_handler.py b/telegrinder/tools/error_handler/error_handler.py
index c36155d6..2dcc26b6 100755
--- a/telegrinder/tools/error_handler/error_handler.py
+++ b/telegrinder/tools/error_handler/error_handler.py
@@ -19,13 +19,13 @@
 @dataclasses.dataclass(frozen=True, repr=False)
 class Catcher(typing.Generic[EventT]):
     func: FuncCatcher[BaseException]
-    _: dataclasses.KW_ONLY
     exceptions: list[type[BaseException] | BaseException] = dataclasses.field(
         default_factory=lambda: [],
+        kw_only=True,
     )
-    logging: bool = dataclasses.field(default=False)
-    raise_exception: bool = dataclasses.field(default=False)
-    ignore_errors: bool = dataclasses.field(default=False)
+    logging: bool = dataclasses.field(default=False, kw_only=True)
+    raise_exception: bool = dataclasses.field(default=False, kw_only=True)
+    ignore_errors: bool = dataclasses.field(default=False, kw_only=True)
 
     def __repr__(self) -> str:
         return "<Catcher: function={!r}, logging={}, raise_exception={}, ignore_errors={}>".format(
@@ -57,9 +57,8 @@ async def process_exception(
     ) -> Result[typing.Any, BaseException]:
         if self.match_exception(exception):
             logger.debug(
-                "Catcher {!r} caught an exception in handler {!r}, " "running catcher...".format(
-                    self.func.__name__,
-                    handler_name,
+                "Error handler caught an exception {!r} in handler {!r}, running catcher {!r}...".format(
+                    exception, handler_name, self.func.__name__
                 )
             )
             return Ok(
@@ -68,7 +67,7 @@ async def process_exception(
                     **magic_bundle(self.func, {"event": event, "api": api} | ctx),  # type: ignore
                 )
             )
-        logger.debug("Failed to match exception {!r}!", exception.__class__.__name__)
+        logger.debug("Failed to match exception {!r}.", exception.__class__.__name__)
         return Error(exception)
 
     def match_exception(self, exception: BaseException) -> bool:
@@ -88,14 +87,11 @@ def __repr__(self) -> str:
         return (
             "<{}: exceptions_handled=[{}], catcher={!r}>".format(
                 self.__class__.__name__,
-                ", ".join(
-                    e.__name__ if isinstance(e, type) else repr(e)
-                    for e in self.catcher.exceptions
-                ),
+                ", ".join(e.__name__ if isinstance(e, type) else repr(e) for e in self.catcher.exceptions),
                 self.catcher,
             )
             if self.catcher is not None
-            else "<{}: No catcher>".format(self.__class__.__name__)
+            else "<{}()>".format(self.__class__.__name__)
         )
 
     def __call__(
@@ -133,6 +129,7 @@ async def process(
         ctx: Context,
     ) -> Result[typing.Any, BaseException]:
         assert self.catcher is not None
+        logger.debug("Processing the error handler for handler {!r}...", handler.__name__)
 
         try:
             return await self.catcher(handler, event, api, ctx)
@@ -172,7 +169,7 @@ async def run(
             case Ok(value) as ok:
                 if self.catcher.logging:
                     logger.debug(
-                        "Catcher {!r} returned a value: {!r}",
+                        "Catcher {!r} returned: {!r}",
                         self.catcher.func.__name__,
                         value,
                     )
diff --git a/telegrinder/tools/formatting/__init__.py b/telegrinder/tools/formatting/__init__.py
index 3225ed2b..564874bd 100755
--- a/telegrinder/tools/formatting/__init__.py
+++ b/telegrinder/tools/formatting/__init__.py
@@ -18,7 +18,6 @@
     strike,
     tg_emoji,
     underline,
-    user_open_message,
 )
 from .links import (
     get_channel_boost_link,
@@ -27,7 +26,6 @@
     get_resolve_domain_link,
     get_start_bot_link,
     get_start_group_link,
-    user_open_message_link,
 )
 from .spec_html_formats import (
     BaseSpecFormat,
@@ -41,7 +39,6 @@
     StartBotLink,
     StartGroupLink,
     TgEmoji,
-    UserOpenMessage,
 )
 
 __all__ = (
@@ -58,7 +55,6 @@
     "StartBotLink",
     "StartGroupLink",
     "TgEmoji",
-    "UserOpenMessage",
     "block_quote",
     "bold",
     "channel_boost_link",
@@ -82,6 +78,4 @@
     "strike",
     "tg_emoji",
     "underline",
-    "user_open_message",
-    "user_open_message_link",
 )
diff --git a/telegrinder/tools/formatting/html.py b/telegrinder/tools/formatting/html.py
index c7d27440..a5307822 100755
--- a/telegrinder/tools/formatting/html.py
+++ b/telegrinder/tools/formatting/html.py
@@ -1,3 +1,5 @@
+# NOTE: NEED REFACTORING
+
 import html
 import string
 import typing
@@ -13,7 +15,6 @@
     get_resolve_domain_link,
     get_start_bot_link,
     get_start_group_link,
-    user_open_message_link,
 )
 from .spec_html_formats import SpecialFormat, is_spec_format
 
@@ -21,6 +22,12 @@
 QUOT_MARK = '"'
 
 
+class StringFormatterProto(typing.Protocol):
+    def format_field(self, value: typing.Any, fmt: str) -> "HTMLFormatter": ...
+
+    def format(self, __string: str, *args: object, **kwargs: object) -> "HTMLFormatter": ...
+
+
 class StringFormatter(string.Formatter):
     """String formatter, using substitutions from args and kwargs.
     The substitutions are identified by braces ('{' and '}') with
@@ -107,7 +114,7 @@ def format(self, __string: str, *args: object, **kwargs: object) -> "HTMLFormatt
 
 
 class FormatString(str):
-    string_formatter = StringFormatter()
+    STRING_FORMATTER: StringFormatterProto = StringFormatter()
 
     def __new__(cls, string: str) -> typing.Self:
         if isinstance(string, TagFormat):
@@ -131,7 +138,7 @@ def as_str(self) -> str:
         return self.__str__()
 
     def format(self, *args: object, **kwargs: object) -> "HTMLFormatter":
-        return self.string_formatter.format(self, *args, **kwargs)
+        return self.STRING_FORMATTER.format(self, *args, **kwargs)
 
 
 class EscapedString(FormatString):
@@ -273,14 +280,6 @@ def underline(string: str) -> TagFormat:
     return TagFormat(string, tag="u")
 
 
-def user_open_message(
-    user_id: int,
-    message: str | None = None,
-    string: str | None = None,
-) -> TagFormat:
-    return link(user_open_message_link(user_id, message), string)
-
-
 __all__ = (
     "FormatString",
     "HTMLFormatter",
@@ -308,5 +307,4 @@ def user_open_message(
     "strike",
     "tg_emoji",
     "underline",
-    "user_open_message",
 )
diff --git a/telegrinder/tools/formatting/links.py b/telegrinder/tools/formatting/links.py
index ab145a97..6fdc3aa5 100755
--- a/telegrinder/tools/formatting/links.py
+++ b/telegrinder/tools/formatting/links.py
@@ -28,10 +28,6 @@ def get_invite_chat_link(invite_link: str) -> str:
     return f"tg://join?invite={invite_link}"
 
 
-def user_open_message_link(user_id: int, message: str | None = None) -> str:
-    return f"tg://openmessage?user_id={user_id}" + ("" if not message else f"&msg?text={message}")
-
-
 __all__ = (
     "get_channel_boost_link",
     "get_invite_chat_link",
@@ -39,5 +35,4 @@ def user_open_message_link(user_id: int, message: str | None = None) -> str:
     "get_resolve_domain_link",
     "get_start_bot_link",
     "get_start_group_link",
-    "user_open_message_link",
 )
diff --git a/telegrinder/tools/formatting/spec_html_formats.py b/telegrinder/tools/formatting/spec_html_formats.py
index cf803369..5ee8b89c 100755
--- a/telegrinder/tools/formatting/spec_html_formats.py
+++ b/telegrinder/tools/formatting/spec_html_formats.py
@@ -13,7 +13,6 @@
     "StartBotLink",
     "StartGroupLink",
     "TgEmoji",
-    "UserOpenMessage",
 ]
 
 
@@ -107,15 +106,6 @@ class ResolveDomain(BaseSpecFormat):
     string: str | None = None
 
 
-@dataclasses.dataclass(repr=False)
-class UserOpenMessage(BaseSpecFormat):
-    __formatter_name__ = "user_open_message"
-
-    user_id: int
-    message: str | None = None
-    string: str | None = None
-
-
 __all__ = (
     "BaseSpecFormat",
     "ChannelBoostLink",
@@ -128,5 +118,4 @@ class UserOpenMessage(BaseSpecFormat):
     "StartBotLink",
     "StartGroupLink",
     "TgEmoji",
-    "UserOpenMessage",
 )
diff --git a/telegrinder/tools/global_context/abc.py b/telegrinder/tools/global_context/abc.py
index 3c50af6a..184336f5 100755
--- a/telegrinder/tools/global_context/abc.py
+++ b/telegrinder/tools/global_context/abc.py
@@ -35,9 +35,7 @@ def __repr__(self) -> str:
 
     @classmethod
     def collect(cls, name: str, ctx_value: T | CtxVariable[T]) -> typing.Self:
-        ctx_value = (
-            CtxVar(ctx_value) if not isinstance(ctx_value, CtxVar | GlobalCtxVar) else ctx_value
-        )
+        ctx_value = CtxVar(ctx_value) if not isinstance(ctx_value, CtxVar | GlobalCtxVar) else ctx_value
         params = ctx_value.__dict__
         params["name"] = name
         return cls(**params)
diff --git a/telegrinder/tools/global_context/global_context.py b/telegrinder/tools/global_context/global_context.py
index 40751da3..e7f84bc9 100755
--- a/telegrinder/tools/global_context/global_context.py
+++ b/telegrinder/tools/global_context/global_context.py
@@ -84,7 +84,6 @@ class MyCtx(GlobalContext):
 @dataclasses.dataclass(frozen=True, eq=False)
 class RootAttr:
     name: str
-    _: dataclasses.KW_ONLY
     can_be_read: bool = dataclasses.field(default=True, kw_only=True)
     can_be_rewritten: bool = dataclasses.field(default=False, kw_only=True)
 
@@ -124,9 +123,7 @@ def delete(self, ctx_name: str) -> None:
     order_default=True,
     field_specifiers=(ctx_var,),
 )
-class GlobalContext(
-    ABCGlobalContext, typing.Generic[CtxValueT], dict[str, GlobalCtxVar[CtxValueT]]
-):
+class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, GlobalCtxVar[CtxValueT]]):
     """GlobalContext.
 
     ```
@@ -315,10 +312,10 @@ def pop(
         var_value_type: type[T],
     ) -> Option[GlobalCtxVar[T]]: ...
 
-    def pop(self, var_name: str, var_value_type: type[T] = object) -> Option[GlobalCtxVar[T]]:
+    def pop(self, var_name: str, var_value_type=object):  # type: ignore
         """Pop context variable by name."""
 
-        val = self.get(var_name, var_value_type)
+        val = self.get(var_name, var_value_type)  # type: ignore
         if val:
             del self[var_name]
             return val
@@ -334,13 +331,10 @@ def get(
         var_value_type: type[T],
     ) -> Option[GlobalCtxVar[T]]: ...
 
-    def get(
-        self,
-        var_name: str,
-        var_value_type: type[T] = object,
-    ) -> Option[GlobalCtxVar[T]]:
+    def get(self, var_name, var_value_type=object):  # type: ignore
         """Get context variable by name."""
 
+        var_value_type = typing.Any if var_value_type is object else type
         generic_types = typing.get_args(get_orig_class(self))
         if generic_types and var_value_type is object:
             var_value_type = generic_types[0]
@@ -369,11 +363,7 @@ def get_value(
         var_value_type: type[T],
     ) -> Option[T]: ...
 
-    def get_value(
-        self,
-        var_name: str,
-        var_value_type: type[T] = object,
-    ) -> Option[T]:
+    def get_value(self, var_name, var_value_type=object):  # type: ignore
         """Get context variable value by name."""
 
         return self.get(var_name, var_value_type).map(lambda var: var.value)
diff --git a/telegrinder/tools/i18n/simple.py b/telegrinder/tools/i18n/simple.py
index 9b6ec7fe..7d65c1c9 100755
--- a/telegrinder/tools/i18n/simple.py
+++ b/telegrinder/tools/i18n/simple.py
@@ -29,9 +29,7 @@ def _load_translators(self) -> dict[str, gettext.GNUTranslations]:
         return result
 
     def get_translator_by_locale(self, locale: str) -> "SimpleTranslator":
-        return SimpleTranslator(
-            locale, self.translators.get(locale, self.translators[self.default_locale])
-        )
+        return SimpleTranslator(locale, self.translators.get(locale, self.translators[self.default_locale]))
 
 
 class SimpleTranslator(ABCTranslator):
diff --git a/telegrinder/tools/kb_set/yaml.py b/telegrinder/tools/kb_set/yaml.py
index b566c4cc..1801b3b8 100755
--- a/telegrinder/tools/kb_set/yaml.py
+++ b/telegrinder/tools/kb_set/yaml.py
@@ -44,8 +44,7 @@ def load(cls) -> None:
                 or not isinstance(kb_config["buttons"], list)
             ):
                 raise KeyboardSetError(
-                    "Keyboard should be dict with field buttons which must be a list, "
-                    "check documentation."
+                    "Keyboard should be dict with field buttons which must be a list, " "check documentation."
                 )
 
             buttons = kb_config.pop("buttons")
diff --git a/telegrinder/tools/keyboard.py b/telegrinder/tools/keyboard.py
index ba13372a..b2f109f9 100755
--- a/telegrinder/tools/keyboard.py
+++ b/telegrinder/tools/keyboard.py
@@ -5,7 +5,6 @@
 
 from fntypes.option import Nothing, Some
 
-from telegrinder.msgspec_utils import Option
 from telegrinder.types.objects import (
     InlineKeyboardMarkup,
     ReplyKeyboardMarkup,
@@ -18,12 +17,12 @@
 AnyMarkup: typing.TypeAlias = InlineKeyboardMarkup | ReplyKeyboardMarkup
 
 
-@dataclasses.dataclass
+@dataclasses.dataclass(kw_only=True)
 class KeyboardModel:
-    resize_keyboard: bool | Option[bool]
-    one_time_keyboard: bool | Option[bool]
-    selective: bool | Option[bool]
-    is_persistent: bool | Option[bool]
+    resize_keyboard: bool
+    one_time_keyboard: bool
+    selective: bool
+    is_persistent: bool
     keyboard: list[list[DictStrAny]]
 
 
@@ -40,7 +39,7 @@ def get_markup(self) -> AnyMarkup:
         pass
 
     @classmethod
-    def empty(cls) -> AnyMarkup:
+    def get_empty_markup(cls) -> AnyMarkup:
         return cls().get_markup()
 
     def add(self, row_or_button: RowButtons[ButtonT] | ButtonT) -> typing.Self:
@@ -102,7 +101,7 @@ def dict(self) -> DictStrAny:
     def get_markup(self) -> ReplyKeyboardMarkup:
         return ReplyKeyboardMarkup(**self.dict())
 
-    def get_empty_markup(self, *, selective: bool = False) -> ReplyKeyboardRemove:
+    def keyboard_remove(self, *, selective: bool = False) -> ReplyKeyboardRemove:
         return ReplyKeyboardRemove(remove_keyboard=True, selective=Some(selective))
 
 
diff --git a/telegrinder/tools/limited_dict.py b/telegrinder/tools/limited_dict.py
index ebabbbc6..eb557090 100644
--- a/telegrinder/tools/limited_dict.py
+++ b/telegrinder/tools/limited_dict.py
@@ -27,7 +27,7 @@ def set(self, key: KT, value: VT, /) -> VT | None:
 
     def __setitem__(self, key: KT, value: VT, /) -> None:
         self.set(key, value)
-    
+
     def __delitem__(self, key: KT) -> None:
         if key in self.queue:
             self.queue.remove(key)
diff --git a/telegrinder/tools/loop_wrapper/loop_wrapper.py b/telegrinder/tools/loop_wrapper/loop_wrapper.py
index 58a0c8b9..c534701f 100755
--- a/telegrinder/tools/loop_wrapper/loop_wrapper.py
+++ b/telegrinder/tools/loop_wrapper/loop_wrapper.py
@@ -48,7 +48,10 @@ async def __call__(self, *args, **kwargs) -> None:
             await asyncio.sleep(self.seconds)
             if self.is_cancelled:
                 break
-            await self.handler(*args, **kwargs)
+            try:
+                await self.handler(*args, **kwargs)
+            except Exception as e:
+                logger.exception("Error in delayed task: {}", str(e))
             if not self.repeat:
                 break
 
@@ -63,13 +66,11 @@ class Lifespan:
     shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
 
     def on_startup(self, task_or_func: Task) -> Task:
-        task_or_func = to_coroutine_task(task_or_func)
-        self.startup_tasks.append(task_or_func)
+        self.startup_tasks.append(to_coroutine_task(task_or_func))
         return task_or_func
 
     def on_shutdown(self, task_or_func: Task) -> Task:
-        task_or_func = to_coroutine_task(task_or_func)
-        self.shutdown_tasks.append(task_or_func)
+        self.shutdown_tasks.append(to_coroutine_task(task_or_func))
         return task_or_func
 
     def start(self, loop: asyncio.AbstractEventLoop) -> None:
diff --git a/telegrinder/tools/magic.py b/telegrinder/tools/magic.py
index 2c02575b..7ed2fa52 100755
--- a/telegrinder/tools/magic.py
+++ b/telegrinder/tools/magic.py
@@ -8,8 +8,11 @@
 
     T = typing.TypeVar("T", bound=ABCRule)
 
+Impl: typing.TypeAlias = type[classmethod]
 FuncType: typing.TypeAlias = types.FunctionType | typing.Callable[..., typing.Any]
+
 TRANSLATIONS_KEY: typing.Final[str] = "_translations"
+IMPL_MARK: typing.Final[str] = "_is_impl"
 
 
 def resolve_arg_names(func: FuncType, start_idx: int = 1) -> tuple[str, ...]:
@@ -21,6 +24,14 @@ def get_default_args(func: FuncType) -> dict[str, typing.Any]:
     return dict(zip(fspec.args[::-1], (fspec.defaults or ())[::-1]))
 
 
+def get_annotations(func: FuncType) -> dict[str, typing.Any]:
+    return {
+        name: parameter.annotation
+        for name, parameter in inspect.signature(func).parameters.items()
+        if parameter.annotation is not inspect._empty
+    }
+
+
 def to_str(s: str | enum.Enum) -> str:
     if isinstance(s, enum.Enum):
         return str(s.value)
@@ -42,11 +53,8 @@ def magic_bundle(
     return args
 
 
-def get_cached_translation(rule: "T", locale: str) -> typing.Optional["T"]:
-    translations = getattr(rule, TRANSLATIONS_KEY, {})
-    if not translations or locale not in translations:
-        return None
-    return translations[locale]
+def get_cached_translation(rule: "T", locale: str) -> "T | None":
+    return getattr(rule, TRANSLATIONS_KEY, {}).get(locale)
 
 
 def cache_translation(base_rule: "T", locale: str, translated_rule: "T") -> None:
@@ -54,6 +62,18 @@ def cache_translation(base_rule: "T", locale: str, translated_rule: "T") -> None
     setattr(base_rule, TRANSLATIONS_KEY, {locale: translated_rule, **translations})
 
 
+def get_impls(cls: type[typing.Any]) -> list[typing.Callable[..., typing.Any]]:
+    functions = [func.__func__ for func in vars(cls).values() if isinstance(func, classmethod)]
+    return [impl for impl in functions if getattr(impl, IMPL_MARK, False) is True]
+
+
+@typing.cast(typing.Callable[..., Impl], lambda f: f)
+def impl(method: typing.Callable[..., typing.Any]):
+    bound_method = classmethod(method)
+    setattr(method, IMPL_MARK, True)
+    return bound_method
+
+
 __all__ = (
     "TRANSLATIONS_KEY",
     "cache_translation",
@@ -61,6 +81,8 @@ def cache_translation(base_rule: "T", locale: str, translated_rule: "T") -> None
     "get_default_args",
     "get_default_args",
     "magic_bundle",
+    "impl",
     "resolve_arg_names",
     "to_str",
+    "get_annotations",
 )
diff --git a/telegrinder/types/__init__.py b/telegrinder/types/__init__.py
index b37d8eb7..ef8f6b00 100755
--- a/telegrinder/types/__init__.py
+++ b/telegrinder/types/__init__.py
@@ -136,6 +136,9 @@
     "InputMediaPhoto",
     "InputMediaVideo",
     "InputMessageContent",
+    "InputPaidMedia",
+    "InputPaidMediaPhoto",
+    "InputPaidMediaVideo",
     "InputPollOption",
     "InputSticker",
     "InputTextMessageContent",
@@ -171,6 +174,11 @@
     "MessageReactionUpdated",
     "Model",
     "OrderInfo",
+    "PaidMedia",
+    "PaidMediaInfo",
+    "PaidMediaPhoto",
+    "PaidMediaPreview",
+    "PaidMediaVideo",
     "PassportData",
     "PassportElementError",
     "PassportElementErrorDataField",
@@ -198,15 +206,22 @@
     "ReactionTypeCustomEmoji",
     "ReactionTypeEmoji",
     "ReactionTypeType",
+    "RefundedPayment",
     "ReplyKeyboardMarkup",
     "ReplyKeyboardRemove",
     "ReplyParameters",
     "ResponseParameters",
+    "RevenueWithdrawalState",
+    "RevenueWithdrawalStateFailed",
+    "RevenueWithdrawalStatePending",
+    "RevenueWithdrawalStateSucceeded",
     "SentWebAppMessage",
     "SharedUser",
     "ShippingAddress",
     "ShippingOption",
     "ShippingQuery",
+    "StarTransaction",
+    "StarTransactions",
     "Sticker",
     "StickerFormat",
     "StickerSet",
@@ -217,6 +232,11 @@
     "SwitchInlineQueryChosenChat",
     "TextQuote",
     "TopicIconColor",
+    "TransactionPartner",
+    "TransactionPartnerFragment",
+    "TransactionPartnerOther",
+    "TransactionPartnerTelegramAds",
+    "TransactionPartnerUser",
     "Update",
     "UpdateType",
     "User",
diff --git a/telegrinder/types/enums.py b/telegrinder/types/enums.py
index c498fd93..a04a0844 100755
--- a/telegrinder/types/enums.py
+++ b/telegrinder/types/enums.py
@@ -351,6 +351,7 @@ class Currency(str, enum.Enum):
     VND = "VND"
     YER = "YER"
     ZAR = "ZAR"
+    XTR = "XTR"
 
 
 class InlineQueryResultType(str, enum.Enum):
@@ -435,7 +436,7 @@ class BotCommandScopeType(str, enum.Enum):
 
 
 class ChatType(str, enum.Enum):
-    """Type of chat, can be either “private”, “group”, “supergroup” or “channel”"""
+    """Type of chat, can be either `private`, `group`, `supergroup` or `channel`."""
 
     PRIVATE = "private"
     GROUP = "group"
@@ -467,15 +468,15 @@ class DiceEmoji(str, enum.Enum):
 
 class MessageEntityType(str, enum.Enum):
     """Type of the entity.
-    Currently, can be “mention” (`@username`), “hashtag”
-    (`#hashtag`), “cashtag” (`$USD`), “bot_command” (`/start@jobs_bot`),
-    “url” (`https://telegram.org`), “email” (`do-not-reply@telegram.org`),
-    “phone_number” (`+1-212-555-0123`), “bold” (**bold text**), “italic”
-    (*italic text*), “underline” (underlined text), “strikethrough” (strikethrough
-    text), “spoiler” (spoiler message), “code” (monowidth string), “pre”
-    (monowidth block), “text_link” (for clickable text URLs), “text_mention”
+    Currently, can be `mention` (`@username`), `hashtag`
+    (`#hashtag`), `cashtag` (`$USD`), `bot_command` (`/start@jobs_bot`),
+    `url` (`https://telegram.org`), `email` (`do-not-reply@telegram.org`),
+    `phone_number` (`+1-212-555-0123`), `bold` (**bold text**), `italic`
+    (*italic text*), `underline` (underlined text), `strikethrough` (strikethrough
+    text), `spoiler` (spoiler message), `code` (monowidth string), `pre`
+    (monowidth block), `text_link` (for clickable text URLs), `text_mention`
     (for users [without usernames](https://telegram.org/blog/edit#new-mentions)),
-    “custom_emoji” (for inline custom emoji stickers), “blockquote” (blockquote)
+    `custom_emoji` (for inline custom emoji stickers), `blockquote` (blockquote)
     [docs](https://core.telegram.org/bots/api#messageentity)"""
 
     MENTION = "mention"
@@ -496,19 +497,20 @@ class MessageEntityType(str, enum.Enum):
     TEXT_LINK = "text_link"
     TEXT_MENTION = "text_mention"
     CUSTOM_EMOJI = "custom_emoji"
+    EXPANDABLE_BLOCKQUOTE = "expandable_blockquote"
 
 
 class PollType(str, enum.Enum):
-    """Poll type, currently can be “regular” or “quiz”"""
+    """Poll type, currently can be `regular` or `quiz`."""
 
     REGULAR = "regular"
     QUIZ = "quiz"
 
 
 class StickerType(str, enum.Enum):
-    """Type of the sticker, currently one of “regular”, “mask”, “custom_emoji”.
+    """Type of the sticker, currently one of `regular`, `mask`, `custom_emoji`.
     The type of the sticker is independent from its format, which is determined
-    by the fields *is_animated* and *is_video*."""
+    by the fields `is_animated` and `is_video`."""
 
     REGULAR = "regular"
     MASK = "mask"
@@ -526,7 +528,7 @@ class MessageOriginType(str, enum.Enum):
 
 
 class StickerSetStickerType(str, enum.Enum):
-    """Type of stickers in the set, currently one of “regular”, “mask”, “custom_emoji”"""
+    """Type of stickers in the set, currently one of `regular`, `mask`, `custom_emoji`."""
 
     REGULAR = "regular"
     MASK = "mask"
@@ -534,8 +536,8 @@ class StickerSetStickerType(str, enum.Enum):
 
 
 class MaskPositionPoint(str, enum.Enum):
-    """The part of the face relative to which the mask should be placed. One of “forehead”,
-    “eyes”, “mouth”, or “chin”."""
+    """The part of the face relative to which the mask should be placed. One of `forehead`,
+    `eyes`, `mouth`, or `chin`."""
 
     FOREHEAD = "forehead"
     EYES = "eyes"
@@ -545,10 +547,10 @@ class MaskPositionPoint(str, enum.Enum):
 
 class InlineQueryChatType(str, enum.Enum):
     """Type of the chat from which the inline query was sent. Can be
-    either “sender” for a private chat with the inline query sender, “private”,
-    “group”, “supergroup”, or “channel”. The chat type should be always known
+    either `sender` for a private chat with the inline query sender, `private`,
+    `group`, `supergroup`, or `channel`. The chat type should be always known
     for requests sent from official clients and most third-party clients,
-    unless the request was sent from a secret chat"""
+    unless the request was sent from a secret chat."""
 
     SENDER = "sender"
     PRIVATE = "private"
@@ -558,15 +560,16 @@ class InlineQueryChatType(str, enum.Enum):
 
 
 class InlineQueryResultMimeType(str, enum.Enum):
-    """MIME type of the content of the video URL, “text/html” or “video/mp4”"""
+    """MIME type of the content of the video URL, `text/html` or `video/mp4`."""
 
     TEXT_HTML = "text/html"
     VIDEO_MP4 = "video/mp4"
 
 
 class InlineQueryResultThumbnailMimeType(str, enum.Enum):
-    """MIME type of the thumbnail, must be one of “image/jpeg”, “image/gif”,
-    or “video/mp4”. Defaults to “image/jpeg”"""
+    """MIME type of the thumbnail, must be one of `image/jpeg`, `image/gif`,
+    or `video/mp4`. Defaults to `image/jpeg`
+    """
 
     IMAGE_JPEG = "image/jpeg"
     IMAGE_GIF = "image/gif"
@@ -599,8 +602,9 @@ class ReactionTypeType(str, enum.Enum):
 
 
 class InlineQueryResultGifThumbnailMimeType(str, enum.Enum):
-    """MIME type of the thumbnail, must be one of “image/jpeg”, “image/gif”,
-    or “video/mp4”. Defaults to “image/jpeg”"""
+    """MIME type of the thumbnail, must be one of `image/jpeg`, `image/gif`,
+    or `video/mp4`. Defaults to `image/jpeg`.
+    """
 
     IMAGE_JPEG = "image/jpeg"
     IMAGE_GIF = "image/gif"
@@ -608,8 +612,9 @@ class InlineQueryResultGifThumbnailMimeType(str, enum.Enum):
 
 
 class InlineQueryResultMpeg4GifThumbnailMimeType(str, enum.Enum):
-    """MIME type of the thumbnail, must be one of “image/jpeg”, “image/gif”,
-    or “video/mp4”. Defaults to “image/jpeg”"""
+    """MIME type of the thumbnail, must be one of `image/jpeg`, `image/gif`,
+    or `video/mp4`. Defaults to `image/jpeg`.
+    """
 
     IMAGE_JPEG = "image/jpeg"
     IMAGE_GIF = "image/gif"
@@ -617,24 +622,25 @@ class InlineQueryResultMpeg4GifThumbnailMimeType(str, enum.Enum):
 
 
 class InlineQueryResultVideoMimeType(str, enum.Enum):
-    """MIME type of the content of the video URL, “text/html” or “video/mp4”"""
+    """MIME type of the content of the video URL, `text/html` or `video/mp4`."""
 
     TEXT_HTML = "text/html"
     VIDEO_MP4 = "video/mp4"
 
 
 class InlineQueryResultDocumentMimeType(str, enum.Enum):
-    """MIME type of the content of the file, either “application/pdf” or “application/zip”"""
+    """MIME type of the content of the file, either `application/pdf` or `application/zip`."""
 
     APPLICATION_PDF = "application/pdf"
     APPLICATION_ZIP = "application/zip"
 
 
 class EncryptedPassportElementType(str, enum.Enum):
-    """Element type. One of “personal_details”, “passport”, “driver_license”,
-    “identity_card”, “internal_passport”, “address”, “utility_bill”,
-    “bank_statement”, “rental_agreement”, “passport_registration”,
-    “temporary_registration”, “phone_number”, “email”."""
+    """Element type. One of `personal_details`, `passport`, `driver_license`,
+    `identity_card`, `internal_passport`, `address`, `utility_bill`,
+    `bank_statement`, `rental_agreement`, `passport_registration`,
+    `temporary_registration`, `phone_number`, `email`.
+    """
 
     PERSONAL_DETAILS = "personal_details"
     PASSPORT = "passport"
diff --git a/telegrinder/types/methods.py b/telegrinder/types/methods.py
index 3ef68718..ec268789 100644
--- a/telegrinder/types/methods.py
+++ b/telegrinder/types/methods.py
@@ -4,7 +4,7 @@
 from fntypes.co import Result, Variative
 
 from telegrinder.api.error import APIError
-from telegrinder.model import full_result, get_params
+from telegrinder.model import ProxiedDict, full_result, get_params
 from telegrinder.types.enums import *  # noqa: F403
 from telegrinder.types.objects import *  # noqa: F403
 
@@ -13,7 +13,13 @@
 
 
 class APIMethods:
-    """Telegram Bot API 7.3 methods, released `May 6, 2024`."""
+    """Telegram Bot API 7.7 methods, released `July 7, 2024`."""
+
+    default_params = ProxiedDict(
+        typing.TypedDict(
+            "DefaultParams", {"parse_mode": str, "question_parse_mode": str, "explanation_parse_mode": str}
+        )
+    )
 
     def __init__(self, api: "ABCAPI") -> None:
         self.api = api
@@ -28,7 +34,7 @@ async def get_updates(
     ) -> Result[list[Update], APIError]:
         """Method `getUpdates`, see the [documentation](https://core.telegram.org/bots/api#getupdates)
 
-        Use this method to receive incoming updates using long polling (wiki). 
+        Use this method to receive incoming updates using long polling (wiki).
         Returns an Array of Update objects.
 
         :param offset: Identifier of the first update to be returned. Must be greater by one than \
@@ -75,13 +81,13 @@ async def set_webhook(
     ) -> Result[bool, APIError]:
         """Method `setWebhook`, see the [documentation](https://core.telegram.org/bots/api#setwebhook)
 
-        Use this method to specify a URL and receive incoming updates via an outgoing 
-        webhook. Whenever there is an update for the bot, we will send an HTTPS POST 
-        request to the specified URL, containing a JSON-serialized Update. In 
-        case of an unsuccessful request, we will give up after a reasonable amount 
-        of attempts. Returns True on success. If you'd like to make sure that the 
-        webhook was set by you, you can specify secret data in the parameter secret_token. 
-        If specified, the request will contain a header "X-Telegram-Bot-Api-Secret-Token" 
+        Use this method to specify a URL and receive incoming updates via an outgoing
+        webhook. Whenever there is an update for the bot, we will send an HTTPS POST
+        request to the specified URL, containing a JSON-serialized Update. In
+        case of an unsuccessful request, we will give up after a reasonable amount
+        of attempts. Returns True on success. If you'd like to make sure that the
+        webhook was set by you, you can specify secret data in the parameter secret_token.
+        If specified, the request will contain a header "X-Telegram-Bot-Api-Secret-Token"
         with the secret token as content.
 
         :param url: HTTPS URL to send updates to. Use an empty string to remove webhook integration. \
@@ -206,15 +212,18 @@ async def send_message(
         text: str,
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
-        parse_mode: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
         entities: list[MessageEntity] | None = None,
         link_preview_options: LinkPreviewOptions | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendMessage`, see the [documentation](https://core.telegram.org/bots/api#sendmessage)
@@ -244,6 +253,9 @@ async def send_message(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -269,8 +281,8 @@ async def forward_message(
     ) -> Result[Message, APIError]:
         """Method `forwardMessage`, see the [documentation](https://core.telegram.org/bots/api#forwardmessage)
 
-        Use this method to forward messages of any kind. Service messages and messages 
-        with protected content can't be forwarded. On success, the sent Message 
+        Use this method to forward messages of any kind. Service messages and messages
+        with protected content can't be forwarded. On success, the sent Message
         is returned.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -307,10 +319,10 @@ async def forward_messages(
     ) -> Result[list[MessageId], APIError]:
         """Method `forwardMessages`, see the [documentation](https://core.telegram.org/bots/api#forwardmessages)
 
-        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 
+        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.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -345,24 +357,27 @@ async def copy_message(
         message_id: int,
         message_thread_id: int | None = None,
         caption: str | None = None,
-        parse_mode: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
         caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[MessageId, APIError]:
         """Method `copyMessage`, see the [documentation](https://core.telegram.org/bots/api#copymessage)
 
-        Use this method to copy messages of any kind. 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 forwardMessage, 
-        but the copied message doesn't have a link to the original message. Returns 
-        the MessageId of the sent message on success.
+        Use this method to copy messages of any kind. Service messages, paid media
+        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 forwardMessage, but the copied message doesn't have a link to
+        the original message. Returns the MessageId of the sent message on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
         (in the format @channelusername).
@@ -384,6 +399,9 @@ async def copy_message(
         :param caption_entities: A JSON-serialized list of special entities that appear in the new caption, \
         which can be specified instead of parse_mode.
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media. Ignored \
+        if a new caption isn't specified.
+
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
@@ -414,14 +432,14 @@ async def copy_messages(
     ) -> Result[list[MessageId], APIError]:
         """Method `copyMessages`, see the [documentation](https://core.telegram.org/bots/api#copymessages)
 
-        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.
+        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, paid media
+        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.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
         (in the format @channelusername).
@@ -456,15 +474,19 @@ async def send_photo(
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
         caption: str | None = None,
-        parse_mode: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
         caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
         has_spoiler: bool | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendPhoto`, see the [documentation](https://core.telegram.org/bots/api#sendphoto)
@@ -496,12 +518,17 @@ async def send_photo(
         :param caption_entities: A JSON-serialized list of special entities that appear in the caption, \
         which can be specified instead of parse_mode.
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param has_spoiler: Pass True if the photo needs to be covered with a spoiler animation.
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -522,7 +549,7 @@ async def send_audio(
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
         caption: str | None = None,
-        parse_mode: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
         caption_entities: list[MessageEntity] | None = None,
         duration: int | None = None,
         performer: str | None = None,
@@ -530,18 +557,21 @@ async def send_audio(
         thumbnail: InputFile | str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendAudio`, see the [documentation](https://core.telegram.org/bots/api#sendaudio)
 
-        Use this method to send audio files, if you want Telegram clients to display 
-        them in the music player. Your audio must be in the .MP3 or .M4A format. On 
-        success, the sent Message is returned. Bots can currently send audio files 
-        of up to 50 MB in size, this limit may be changed in the future. For sending 
+        Use this method to send audio files, if you want Telegram clients to display
+        them in the music player. Your audio must be in the .MP3 or .M4A format. On
+        success, the sent Message is returned. Bots can currently send audio files
+        of up to 50 MB in size, this limit may be changed in the future. For sending
         voice messages, use the sendVoice method instead.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -584,6 +614,9 @@ async def send_audio(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -605,21 +638,24 @@ async def send_document(
         message_thread_id: int | None = None,
         thumbnail: InputFile | str | None = None,
         caption: str | None = None,
-        parse_mode: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
         caption_entities: list[MessageEntity] | None = None,
         disable_content_type_detection: bool | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendDocument`, see the [documentation](https://core.telegram.org/bots/api#senddocument)
 
-        Use this method to send general files. On success, the sent Message is returned. 
-        Bots can currently send files of any type of up to 50 MB in size, this limit 
+        Use this method to send general files. On success, the sent Message is returned.
+        Bots can currently send files of any type of up to 50 MB in size, this limit
         may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -660,6 +696,9 @@ async def send_document(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -684,23 +723,27 @@ async def send_video(
         height: int | None = None,
         thumbnail: InputFile | str | None = None,
         caption: str | None = None,
-        parse_mode: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
         caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
         has_spoiler: bool | None = None,
         supports_streaming: bool | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendVideo`, see the [documentation](https://core.telegram.org/bots/api#sendvideo)
 
-        Use this method to send video files, Telegram clients support MPEG4 videos 
-        (other formats may be sent as Document). On success, the sent Message is 
-        returned. Bots can currently send video files of up to 50 MB in size, this 
+        Use this method to send video files, Telegram clients support MPEG4 videos
+        (other formats may be sent as Document). On success, the sent Message is
+        returned. Bots can currently send video files of up to 50 MB in size, this
         limit may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -740,6 +783,8 @@ async def send_video(
         :param caption_entities: A JSON-serialized list of special entities that appear in the caption, \
         which can be specified instead of parse_mode.
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param has_spoiler: Pass True if the video needs to be covered with a spoiler animation.
 
         :param supports_streaming: Pass True if the uploaded video is suitable for streaming.
@@ -748,6 +793,9 @@ async def send_video(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -772,21 +820,25 @@ async def send_animation(
         height: int | None = None,
         thumbnail: InputFile | str | None = None,
         caption: str | None = None,
-        parse_mode: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
         caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
         has_spoiler: bool | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendAnimation`, see the [documentation](https://core.telegram.org/bots/api#sendanimation)
 
-        Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without 
-        sound). On success, the sent Message is returned. Bots can currently send 
+        Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without
+        sound). On success, the sent Message is returned. Bots can currently send
         animation files of up to 50 MB in size, this limit may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -826,12 +878,17 @@ async def send_animation(
         :param caption_entities: A JSON-serialized list of special entities that appear in the caption, \
         which can be specified instead of parse_mode.
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
         :param has_spoiler: Pass True if the animation needs to be covered with a spoiler animation. \
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -852,24 +909,27 @@ async def send_voice(
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
         caption: str | None = None,
-        parse_mode: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
         caption_entities: list[MessageEntity] | None = None,
         duration: int | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendVoice`, see the [documentation](https://core.telegram.org/bots/api#sendvoice)
 
-        Use this method to send audio files, if you want Telegram clients to display 
-        the file as a playable voice message. For this to work, your audio must be 
-        in an .OGG file encoded with OPUS, or in .MP3 format, or in .M4A format (other 
-        formats may be sent as Audio or Document). On success, the sent Message is 
-        returned. Bots can currently send voice messages of up to 50 MB in size, this 
+        Use this method to send audio files, if you want Telegram clients to display
+        the file as a playable voice message. For this to work, your audio must be
+        in an .OGG file encoded with OPUS, or in .MP3 format, or in .M4A format (other
+        formats may be sent as Audio or Document). On success, the sent Message is
+        returned. Bots can currently send voice messages of up to 50 MB in size, this
         limit may be changed in the future.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -900,6 +960,9 @@ async def send_voice(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -924,16 +987,19 @@ async def send_video_note(
         thumbnail: InputFile | str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendVideoNote`, see the [documentation](https://core.telegram.org/bots/api#sendvideonote)
 
-        As of v.4.0, Telegram clients support rounded square MPEG4 videos of up 
-        to 1 minute long. Use this method to send video messages. On success, the 
+        As of v.4.0, Telegram clients support rounded square MPEG4 videos of up
+        to 1 minute long. Use this method to send video messages. On success, the
         sent Message is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -966,6 +1032,9 @@ async def send_video_note(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -979,6 +1048,64 @@ async def send_video_note(
         )
         return full_result(method_response, Message)
 
+    async def send_paid_media(
+        self,
+        chat_id: int | str,
+        star_count: int,
+        media: list[InputPaidMedia],
+        caption: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
+        caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
+        disable_notification: bool | None = None,
+        protect_content: bool | None = None,
+        reply_parameters: ReplyParameters | None = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
+        **other: typing.Any,
+    ) -> Result[Message, APIError]:
+        """Method `sendPaidMedia`, see the [documentation](https://core.telegram.org/bots/api#sendpaidmedia)
+
+        Use this method to send paid media to channel chats. On success, the sent
+        Message is returned.
+
+        :param chat_id: Unique identifier for the target chat or username of the target channel \
+        (in the format @channelusername).
+
+        :param star_count: The number of Telegram Stars that must be paid to buy access to the media. \
+
+        :param media: A JSON-serialized array describing the media to be sent; up to 10 items. \
+
+        :param caption: Media caption, 0-1024 characters after entities parsing.
+
+        :param parse_mode: Mode for parsing entities in the media caption. See formatting options \
+        for more details.
+
+        :param caption_entities: A JSON-serialized list of special entities that appear in the caption, \
+        which can be specified instead of parse_mode.
+
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media.
+
+        :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
+
+        :param protect_content: Protects the contents of the sent message from forwarding and saving.
+
+        :param reply_parameters: Description of the message to reply to.
+
+        :param reply_markup: 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.
+        """
+
+        method_response = await self.api.request_raw(
+            "sendPaidMedia",
+            get_params(locals()),
+        )
+        return full_result(method_response, Message)
+
     async def send_media_group(
         self,
         chat_id: int | str,
@@ -987,13 +1114,14 @@ async def send_media_group(
         message_thread_id: int | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
         **other: typing.Any,
     ) -> Result[list[Message], APIError]:
         """Method `sendMediaGroup`, see the [documentation](https://core.telegram.org/bots/api#sendmediagroup)
 
-        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 
+        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 Messages that were sent is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1012,6 +1140,9 @@ async def send_media_group(
 
         :param protect_content: Protects the contents of the sent messages from forwarding and saving. \
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
         """
 
@@ -1034,10 +1165,13 @@ async def send_location(
         proximity_alert_radius: int | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendLocation`, see the [documentation](https://core.telegram.org/bots/api#sendlocation)
@@ -1073,6 +1207,9 @@ async def send_location(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -1101,15 +1238,18 @@ async def send_venue(
         google_place_type: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendVenue`, see the [documentation](https://core.telegram.org/bots/api#sendvenue)
 
-        Use this method to send information about a venue. On success, the sent Message 
+        Use this method to send information about a venue. On success, the sent Message
         is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1142,6 +1282,9 @@ async def send_venue(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -1166,10 +1309,13 @@ async def send_contact(
         vcard: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendContact`, see the [documentation](https://core.telegram.org/bots/api#sendcontact)
@@ -1197,6 +1343,9 @@ async def send_contact(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -1217,24 +1366,27 @@ async def send_poll(
         options: list[InputPollOption],
         business_connection_id: str | None = None,
         message_thread_id: int | None = None,
-        question_parse_mode: str | None = None,
+        question_parse_mode: str | None = default_params["question_parse_mode"],
         question_entities: list[MessageEntity] | None = None,
         is_anonymous: bool | None = None,
         type: typing.Literal["quiz", "regular"] | None = None,
         allows_multiple_answers: bool | None = None,
         correct_option_id: int | None = None,
         explanation: str | None = None,
-        explanation_parse_mode: str | None = None,
+        explanation_parse_mode: str | None = default_params["explanation_parse_mode"],
         explanation_entities: list[MessageEntity] | None = None,
         open_period: int | None = None,
         close_date: datetime | int | None = None,
         is_closed: bool | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendPoll`, see the [documentation](https://core.telegram.org/bots/api#sendpoll)
@@ -1294,6 +1446,9 @@ async def send_poll(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -1315,15 +1470,18 @@ async def send_dice(
         emoji: DiceEmoji | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendDice`, see the [documentation](https://core.telegram.org/bots/api#senddice)
 
-        Use this method to send an animated emoji that will display a random value. 
+        Use this method to send an animated emoji that will display a random value.
         On success, the sent Message is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -1343,6 +1501,9 @@ async def send_dice(
 
         :param protect_content: Protects the contents of the sent message from forwarding.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -1366,10 +1527,10 @@ async def send_chat_action(
     ) -> Result[bool, APIError]:
         """Method `sendChatAction`, see the [documentation](https://core.telegram.org/bots/api#sendchataction)
 
-        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. We only recommend using this method when a response from the 
+        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. We only recommend using this method when a response from the
         bot will take a noticeable amount of time to arrive.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the action \
@@ -1404,9 +1565,9 @@ async def set_message_reaction(
     ) -> Result[bool, APIError]:
         """Method `setMessageReaction`, see the [documentation](https://core.telegram.org/bots/api#setmessagereaction)
 
-        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 
+        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.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -1438,7 +1599,7 @@ async def get_user_profile_photos(
     ) -> Result[UserProfilePhotos, APIError]:
         """Method `getUserProfilePhotos`, see the [documentation](https://core.telegram.org/bots/api#getuserprofilephotos)
 
-        Use this method to get a list of profile pictures for a user. Returns a UserProfilePhotos 
+        Use this method to get a list of profile pictures for a user. Returns a UserProfilePhotos
         object.
 
         :param user_id: Unique identifier of the target user.
@@ -1491,10 +1652,10 @@ async def ban_chat_member(
     ) -> Result[bool, APIError]:
         """Method `banChatMember`, see the [documentation](https://core.telegram.org/bots/api#banchatmember)
 
-        Use this method to ban a user in a group, a supergroup or a channel. In the case 
-        of supergroups and channels, the user will not be able to return to the chat 
-        on their own using invite links, etc., unless unbanned first. The bot must 
-        be an administrator in the chat for this to work and must have the appropriate 
+        Use this method to ban a user in a group, a supergroup or a channel. In the case
+        of supergroups and channels, the user will not be able to return to the chat
+        on their own using invite links, etc., unless unbanned first. The bot must
+        be an administrator in the chat for this to work and must have the appropriate
         administrator rights. Returns True on success.
 
         :param chat_id: Unique identifier for the target group or username of the target supergroup \
@@ -1526,12 +1687,12 @@ async def unban_chat_member(
     ) -> Result[bool, APIError]:
         """Method `unbanChatMember`, see the [documentation](https://core.telegram.org/bots/api#unbanchatmember)
 
-        Use this method to unban a previously banned user in a supergroup or channel. 
-        The user will not return to the group or channel automatically, but will 
-        be able to join via link, etc. The bot must be an administrator for this to 
-        work. By default, this method guarantees that after the call the user is 
-        not a member of the chat, but will be able to join it. So if the user is a member 
-        of the chat they will also be removed from the chat. If you don't want this, 
+        Use this method to unban a previously banned user in a supergroup or channel.
+        The user will not return to the group or channel automatically, but will
+        be able to join via link, etc. The bot must be an administrator for this to
+        work. By default, this method guarantees that after the call the user is
+        not a member of the chat, but will be able to join it. So if the user is a member
+        of the chat they will also be removed from the chat. If you don't want this,
         use the parameter only_if_banned. Returns True on success.
 
         :param chat_id: Unique identifier for the target group or username of the target supergroup \
@@ -1559,9 +1720,9 @@ async def restrict_chat_member(
     ) -> Result[bool, APIError]:
         """Method `restrictChatMember`, see the [documentation](https://core.telegram.org/bots/api#restrictchatmember)
 
-        Use this method to restrict a user in a supergroup. The bot must be an administrator 
-        in the supergroup for this to work and must have the appropriate administrator 
-        rights. Pass True for all permissions to lift restrictions from a user. 
+        Use this method to restrict a user in a supergroup. The bot must be an administrator
+        in the supergroup for this to work and must have the appropriate administrator
+        rights. Pass True for all permissions to lift restrictions from a user.
         Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -1611,9 +1772,9 @@ async def promote_chat_member(
     ) -> Result[bool, APIError]:
         """Method `promoteChatMember`, see the [documentation](https://core.telegram.org/bots/api#promotechatmember)
 
-        Use this method to promote or demote a user in a supergroup or a channel. The 
-        bot must be an administrator in the chat for this to work and must have the 
-        appropriate administrator rights. Pass False for all boolean parameters 
+        Use this method to promote or demote a user in a supergroup or a channel. The
+        bot must be an administrator in the chat for this to work and must have the
+        appropriate administrator rights. Pass False for all boolean parameters
         to demote a user. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -1677,7 +1838,7 @@ async def set_chat_administrator_custom_title(
     ) -> Result[bool, APIError]:
         """Method `setChatAdministratorCustomTitle`, see the [documentation](https://core.telegram.org/bots/api#setchatadministratorcustomtitle)
 
-        Use this method to set a custom title for an administrator in a supergroup 
+        Use this method to set a custom title for an administrator in a supergroup
         promoted by the bot. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -1703,10 +1864,10 @@ async def ban_chat_sender_chat(
     ) -> Result[bool, APIError]:
         """Method `banChatSenderChat`, see the [documentation](https://core.telegram.org/bots/api#banchatsenderchat)
 
-        Use this method to ban a channel chat in a supergroup or a channel. Until the 
-        chat is unbanned, the owner of the banned chat won't be able to send messages 
-        on behalf of any of their channels. The bot must be an administrator in the 
-        supergroup or channel for this to work and must have the appropriate administrator 
+        Use this method to ban a channel chat in a supergroup or a channel. Until the
+        chat is unbanned, the owner of the banned chat won't be able to send messages
+        on behalf of any of their channels. The bot must be an administrator in the
+        supergroup or channel for this to work and must have the appropriate administrator
         rights. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -1729,8 +1890,8 @@ async def unban_chat_sender_chat(
     ) -> Result[bool, APIError]:
         """Method `unbanChatSenderChat`, see the [documentation](https://core.telegram.org/bots/api#unbanchatsenderchat)
 
-        Use this method to unban a previously banned channel chat in a supergroup 
-        or channel. The bot must be an administrator for this to work and must have 
+        Use this method to unban a previously banned channel chat in a supergroup
+        or channel. The bot must be an administrator for this to work and must have
         the appropriate administrator rights. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -1754,9 +1915,9 @@ async def set_chat_permissions(
     ) -> Result[bool, APIError]:
         """Method `setChatPermissions`, see the [documentation](https://core.telegram.org/bots/api#setchatpermissions)
 
-        Use this method to set default chat permissions for all members. The bot 
-        must be an administrator in the group or a supergroup for this to work and 
-        must have the can_restrict_members administrator rights. Returns True 
+        Use this method to set default chat permissions for all members. The bot
+        must be an administrator in the group or a supergroup for this to work and
+        must have the can_restrict_members administrator rights. Returns True
         on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -1784,9 +1945,9 @@ async def export_chat_invite_link(
     ) -> Result[str, APIError]:
         """Method `exportChatInviteLink`, see the [documentation](https://core.telegram.org/bots/api#exportchatinvitelink)
 
-        Use this method to generate a new primary invite link for a chat; any previously 
-        generated primary link is revoked. The bot must be an administrator in the 
-        chat for this to work and must have the appropriate administrator rights. 
+        Use this method to generate a new primary invite link for a chat; any previously
+        generated primary link is revoked. The bot must be an administrator in the
+        chat for this to work and must have the appropriate administrator rights.
         Returns the new invite link as String on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -1810,9 +1971,9 @@ async def create_chat_invite_link(
     ) -> Result[ChatInviteLink, APIError]:
         """Method `createChatInviteLink`, see the [documentation](https://core.telegram.org/bots/api#createchatinvitelink)
 
-        Use this method to create an additional invite link for a chat. The bot must 
-        be an administrator in the chat for this to work and must have the appropriate 
-        administrator rights. The link can be revoked using the method revokeChatInviteLink. 
+        Use this method to create an additional invite link for a chat. The bot must
+        be an administrator in the chat for this to work and must have the appropriate
+        administrator rights. The link can be revoked using the method revokeChatInviteLink.
         Returns the new invite link as ChatInviteLink object.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -1847,9 +2008,9 @@ async def edit_chat_invite_link(
     ) -> Result[ChatInviteLink, APIError]:
         """Method `editChatInviteLink`, see the [documentation](https://core.telegram.org/bots/api#editchatinvitelink)
 
-        Use this method to edit a non-primary invite link created by the bot. The 
-        bot must be an administrator in the chat for this to work and must have the 
-        appropriate administrator rights. Returns the edited invite link as a 
+        Use this method to edit a non-primary invite link created by the bot. The
+        bot must be an administrator in the chat for this to work and must have the
+        appropriate administrator rights. Returns the edited invite link as a
         ChatInviteLink object.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -1882,10 +2043,10 @@ async def revoke_chat_invite_link(
     ) -> Result[ChatInviteLink, APIError]:
         """Method `revokeChatInviteLink`, see the [documentation](https://core.telegram.org/bots/api#revokechatinvitelink)
 
-        Use this method to revoke an invite link created by the bot. If the primary 
-        link is revoked, a new link is automatically generated. The bot must be an 
-        administrator in the chat for this to work and must have the appropriate 
-        administrator rights. Returns the revoked invite link as ChatInviteLink 
+        Use this method to revoke an invite link created by the bot. If the primary
+        link is revoked, a new link is automatically generated. The bot must be an
+        administrator in the chat for this to work and must have the appropriate
+        administrator rights. Returns the revoked invite link as ChatInviteLink
         object.
 
         :param chat_id: Unique identifier of the target chat or username of the target channel (in \
@@ -1908,8 +2069,8 @@ async def approve_chat_join_request(
     ) -> Result[bool, APIError]:
         """Method `approveChatJoinRequest`, see the [documentation](https://core.telegram.org/bots/api#approvechatjoinrequest)
 
-        Use this method to approve a chat join request. The bot must be an administrator 
-        in the chat for this to work and must have the can_invite_users administrator 
+        Use this method to approve a chat join request. The bot must be an administrator
+        in the chat for this to work and must have the can_invite_users administrator
         right. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -1932,8 +2093,8 @@ async def decline_chat_join_request(
     ) -> Result[bool, APIError]:
         """Method `declineChatJoinRequest`, see the [documentation](https://core.telegram.org/bots/api#declinechatjoinrequest)
 
-        Use this method to decline a chat join request. The bot must be an administrator 
-        in the chat for this to work and must have the can_invite_users administrator 
+        Use this method to decline a chat join request. The bot must be an administrator
+        in the chat for this to work and must have the can_invite_users administrator
         right. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -1956,9 +2117,9 @@ async def set_chat_photo(
     ) -> Result[bool, APIError]:
         """Method `setChatPhoto`, see the [documentation](https://core.telegram.org/bots/api#setchatphoto)
 
-        Use this method to set a new profile photo for the chat. Photos can't be changed 
-        for private chats. The bot must be an administrator in the chat for this to 
-        work and must have the appropriate administrator rights. Returns True 
+        Use this method to set a new profile photo for the chat. Photos can't be changed
+        for private chats. The bot must be an administrator in the chat for this to
+        work and must have the appropriate administrator rights. Returns True
         on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -1980,8 +2141,8 @@ async def delete_chat_photo(
     ) -> Result[bool, APIError]:
         """Method `deleteChatPhoto`, see the [documentation](https://core.telegram.org/bots/api#deletechatphoto)
 
-        Use this method to delete a chat photo. Photos can't be changed for private 
-        chats. The bot must be an administrator in the chat for this to work and must 
+        Use this method to delete a chat photo. Photos can't be changed for private
+        chats. The bot must be an administrator in the chat for this to work and must
         have the appropriate administrator rights. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -2002,8 +2163,8 @@ async def set_chat_title(
     ) -> Result[bool, APIError]:
         """Method `setChatTitle`, see the [documentation](https://core.telegram.org/bots/api#setchattitle)
 
-        Use this method to change the title of a chat. Titles can't be changed for 
-        private chats. The bot must be an administrator in the chat for this to work 
+        Use this method to change the title of a chat. Titles can't be changed for
+        private chats. The bot must be an administrator in the chat for this to work
         and must have the appropriate administrator rights. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -2026,8 +2187,8 @@ async def set_chat_description(
     ) -> Result[bool, APIError]:
         """Method `setChatDescription`, see the [documentation](https://core.telegram.org/bots/api#setchatdescription)
 
-        Use this method to change the description of a group, a supergroup or a channel. 
-        The bot must be an administrator in the chat for this to work and must have 
+        Use this method to change the description of a group, a supergroup or a channel.
+        The bot must be an administrator in the chat for this to work and must have
         the appropriate administrator rights. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -2051,10 +2212,10 @@ async def pin_chat_message(
     ) -> Result[bool, APIError]:
         """Method `pinChatMessage`, see the [documentation](https://core.telegram.org/bots/api#pinchatmessage)
 
-        Use this method to add a message to the list of pinned messages in a chat. If 
-        the chat is not a private chat, the bot must be an administrator in the chat 
-        for this to work and must have the 'can_pin_messages' administrator right 
-        in a supergroup or 'can_edit_messages' administrator right in a channel. 
+        Use this method to add a message to the list of pinned messages in a chat. If
+        the chat is not a private chat, the bot must be an administrator in the chat
+        for this to work and must have the 'can_pin_messages' administrator right
+        in a supergroup or 'can_edit_messages' administrator right in a channel.
         Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -2081,10 +2242,10 @@ async def unpin_chat_message(
     ) -> Result[bool, APIError]:
         """Method `unpinChatMessage`, see the [documentation](https://core.telegram.org/bots/api#unpinchatmessage)
 
-        Use this method to remove a message from the list of pinned messages in a chat. 
-        If the chat is not a private chat, the bot must be an administrator in the chat 
-        for this to work and must have the 'can_pin_messages' administrator right 
-        in a supergroup or 'can_edit_messages' administrator right in a channel. 
+        Use this method to remove a message from the list of pinned messages in a chat.
+        If the chat is not a private chat, the bot must be an administrator in the chat
+        for this to work and must have the 'can_pin_messages' administrator right
+        in a supergroup or 'can_edit_messages' administrator right in a channel.
         Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -2107,10 +2268,10 @@ async def unpin_all_chat_messages(
     ) -> Result[bool, APIError]:
         """Method `unpinAllChatMessages`, see the [documentation](https://core.telegram.org/bots/api#unpinallchatmessages)
 
-        Use this method to clear the list of pinned messages in a chat. If the chat 
-        is not a private chat, the bot must be an administrator in the chat for this 
-        to work and must have the 'can_pin_messages' administrator right in a supergroup 
-        or 'can_edit_messages' administrator right in a channel. Returns True 
+        Use this method to clear the list of pinned messages in a chat. If the chat
+        is not a private chat, the bot must be an administrator in the chat for this
+        to work and must have the 'can_pin_messages' administrator right in a supergroup
+        or 'can_edit_messages' administrator right in a channel. Returns True
         on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -2130,7 +2291,7 @@ async def leave_chat(
     ) -> Result[bool, APIError]:
         """Method `leaveChat`, see the [documentation](https://core.telegram.org/bots/api#leavechat)
 
-        Use this method for your bot to leave a group, supergroup or channel. Returns 
+        Use this method for your bot to leave a group, supergroup or channel. Returns
         True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2150,7 +2311,7 @@ async def get_chat(
     ) -> Result[ChatFullInfo, APIError]:
         """Method `getChat`, see the [documentation](https://core.telegram.org/bots/api#getchat)
 
-        Use this method to get up-to-date information about the chat. Returns a 
+        Use this method to get up-to-date information about the chat. Returns a
         ChatFullInfo object on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2182,7 +2343,7 @@ async def get_chat_administrators(
     ]:
         """Method `getChatAdministrators`, see the [documentation](https://core.telegram.org/bots/api#getchatadministrators)
 
-        Use this method to get a list of administrators in a chat, which aren't bots. 
+        Use this method to get a list of administrators in a chat, which aren't bots.
         Returns an Array of ChatMember objects.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2244,8 +2405,8 @@ async def get_chat_member(
     ]:
         """Method `getChatMember`, see the [documentation](https://core.telegram.org/bots/api#getchatmember)
 
-        Use this method to get information about a member of a chat. The method is 
-        only guaranteed to work for other users if the bot is an administrator in 
+        Use this method to get information about a member of a chat. The method is
+        only guaranteed to work for other users if the bot is an administrator in
         the chat. Returns a ChatMember object on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2278,10 +2439,10 @@ async def set_chat_sticker_set(
     ) -> Result[bool, APIError]:
         """Method `setChatStickerSet`, see the [documentation](https://core.telegram.org/bots/api#setchatstickerset)
 
-        Use this method to set a new group sticker set for a supergroup. The bot must 
-        be an administrator in the chat for this to work and must have the appropriate 
-        administrator rights. Use the field can_set_sticker_set optionally 
-        returned in getChat requests to check if the bot can use this method. Returns 
+        Use this method to set a new group sticker set for a supergroup. The bot must
+        be an administrator in the chat for this to work and must have the appropriate
+        administrator rights. Use the field can_set_sticker_set optionally
+        returned in getChat requests to check if the bot can use this method. Returns
         True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2303,10 +2464,10 @@ async def delete_chat_sticker_set(
     ) -> Result[bool, APIError]:
         """Method `deleteChatStickerSet`, see the [documentation](https://core.telegram.org/bots/api#deletechatstickerset)
 
-        Use this method to delete a group sticker set from a supergroup. The bot must 
-        be an administrator in the chat for this to work and must have the appropriate 
-        administrator rights. Use the field can_set_sticker_set optionally 
-        returned in getChat requests to check if the bot can use this method. Returns 
+        Use this method to delete a group sticker set from a supergroup. The bot must
+        be an administrator in the chat for this to work and must have the appropriate
+        administrator rights. Use the field can_set_sticker_set optionally
+        returned in getChat requests to check if the bot can use this method. Returns
         True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2343,9 +2504,9 @@ async def create_forum_topic(
     ) -> Result[ForumTopic, APIError]:
         """Method `createForumTopic`, see the [documentation](https://core.telegram.org/bots/api#createforumtopic)
 
-        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 
+        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.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2377,9 +2538,9 @@ async def edit_forum_topic(
     ) -> Result[bool, APIError]:
         """Method `editForumTopic`, see the [documentation](https://core.telegram.org/bots/api#editforumtopic)
 
-        Use this method to edit name and icon of a topic in a forum supergroup chat. 
-        The bot must be an administrator in the chat for this to work and must have 
-        can_manage_topics administrator rights, unless it is the creator of the 
+        Use this method to edit name and icon of a topic in a forum supergroup chat.
+        The bot must be an administrator in the chat for this to work and must have
+        can_manage_topics administrator rights, unless it is the creator of the
         topic. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2409,9 +2570,9 @@ async def close_forum_topic(
     ) -> Result[bool, APIError]:
         """Method `closeForumTopic`, see the [documentation](https://core.telegram.org/bots/api#closeforumtopic)
 
-        Use this method to close an open 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, unless it is the creator of the topic. Returns True 
+        Use this method to close an open 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, unless it is the creator of the topic. Returns True
         on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2434,9 +2595,9 @@ async def reopen_forum_topic(
     ) -> Result[bool, APIError]:
         """Method `reopenForumTopic`, see the [documentation](https://core.telegram.org/bots/api#reopenforumtopic)
 
-        Use this method to reopen a closed 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, unless it is the creator of the topic. Returns True 
+        Use this method to reopen a closed 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, unless it is the creator of the topic. Returns True
         on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2459,9 +2620,9 @@ async def delete_forum_topic(
     ) -> Result[bool, APIError]:
         """Method `deleteForumTopic`, see the [documentation](https://core.telegram.org/bots/api#deleteforumtopic)
 
-        Use this method to delete a forum topic along with all its messages in a forum 
-        supergroup chat. The bot must be an administrator in the chat for this to 
-        work and must have the can_delete_messages administrator rights. Returns 
+        Use this method to delete a forum topic along with all its messages in a forum
+        supergroup chat. The bot must be an administrator in the chat for this to
+        work and must have the can_delete_messages administrator rights. Returns
         True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2484,9 +2645,9 @@ async def unpin_all_forum_topic_messages(
     ) -> Result[bool, APIError]:
         """Method `unpinAllForumTopicMessages`, see the [documentation](https://core.telegram.org/bots/api#unpinallforumtopicmessages)
 
-        Use this method to clear the list of pinned messages in a forum topic. The 
-        bot must be an administrator in the chat for this to work and must have the 
-        can_pin_messages administrator right in the supergroup. Returns True 
+        Use this method to clear the list of pinned messages in a forum topic. The
+        bot must be an administrator in the chat for this to work and must have the
+        can_pin_messages administrator right in the supergroup. Returns True
         on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2509,8 +2670,8 @@ async def edit_general_forum_topic(
     ) -> Result[bool, APIError]:
         """Method `editGeneralForumTopic`, see the [documentation](https://core.telegram.org/bots/api#editgeneralforumtopic)
 
-        Use this method to edit the name of the 'General' topic in a forum supergroup 
-        chat. The bot must be an administrator in the chat for this to work and must 
+        Use this method to edit the name of the 'General' topic in a forum supergroup
+        chat. The bot must be an administrator in the chat for this to work and must
         have can_manage_topics administrator rights. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2532,8 +2693,8 @@ async def close_general_forum_topic(
     ) -> Result[bool, APIError]:
         """Method `closeGeneralForumTopic`, see the [documentation](https://core.telegram.org/bots/api#closegeneralforumtopic)
 
-        Use this method to close an open 'General' topic in a forum supergroup chat. 
-        The bot must be an administrator in the chat for this to work and must have 
+        Use this method to close an open 'General' 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 True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2553,9 +2714,9 @@ async def reopen_general_forum_topic(
     ) -> Result[bool, APIError]:
         """Method `reopenGeneralForumTopic`, see the [documentation](https://core.telegram.org/bots/api#reopengeneralforumtopic)
 
-        Use this method to reopen a closed 'General' 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. The topic will be automatically 
+        Use this method to reopen a closed 'General' 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. The topic will be automatically
         unhidden if it was hidden. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2575,9 +2736,9 @@ async def hide_general_forum_topic(
     ) -> Result[bool, APIError]:
         """Method `hideGeneralForumTopic`, see the [documentation](https://core.telegram.org/bots/api#hidegeneralforumtopic)
 
-        Use this method to hide the 'General' 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. The topic will be automatically 
+        Use this method to hide the 'General' 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. The topic will be automatically
         closed if it was open. Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2597,8 +2758,8 @@ async def unhide_general_forum_topic(
     ) -> Result[bool, APIError]:
         """Method `unhideGeneralForumTopic`, see the [documentation](https://core.telegram.org/bots/api#unhidegeneralforumtopic)
 
-        Use this method to unhide the 'General' topic in a forum supergroup chat. 
-        The bot must be an administrator in the chat for this to work and must have 
+        Use this method to unhide the 'General' 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 True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2618,9 +2779,9 @@ async def unpin_all_general_forum_topic_messages(
     ) -> Result[bool, APIError]:
         """Method `unpinAllGeneralForumTopicMessages`, see the [documentation](https://core.telegram.org/bots/api#unpinallgeneralforumtopicmessages)
 
-        Use this method to clear the list of pinned messages in a General forum topic. 
-        The bot must be an administrator in the chat for this to work and must have 
-        the can_pin_messages administrator right in the supergroup. Returns 
+        Use this method to clear the list of pinned messages in a General forum topic.
+        The bot must be an administrator in the chat for this to work and must have
+        the can_pin_messages administrator right in the supergroup. Returns
         True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target supergroup \
@@ -2644,8 +2805,8 @@ async def answer_callback_query(
     ) -> Result[bool, APIError]:
         """Method `answerCallbackQuery`, see the [documentation](https://core.telegram.org/bots/api#answercallbackquery)
 
-        Use this method to send answers to callback queries sent from inline keyboards. 
-        The answer will be displayed to the user as a notification at the top of the 
+        Use this method to send answers to callback queries sent from inline keyboards.
+        The answer will be displayed to the user as a notification at the top of the
         chat screen or as an alert. On success, True is returned.
 
         :param callback_query_id: Unique identifier for the query to be answered.
@@ -2681,7 +2842,7 @@ async def get_user_chat_boosts(
     ) -> Result[UserChatBoosts, APIError]:
         """Method `getUserChatBoosts`, see the [documentation](https://core.telegram.org/bots/api#getuserchatboosts)
 
-        Use this method to get the list of boosts added to a chat by a user. Requires 
+        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.
 
         :param chat_id: Unique identifier for the chat or username of the channel (in the format \
@@ -2724,7 +2885,7 @@ async def set_my_commands(
     ) -> Result[bool, APIError]:
         """Method `setMyCommands`, see the [documentation](https://core.telegram.org/bots/api#setmycommands)
 
-        Use this method to change the list of the bot's commands. See this manual 
+        Use this method to change the list of the bot's commands. See this manual
         for more details about bot commands. Returns True on success.
 
         :param commands: A JSON-serialized list of bot commands to be set as the list of the bot's commands. \
@@ -2752,8 +2913,8 @@ async def delete_my_commands(
     ) -> Result[bool, APIError]:
         """Method `deleteMyCommands`, see the [documentation](https://core.telegram.org/bots/api#deletemycommands)
 
-        Use this method to delete the list of the bot's commands for the given scope 
-        and user language. After deletion, higher level commands will be shown 
+        Use this method to delete the list of the bot's commands for the given scope
+        and user language. After deletion, higher level commands will be shown
         to affected users. Returns True on success.
 
         :param scope: A JSON-serialized object, describing scope of users for which the commands \
@@ -2778,8 +2939,8 @@ async def get_my_commands(
     ) -> Result[list[BotCommand], APIError]:
         """Method `getMyCommands`, see the [documentation](https://core.telegram.org/bots/api#getmycommands)
 
-        Use this method to get the current list of the bot's commands for the given 
-        scope and user language. Returns an Array of BotCommand objects. If commands 
+        Use this method to get the current list of the bot's commands for the given
+        scope and user language. Returns an Array of BotCommand objects. If commands
         aren't set, an empty list is returned.
 
         :param scope: A JSON-serialized object, describing scope of users. Defaults to BotCommandScopeDefault. \
@@ -2843,7 +3004,7 @@ async def set_my_description(
     ) -> Result[bool, APIError]:
         """Method `setMyDescription`, see the [documentation](https://core.telegram.org/bots/api#setmydescription)
 
-        Use this method to change the bot's description, which is shown in the chat 
+        Use this method to change the bot's description, which is shown in the chat
         with the bot if the chat is empty. Returns True on success.
 
         :param description: New bot description; 0-512 characters. Pass an empty string to remove the \
@@ -2886,8 +3047,8 @@ async def set_my_short_description(
     ) -> Result[bool, APIError]:
         """Method `setMyShortDescription`, see the [documentation](https://core.telegram.org/bots/api#setmyshortdescription)
 
-        Use this method to change the bot's short description, which is shown on 
-        the bot's profile page and is sent together with the link when users share 
+        Use this method to change the bot's short description, which is shown on
+        the bot's profile page and is sent together with the link when users share
         the bot. Returns True on success.
 
         :param short_description: New short description for the bot; 0-120 characters. Pass an empty string \
@@ -2930,7 +3091,7 @@ async def set_chat_menu_button(
     ) -> Result[bool, APIError]:
         """Method `setChatMenuButton`, see the [documentation](https://core.telegram.org/bots/api#setchatmenubutton)
 
-        Use this method to change the bot's menu button in a private chat, or the default 
+        Use this method to change the bot's menu button in a private chat, or the default
         menu button. Returns True on success.
 
         :param chat_id: Unique identifier for the target private chat. If not specified, default \
@@ -2952,7 +3113,7 @@ async def get_chat_menu_button(
     ) -> Result[Variative[MenuButtonCommands, MenuButtonWebApp, MenuButtonDefault], APIError]:
         """Method `getChatMenuButton`, see the [documentation](https://core.telegram.org/bots/api#getchatmenubutton)
 
-        Use this method to get the current value of the bot's menu button in a private 
+        Use this method to get the current value of the bot's menu button in a private
         chat, or the default menu button. Returns MenuButton on success.
 
         :param chat_id: Unique identifier for the target private chat. If not specified, default \
@@ -2975,9 +3136,9 @@ async def set_my_default_administrator_rights(
     ) -> Result[bool, APIError]:
         """Method `setMyDefaultAdministratorRights`, see the [documentation](https://core.telegram.org/bots/api#setmydefaultadministratorrights)
 
-        Use this method to change the default administrator rights requested by 
-        the bot when it's added as an administrator to groups or channels. These 
-        rights will be suggested to users, but they are free to modify the list before 
+        Use this method to change the default administrator rights requested by
+        the bot when it's added as an administrator to groups or channels. These
+        rights will be suggested to users, but they are free to modify the list before
         adding the bot. Returns True on success.
 
         :param rights: A JSON-serialized object describing new default administrator rights. \
@@ -3001,7 +3162,7 @@ async def get_my_default_administrator_rights(
     ) -> Result[ChatAdministratorRights, APIError]:
         """Method `getMyDefaultAdministratorRights`, see the [documentation](https://core.telegram.org/bots/api#getmydefaultadministratorrights)
 
-        Use this method to get the current default administrator rights of the bot. 
+        Use this method to get the current default administrator rights of the bot.
         Returns ChatAdministratorRights on success.
 
         :param for_channels: Pass True to get default administrator rights of the bot in channels. Otherwise, \
@@ -3018,10 +3179,11 @@ async def get_my_default_administrator_rights(
     async def edit_message_text(
         self,
         text: str,
+        business_connection_id: str | None = None,
         chat_id: int | str | None = None,
         message_id: int | None = None,
         inline_message_id: str | None = None,
-        parse_mode: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
         entities: list[MessageEntity] | None = None,
         link_preview_options: LinkPreviewOptions | None = None,
         reply_markup: InlineKeyboardMarkup | None = None,
@@ -3029,9 +3191,14 @@ async def edit_message_text(
     ) -> Result[Variative[Message, bool], APIError]:
         """Method `editMessageText`, see the [documentation](https://core.telegram.org/bots/api#editmessagetext)
 
-        Use this method to edit text and game messages. On success, if the edited 
-        message is not an inline message, the edited Message is returned, otherwise 
-        True is returned.
+        Use this method to edit text and game messages. On success, if the edited
+        message is not an inline message, the edited Message is returned, otherwise
+        True is returned. Note that business messages that were not sent by the bot
+        and do not contain an inline keyboard can only be edited within 48 hours from
+        the time they were sent.
+
+        :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
+        to be edited was sent.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
         the target chat or username of the target channel (in the format @channelusername). \
@@ -3063,20 +3230,27 @@ async def edit_message_text(
 
     async def edit_message_caption(
         self,
+        business_connection_id: str | None = None,
         chat_id: int | str | None = None,
         message_id: int | None = None,
         inline_message_id: str | None = None,
         caption: str | None = None,
-        parse_mode: str | None = None,
+        parse_mode: str | None = default_params["parse_mode"],
         caption_entities: list[MessageEntity] | None = None,
+        show_caption_above_media: bool | None = None,
         reply_markup: InlineKeyboardMarkup | None = None,
         **other: typing.Any,
     ) -> Result[Variative[Message, bool], APIError]:
         """Method `editMessageCaption`, see the [documentation](https://core.telegram.org/bots/api#editmessagecaption)
 
-        Use this method to edit captions of messages. On success, if the edited message 
-        is not an inline message, the edited Message is returned, otherwise True 
-        is returned.
+        Use this method to edit captions of messages. On success, if the edited message
+        is not an inline message, the edited Message is returned, otherwise True
+        is returned. Note that business messages that were not sent by the bot and
+        do not contain an inline keyboard can only be edited within 48 hours from
+        the time they were sent.
+
+        :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
+        to be edited was sent.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
         the target chat or username of the target channel (in the format @channelusername). \
@@ -3095,6 +3269,9 @@ async def edit_message_caption(
         :param caption_entities: A JSON-serialized list of special entities that appear in the caption, \
         which can be specified instead of parse_mode.
 
+        :param show_caption_above_media: Pass True, if the caption must be shown above the message media. Supported \
+        only for animation, photo and video messages.
+
         :param reply_markup: A JSON-serialized object for an inline keyboard.
         """
 
@@ -3107,6 +3284,7 @@ async def edit_message_caption(
     async def edit_message_media(
         self,
         media: InputMedia,
+        business_connection_id: str | None = None,
         chat_id: int | str | None = None,
         message_id: int | None = None,
         inline_message_id: str | None = None,
@@ -3115,13 +3293,18 @@ async def edit_message_media(
     ) -> Result[Variative[Message, bool], APIError]:
         """Method `editMessageMedia`, see the [documentation](https://core.telegram.org/bots/api#editmessagemedia)
 
-        Use this method to edit animation, audio, document, photo, or video messages. 
-        If a message is part of a message album, then it can be edited only to an audio 
-        for audio albums, only to a document for document albums and to a photo or 
-        a video otherwise. When an inline message is edited, a new file can't be uploaded; 
-        use a previously uploaded file via its file_id or specify a URL. On success, 
-        if the edited message is not an inline message, the edited Message is returned, 
-        otherwise True is returned.
+        Use this method to edit animation, audio, document, photo, or video messages.
+        If a message is part of a message album, then it can be edited only to an audio
+        for audio albums, only to a document for document albums and to a photo or
+        a video otherwise. When an inline message is edited, a new file can't be uploaded;
+        use a previously uploaded file via its file_id or specify a URL. On success,
+        if the edited message is not an inline message, the edited Message is returned,
+        otherwise True is returned. Note that business messages that were not sent
+        by the bot and do not contain an inline keyboard can only be edited within
+        48 hours from the time they were sent.
+
+        :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
+        to be edited was sent.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
         the target chat or username of the target channel (in the format @channelusername). \
@@ -3147,6 +3330,7 @@ async def edit_message_live_location(
         self,
         latitude: float,
         longitude: float,
+        business_connection_id: str | None = None,
         chat_id: int | str | None = None,
         message_id: int | None = None,
         inline_message_id: str | None = None,
@@ -3159,11 +3343,14 @@ async def edit_message_live_location(
     ) -> Result[Variative[Message, bool], APIError]:
         """Method `editMessageLiveLocation`, see the [documentation](https://core.telegram.org/bots/api#editmessagelivelocation)
 
-        Use this method to edit live location messages. A location can be edited 
-        until its live_period expires or editing is explicitly disabled by a call 
-        to stopMessageLiveLocation. On success, if the edited message is not an 
+        Use this method to edit live location messages. A location can be edited
+        until its live_period expires or editing is explicitly disabled by a call
+        to stopMessageLiveLocation. On success, if the edited message is not an
         inline message, the edited Message is returned, otherwise True is returned.
 
+        :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
+        to be edited was sent.
+
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
         the target chat or username of the target channel (in the format @channelusername). \
 
@@ -3203,6 +3390,7 @@ async def edit_message_live_location(
 
     async def stop_message_live_location(
         self,
+        business_connection_id: str | None = None,
         chat_id: int | str | None = None,
         message_id: int | None = None,
         inline_message_id: str | None = None,
@@ -3211,10 +3399,13 @@ async def stop_message_live_location(
     ) -> Result[Variative[Message, bool], APIError]:
         """Method `stopMessageLiveLocation`, see the [documentation](https://core.telegram.org/bots/api#stopmessagelivelocation)
 
-        Use this method to stop updating a live location message before live_period 
-        expires. On success, if the message is not an inline message, the edited 
+        Use this method to stop updating a live location message before live_period
+        expires. On success, if the message is not an inline message, the edited
         Message is returned, otherwise True is returned.
 
+        :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
+        to be edited was sent.
+
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
         the target chat or username of the target channel (in the format @channelusername). \
 
@@ -3235,6 +3426,7 @@ async def stop_message_live_location(
 
     async def edit_message_reply_markup(
         self,
+        business_connection_id: str | None = None,
         chat_id: int | str | None = None,
         message_id: int | None = None,
         inline_message_id: str | None = None,
@@ -3243,9 +3435,14 @@ async def edit_message_reply_markup(
     ) -> Result[Variative[Message, bool], APIError]:
         """Method `editMessageReplyMarkup`, see the [documentation](https://core.telegram.org/bots/api#editmessagereplymarkup)
 
-        Use this method to edit only the reply markup of messages. On success, if 
-        the edited message is not an inline message, the edited Message is returned, 
-        otherwise True is returned.
+        Use this method to edit only the reply markup of messages. On success, if
+        the edited message is not an inline message, the edited Message is returned,
+        otherwise True is returned. Note that business messages that were not sent
+        by the bot and do not contain an inline keyboard can only be edited within
+        48 hours from the time they were sent.
+
+        :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
+        to be edited was sent.
 
         :param chat_id: Required if inline_message_id is not specified. Unique identifier for \
         the target chat or username of the target channel (in the format @channelusername). \
@@ -3269,14 +3466,18 @@ async def stop_poll(
         self,
         chat_id: int | str,
         message_id: int,
+        business_connection_id: str | None = None,
         reply_markup: InlineKeyboardMarkup | None = None,
         **other: typing.Any,
     ) -> Result[Poll, APIError]:
         """Method `stopPoll`, see the [documentation](https://core.telegram.org/bots/api#stoppoll)
 
-        Use this method to stop a poll which was sent by the bot. On success, the stopped 
+        Use this method to stop a poll which was sent by the bot. On success, the stopped
         Poll is returned.
 
+        :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
+        to be edited was sent.
+
         :param chat_id: Unique identifier for the target chat or username of the target channel \
         (in the format @channelusername).
 
@@ -3299,16 +3500,16 @@ async def delete_message(
     ) -> Result[bool, APIError]:
         """Method `deleteMessage`, see the [documentation](https://core.telegram.org/bots/api#deletemessage)
 
-        Use this method to delete a message, including service messages, with the 
-        following limitations: - A message can only be deleted if it was sent less 
-        than 48 hours ago. - Service messages about a supergroup, channel, or forum 
-        topic creation can't be deleted. - A dice message in a private chat can only 
-        be deleted if it was sent more than 24 hours ago. - Bots can delete outgoing 
-        messages in private chats, groups, and supergroups. - Bots can delete incoming 
-        messages in private chats. - Bots granted can_post_messages permissions 
-        can delete outgoing messages in channels. - If the bot is an administrator 
-        of a group, it can delete any message there. - If the bot has can_delete_messages 
-        permission in a supergroup or a channel, it can delete any message there. 
+        Use this method to delete a message, including service messages, with the
+        following limitations: - A message can only be deleted if it was sent less
+        than 48 hours ago. - Service messages about a supergroup, channel, or forum
+        topic creation can't be deleted. - A dice message in a private chat can only
+        be deleted if it was sent more than 24 hours ago. - Bots can delete outgoing
+        messages in private chats, groups, and supergroups. - Bots can delete incoming
+        messages in private chats. - Bots granted can_post_messages permissions
+        can delete outgoing messages in channels. - If the bot is an administrator
+        of a group, it can delete any message there. - If the bot has can_delete_messages
+        permission in a supergroup or a channel, it can delete any message there.
         Returns True on success.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -3331,8 +3532,8 @@ async def delete_messages(
     ) -> Result[bool, APIError]:
         """Method `deleteMessages`, see the [documentation](https://core.telegram.org/bots/api#deletemessages)
 
-        Use this method to delete multiple messages simultaneously. If some of 
-        the specified messages can't be found, they are skipped. Returns True on 
+        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.
 
         :param chat_id: Unique identifier for the target chat or username of the target channel \
@@ -3357,15 +3558,18 @@ async def send_sticker(
         emoji: str | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
-        reply_markup: (
-            InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None
-        ) = None,
+        reply_markup: InlineKeyboardMarkup
+        | ReplyKeyboardMarkup
+        | ReplyKeyboardRemove
+        | ForceReply
+        | None = None,
         **other: typing.Any,
     ) -> Result[Message, APIError]:
         """Method `sendSticker`, see the [documentation](https://core.telegram.org/bots/api#sendsticker)
 
-        Use this method to send static .WEBP, animated .TGS, or video .WEBM stickers. 
+        Use this method to send static .WEBP, animated .TGS, or video .WEBM stickers.
         On success, the sent Message is returned.
 
         :param business_connection_id: Unique identifier of the business connection on behalf of which the message \
@@ -3390,6 +3594,9 @@ async def send_sticker(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: Additional interface options. A JSON-serialized object for an inline \
@@ -3428,7 +3635,7 @@ async def get_custom_emoji_stickers(
     ) -> Result[list[Sticker], APIError]:
         """Method `getCustomEmojiStickers`, see the [documentation](https://core.telegram.org/bots/api#getcustomemojistickers)
 
-        Use this method to get information about custom emoji stickers by their 
+        Use this method to get information about custom emoji stickers by their
         identifiers. Returns an Array of Sticker objects.
 
         :param custom_emoji_ids: A JSON-serialized list of custom emoji identifiers. At most 200 custom \
@@ -3450,8 +3657,8 @@ async def upload_sticker_file(
     ) -> Result[File, APIError]:
         """Method `uploadStickerFile`, see the [documentation](https://core.telegram.org/bots/api#uploadstickerfile)
 
-        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 
+        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.
 
         :param user_id: User identifier of sticker file owner.
@@ -3480,7 +3687,7 @@ async def create_new_sticker_set(
     ) -> Result[bool, APIError]:
         """Method `createNewStickerSet`, see the [documentation](https://core.telegram.org/bots/api#createnewstickerset)
 
-        Use this method to create a new sticker set owned by a user. The bot will be 
+        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.
 
         :param user_id: User identifier of created sticker set owner.
@@ -3519,8 +3726,8 @@ async def add_sticker_to_set(
     ) -> Result[bool, APIError]:
         """Method `addStickerToSet`, see the [documentation](https://core.telegram.org/bots/api#addstickertoset)
 
-        Use this method to add a new sticker to a set created by the bot. Emoji sticker 
-        sets can have up to 200 stickers. Other sticker sets can have up to 120 stickers. 
+        Use this method to add a new sticker to a set created by the bot. Emoji sticker
+        sets can have up to 200 stickers. Other sticker sets can have up to 120 stickers.
         Returns True on success.
 
         :param user_id: User identifier of sticker set owner.
@@ -3589,8 +3796,8 @@ async def replace_sticker_in_set(
     ) -> Result[bool, APIError]:
         """Method `replaceStickerInSet`, see the [documentation](https://core.telegram.org/bots/api#replacestickerinset)
 
-        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, 
+        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.
 
         :param user_id: User identifier of the sticker set owner.
@@ -3641,8 +3848,8 @@ async def set_sticker_keywords(
     ) -> Result[bool, APIError]:
         """Method `setStickerKeywords`, see the [documentation](https://core.telegram.org/bots/api#setstickerkeywords)
 
-        Use this method to change search keywords assigned to a regular or custom 
-        emoji sticker. The sticker must belong to a sticker set created by the bot. 
+        Use this method to change search keywords assigned to a regular or custom
+        emoji sticker. The sticker must belong to a sticker set created by the bot.
         Returns True on success.
 
         :param sticker: File identifier of the sticker.
@@ -3665,7 +3872,7 @@ async def set_sticker_mask_position(
     ) -> Result[bool, APIError]:
         """Method `setStickerMaskPosition`, see the [documentation](https://core.telegram.org/bots/api#setstickermaskposition)
 
-        Use this method to change the mask position of a mask sticker. The sticker 
+        Use this method to change the mask position of a mask sticker. The sticker
         must belong to a sticker set that was created by the bot. Returns True on success.
 
         :param sticker: File identifier of the sticker.
@@ -3712,8 +3919,8 @@ async def set_sticker_set_thumbnail(
     ) -> Result[bool, APIError]:
         """Method `setStickerSetThumbnail`, see the [documentation](https://core.telegram.org/bots/api#setstickersetthumbnail)
 
-        Use this method to set the thumbnail of a regular or mask sticker set. The 
-        format of the thumbnail file must match the format of the stickers in the 
+        Use this method to set the thumbnail of a regular or mask sticker set. The
+        format of the thumbnail file must match the format of the stickers in the
         set. Returns True on success.
 
         :param name: Sticker set name.
@@ -3751,7 +3958,7 @@ async def set_custom_emoji_sticker_set_thumbnail(
     ) -> Result[bool, APIError]:
         """Method `setCustomEmojiStickerSetThumbnail`, see the [documentation](https://core.telegram.org/bots/api#setcustomemojistickersetthumbnail)
 
-        Use this method to set the thumbnail of a custom emoji sticker set. Returns 
+        Use this method to set the thumbnail of a custom emoji sticker set. Returns
         True on success.
 
         :param name: Sticker set name.
@@ -3797,7 +4004,7 @@ async def answer_inline_query(
     ) -> Result[bool, APIError]:
         """Method `answerInlineQuery`, see the [documentation](https://core.telegram.org/bots/api#answerinlinequery)
 
-        Use this method to send answers to an inline query. On success, True is returned. 
+        Use this method to send answers to an inline query. On success, True is returned.
         No more than 50 results per query are allowed.
 
         :param inline_query_id: Unique identifier for the answered query.
@@ -3854,10 +4061,10 @@ async def send_invoice(
         title: str,
         description: str,
         payload: str,
-        provider_token: str,
         currency: str,
         prices: list[LabeledPrice],
         message_thread_id: int | None = None,
+        provider_token: str | None = None,
         max_tip_amount: int | None = None,
         suggested_tip_amounts: list[int] | None = None,
         start_parameter: str | None = None,
@@ -3875,6 +4082,7 @@ async def send_invoice(
         is_flexible: bool | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
         reply_markup: InlineKeyboardMarkup | None = None,
         **other: typing.Any,
@@ -3896,18 +4104,22 @@ async def send_invoice(
         :param payload: Bot-defined invoice payload, 1-128 bytes. This will not be displayed to \
         the user, use for your internal processes.
 
-        :param provider_token: Payment provider token, obtained via @BotFather.
+        :param provider_token: Payment provider token, obtained via @BotFather. Pass an empty string \
+        for payments in Telegram Stars.
 
-        :param currency: Three-letter ISO 4217 currency code, see more on currencies.
+        :param currency: Three-letter ISO 4217 currency code, see more on currencies. Pass `XTR` \
+        for payments in Telegram Stars.
 
         :param prices: Price breakdown, a JSON-serialized list of components (e.g. product price, \
-        tax, discount, delivery cost, delivery tax, bonus, etc.).
+        tax, discount, delivery cost, delivery tax, bonus, etc.). Must contain \
+        exactly one item for payments in Telegram Stars.
 
         :param max_tip_amount: The maximum accepted amount for tips in the smallest units of the currency \
         (integer, not float/double). For example, for a maximum tip of US$ 1.45 \
         pass max_tip_amount = 145. See the exp parameter in currencies.json, it \
         shows the number of digits past the decimal point for each currency (2 for \
-        the majority of currencies). Defaults to 0.
+        the majority of currencies). Defaults to 0. Not supported for payments \
+        in Telegram Stars.
 
         :param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the smallest units \
         of the currency (integer, not float/double). At most 4 suggested tip amounts \
@@ -3934,24 +4146,34 @@ async def send_invoice(
 
         :param photo_height: Photo height.
 
-        :param need_name: Pass True if you require the user's full name to complete the order.
+        :param need_name: Pass True if you require the user's full name to complete the order. Ignored \
+        for payments in Telegram Stars.
 
-        :param need_phone_number: Pass True if you require the user's phone number to complete the order.
+        :param need_phone_number: Pass True if you require the user's phone number to complete the order. Ignored \
+        for payments in Telegram Stars.
 
         :param need_email: Pass True if you require the user's email address to complete the order. \
+        Ignored for payments in Telegram Stars.
 
         :param need_shipping_address: Pass True if you require the user's shipping address to complete the order. \
+        Ignored for payments in Telegram Stars.
 
-        :param send_phone_number_to_provider: Pass True if the user's phone number should be sent to provider.
+        :param send_phone_number_to_provider: Pass True if the user's phone number should be sent to the provider. Ignored \
+        for payments in Telegram Stars.
 
-        :param send_email_to_provider: Pass True if the user's email address should be sent to provider.
+        :param send_email_to_provider: Pass True if the user's email address should be sent to the provider. Ignored \
+        for payments in Telegram Stars.
 
-        :param is_flexible: Pass True if the final price depends on the shipping method.
+        :param is_flexible: Pass True if the final price depends on the shipping method. Ignored for \
+        payments in Telegram Stars.
 
         :param disable_notification: Sends the message silently. Users will receive a notification with no sound. \
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: A JSON-serialized object for an inline keyboard. If empty, one 'Pay total \
@@ -3969,9 +4191,9 @@ async def create_invoice_link(
         title: str,
         description: str,
         payload: str,
-        provider_token: str,
         currency: str,
         prices: list[LabeledPrice],
+        provider_token: str | None = None,
         max_tip_amount: int | None = None,
         suggested_tip_amounts: list[int] | None = None,
         provider_data: str | None = None,
@@ -3990,7 +4212,7 @@ async def create_invoice_link(
     ) -> Result[str, APIError]:
         """Method `createInvoiceLink`, see the [documentation](https://core.telegram.org/bots/api#createinvoicelink)
 
-        Use this method to create a link for an invoice. Returns the created invoice 
+        Use this method to create a link for an invoice. Returns the created invoice
         link as String on success.
 
         :param title: Product name, 1-32 characters.
@@ -4000,18 +4222,22 @@ async def create_invoice_link(
         :param payload: Bot-defined invoice payload, 1-128 bytes. This will not be displayed to \
         the user, use for your internal processes.
 
-        :param provider_token: Payment provider token, obtained via BotFather.
+        :param provider_token: Payment provider token, obtained via @BotFather. Pass an empty string \
+        for payments in Telegram Stars.
 
-        :param currency: Three-letter ISO 4217 currency code, see more on currencies.
+        :param currency: Three-letter ISO 4217 currency code, see more on currencies. Pass `XTR` \
+        for payments in Telegram Stars.
 
         :param prices: Price breakdown, a JSON-serialized list of components (e.g. product price, \
-        tax, discount, delivery cost, delivery tax, bonus, etc.).
+        tax, discount, delivery cost, delivery tax, bonus, etc.). Must contain \
+        exactly one item for payments in Telegram Stars.
 
         :param max_tip_amount: The maximum accepted amount for tips in the smallest units of the currency \
         (integer, not float/double). For example, for a maximum tip of US$ 1.45 \
         pass max_tip_amount = 145. See the exp parameter in currencies.json, it \
         shows the number of digits past the decimal point for each currency (2 for \
-        the majority of currencies). Defaults to 0.
+        the majority of currencies). Defaults to 0. Not supported for payments \
+        in Telegram Stars.
 
         :param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the smallest units \
         of the currency (integer, not float/double). At most 4 suggested tip amounts \
@@ -4031,19 +4257,26 @@ async def create_invoice_link(
 
         :param photo_height: Photo height.
 
-        :param need_name: Pass True if you require the user's full name to complete the order.
+        :param need_name: Pass True if you require the user's full name to complete the order. Ignored \
+        for payments in Telegram Stars.
 
-        :param need_phone_number: Pass True if you require the user's phone number to complete the order.
+        :param need_phone_number: Pass True if you require the user's phone number to complete the order. Ignored \
+        for payments in Telegram Stars.
 
         :param need_email: Pass True if you require the user's email address to complete the order. \
+        Ignored for payments in Telegram Stars.
 
         :param need_shipping_address: Pass True if you require the user's shipping address to complete the order. \
+        Ignored for payments in Telegram Stars.
 
-        :param send_phone_number_to_provider: Pass True if the user's phone number should be sent to the provider.
+        :param send_phone_number_to_provider: Pass True if the user's phone number should be sent to the provider. Ignored \
+        for payments in Telegram Stars.
 
-        :param send_email_to_provider: Pass True if the user's email address should be sent to the provider.
+        :param send_email_to_provider: Pass True if the user's email address should be sent to the provider. Ignored \
+        for payments in Telegram Stars.
 
-        :param is_flexible: Pass True if the final price depends on the shipping method.
+        :param is_flexible: Pass True if the final price depends on the shipping method. Ignored for \
+        payments in Telegram Stars.
         """
 
         method_response = await self.api.request_raw(
@@ -4062,9 +4295,9 @@ async def answer_shipping_query(
     ) -> Result[bool, APIError]:
         """Method `answerShippingQuery`, see the [documentation](https://core.telegram.org/bots/api#answershippingquery)
 
-        If you sent an invoice requesting a shipping address and the parameter is_flexible 
-        was specified, the Bot API will send an Update with a shipping_query field 
-        to the bot. Use this method to reply to shipping queries. On success, True 
+        If you sent an invoice requesting a shipping address and the parameter is_flexible
+        was specified, the Bot API will send an Update with a shipping_query field
+        to the bot. Use this method to reply to shipping queries. On success, True
         is returned.
 
         :param shipping_query_id: Unique identifier for the query to be answered.
@@ -4096,10 +4329,10 @@ async def answer_pre_checkout_query(
     ) -> Result[bool, APIError]:
         """Method `answerPreCheckoutQuery`, see the [documentation](https://core.telegram.org/bots/api#answerprecheckoutquery)
 
-        Once the user has confirmed their payment and shipping details, the Bot 
-        API sends the final confirmation in the form of an Update with the field pre_checkout_query. 
-        Use this method to respond to such pre-checkout queries. On success, True 
-        is returned. Note: The Bot API must receive an answer within 10 seconds after 
+        Once the user has confirmed their payment and shipping details, the Bot
+        API sends the final confirmation in the form of an Update with the field pre_checkout_query.
+        Use this method to respond to such pre-checkout queries. On success, True
+        is returned. Note: The Bot API must receive an answer within 10 seconds after
         the pre-checkout query was sent.
 
         :param pre_checkout_query_id: Unique identifier for the query to be answered.
@@ -4120,6 +4353,50 @@ async def answer_pre_checkout_query(
         )
         return full_result(method_response, bool)
 
+    async def get_star_transactions(
+        self,
+        offset: int | None = None,
+        limit: int | None = None,
+        **other: typing.Any,
+    ) -> Result[StarTransactions, APIError]:
+        """Method `getStarTransactions`, see the [documentation](https://core.telegram.org/bots/api#getstartransactions)
+
+        Returns the bot's Telegram Star transactions in chronological order.
+        On success, returns a StarTransactions object.
+
+        :param offset: Number of transactions to skip in the response.
+
+        :param limit: The maximum number of transactions to be retrieved. Values between 1-100 \
+        are accepted. Defaults to 100.
+        """
+
+        method_response = await self.api.request_raw(
+            "getStarTransactions",
+            get_params(locals()),
+        )
+        return full_result(method_response, StarTransactions)
+
+    async def refund_star_payment(
+        self,
+        user_id: int,
+        telegram_payment_charge_id: str,
+        **other: typing.Any,
+    ) -> Result[bool, APIError]:
+        """Method `refundStarPayment`, see the [documentation](https://core.telegram.org/bots/api#refundstarpayment)
+
+        Refunds a successful payment in Telegram Stars. Returns True on success.
+
+        :param user_id: Identifier of the user whose payment will be refunded.
+
+        :param telegram_payment_charge_id: Telegram payment identifier.
+        """
+
+        method_response = await self.api.request_raw(
+            "refundStarPayment",
+            get_params(locals()),
+        )
+        return full_result(method_response, bool)
+
     async def set_passport_data_errors(
         self,
         user_id: int,
@@ -4156,6 +4433,7 @@ async def send_game(
         message_thread_id: int | None = None,
         disable_notification: bool | None = None,
         protect_content: bool | None = None,
+        message_effect_id: str | None = None,
         reply_parameters: ReplyParameters | None = None,
         reply_markup: InlineKeyboardMarkup | None = None,
         **other: typing.Any,
@@ -4179,6 +4457,9 @@ async def send_game(
 
         :param protect_content: Protects the contents of the sent message from forwarding and saving.
 
+        :param message_effect_id: Unique identifier of the message effect to be added to the message; for private \
+        chats only.
+
         :param reply_parameters: Description of the message to reply to.
 
         :param reply_markup: A JSON-serialized object for an inline keyboard. If empty, one 'Play game_title' \
@@ -4204,9 +4485,9 @@ async def set_game_score(
     ) -> Result[Variative[Message, bool], APIError]:
         """Method `setGameScore`, see the [documentation](https://core.telegram.org/bots/api#setgamescore)
 
-        Use this method to set the score of the specified user in a game message. On 
-        success, if the message is not an inline message, the Message is returned, 
-        otherwise True is returned. Returns an error, if the new score is not greater 
+        Use this method to set the score of the specified user in a game message. On
+        success, if the message is not an inline message, the Message is returned,
+        otherwise True is returned. Returns an error, if the new score is not greater
         than the user's current score in the chat and force is False.
 
         :param user_id: User identifier.
@@ -4245,8 +4526,8 @@ async def get_game_high_scores(
     ) -> Result[list[GameHighScore], APIError]:
         """Method `getGameHighScores`, see the [documentation](https://core.telegram.org/bots/api#getgamehighscores)
 
-        Use this method to get data for high score tables. Will return the score of 
-        the specified user and several of their neighbors in a game. Returns an Array 
+        Use this method to get data for high score tables. Will return the score of
+        the specified user and several of their neighbors in a game. Returns an Array
         of GameHighScore objects.
 
         :param user_id: Target user id.
diff --git a/telegrinder/types/objects.py b/telegrinder/types/objects.py
index 6865f19e..d8cee911 100644
--- a/telegrinder/types/objects.py
+++ b/telegrinder/types/objects.py
@@ -1,12 +1,34 @@
+import pathlib
 import typing
 
-from fntypes.co import Some, Variative
+from fntypes.co import Variative
 
 from telegrinder.model import Model
 from telegrinder.msgspec_utils import Nothing, Option, datetime
 from telegrinder.types.enums import *  # noqa: F403
 
 
+class TransactionPartner(Model):
+    """Base object `TransactionPartner`, see the [documentation](https://core.telegram.org/bots/api#transactionpartner).
+
+    This object describes the source of a transaction, or its recipient for outgoing transactions. Currently, it can be one of
+    - TransactionPartnerUser
+    - TransactionPartnerFragment
+    - TransactionPartnerTelegramAds
+    - TransactionPartnerOther
+    """
+
+
+class RevenueWithdrawalState(Model):
+    """Base object `RevenueWithdrawalState`, see the [documentation](https://core.telegram.org/bots/api#revenuewithdrawalstate).
+
+    This object describes the state of a revenue withdrawal operation. Currently, it can be one of
+    - RevenueWithdrawalStatePending
+    - RevenueWithdrawalStateSucceeded
+    - RevenueWithdrawalStateFailed
+    """
+
+
 class ReactionType(Model):
     """Base object `ReactionType`, see the [documentation](https://core.telegram.org/bots/api#reactiontype).
 
@@ -32,6 +54,16 @@ class PassportElementError(Model):
     """
 
 
+class PaidMedia(Model):
+    """Base object `PaidMedia`, see the [documentation](https://core.telegram.org/bots/api#paidmedia).
+
+    This object describes paid media. Currently, it can be one of
+    - PaidMediaPreview
+    - PaidMediaPhoto
+    - PaidMediaVideo
+    """
+
+
 class MessageOrigin(Model):
     """Base object `MessageOrigin`, see the [documentation](https://core.telegram.org/bots/api#messageorigin).
 
@@ -75,6 +107,15 @@ class InputMessageContent(Model):
     """
 
 
+class InputPaidMedia(Model):
+    """Base object `InputPaidMedia`, see the [documentation](https://core.telegram.org/bots/api#inputpaidmedia).
+
+    This object describes the paid media to be sent. Currently, it can be one of
+    - InputPaidMediaPhoto
+    - InputPaidMediaVideo
+    """
+
+
 class InputMedia(Model):
     """Base object `InputMedia`, see the [documentation](https://core.telegram.org/bots/api#inputmedia).
 
@@ -181,32 +222,32 @@ class Update(Model):
     """
 
     update_id: int
-    """The update's unique identifier. Update identifiers start from a certain 
-    positive number and increase sequentially. This identifier becomes especially 
-    handy if you're using webhooks, since it allows you to ignore repeated updates 
-    or to restore the correct update sequence, should they get out of order. 
-    If there are no new updates for at least a week, then identifier of the next 
+    """The update's unique identifier. Update identifiers start from a certain
+    positive number and increase sequentially. This identifier becomes especially
+    handy if you're using webhooks, since it allows you to ignore repeated updates
+    or to restore the correct update sequence, should they get out of order.
+    If there are no new updates for at least a week, then identifier of the next
     update will be chosen randomly instead of sequentially."""
 
     message: Option["Message"] = Nothing
     """Optional. New incoming message of any kind - text, photo, sticker, etc."""
 
     edited_message: Option["Message"] = Nothing
-    """Optional. New version of a message that is known to the bot and was edited. 
-    This update may at times be triggered by changes to message fields that are 
+    """Optional. New version of a message that is known to the bot and was edited.
+    This update may at times be triggered by changes to message fields that are
     either unavailable or not actively used by your bot."""
 
     channel_post: Option["Message"] = Nothing
-    """Optional. New incoming channel post of any kind - text, photo, sticker, 
+    """Optional. New incoming channel post of any kind - text, photo, sticker,
     etc."""
 
     edited_channel_post: Option["Message"] = Nothing
-    """Optional. New version of a channel post that is known to the bot and was edited. 
-    This update may at times be triggered by changes to message fields that are 
+    """Optional. New version of a channel post that is known to the bot and was edited.
+    This update may at times be triggered by changes to message fields that are
     either unavailable or not actively used by your bot."""
 
     business_connection: Option["BusinessConnection"] = Nothing
-    """Optional. The bot was connected to or disconnected from a business account, 
+    """Optional. The bot was connected to or disconnected from a business account,
     or a user edited an existing connection with the bot."""
 
     business_message: Option["Message"] = Nothing
@@ -219,79 +260,81 @@ class Update(Model):
     """Optional. Messages were deleted from a connected business account."""
 
     message_reaction: Option["MessageReactionUpdated"] = Nothing
-    """Optional. A reaction to a message was changed by a user. The bot must be an 
-    administrator in the chat and must explicitly specify `message_reaction` 
-    in the list of allowed_updates to receive these updates. The update isn't 
+    """Optional. A reaction to a message was changed by a user. The bot must be an
+    administrator in the chat and must explicitly specify `message_reaction`
+    in the list of allowed_updates to receive these updates. The update isn't
     received for reactions set by bots."""
 
     message_reaction_count: Option["MessageReactionCountUpdated"] = Nothing
-    """Optional. Reactions to a message with anonymous reactions were changed. 
-    The bot must be an administrator in the chat and must explicitly specify 
-    `message_reaction_count` in the list of allowed_updates to receive these 
+    """Optional. Reactions to a message with anonymous reactions were changed.
+    The bot must be an administrator in the chat and must explicitly specify
+    `message_reaction_count` 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."""
 
     inline_query: Option["InlineQuery"] = Nothing
     """Optional. New incoming inline query."""
 
     chosen_inline_result: Option["ChosenInlineResult"] = Nothing
-    """Optional. The result of an inline query that was chosen by a user and sent 
-    to their chat partner. Please see our documentation on the feedback collecting 
+    """Optional. The result of an inline query that was chosen by a user and sent
+    to their chat partner. Please see our documentation on the feedback collecting
     for details on how to enable these updates for your bot."""
 
     callback_query: Option["CallbackQuery"] = Nothing
     """Optional. New incoming callback query."""
 
     shipping_query: Option["ShippingQuery"] = Nothing
-    """Optional. New incoming shipping query. Only for invoices with flexible 
+    """Optional. New incoming shipping query. Only for invoices with flexible
     price."""
 
     pre_checkout_query: Option["PreCheckoutQuery"] = Nothing
-    """Optional. New incoming pre-checkout query. Contains full information 
+    """Optional. New incoming pre-checkout query. Contains full information
     about checkout."""
 
     poll: Option["Poll"] = Nothing
-    """Optional. New poll state. Bots receive only updates about manually stopped 
+    """Optional. New poll state. Bots receive only updates about manually stopped
     polls and polls, which are sent by the bot."""
 
     poll_answer: Option["PollAnswer"] = Nothing
-    """Optional. A user changed their answer in a non-anonymous poll. Bots receive 
+    """Optional. A user changed their answer in a non-anonymous poll. Bots receive
     new votes only in polls that were sent by the bot itself."""
 
     my_chat_member: Option["ChatMemberUpdated"] = Nothing
-    """Optional. The bot's chat member status was updated in a chat. For private 
-    chats, this update is received only when the bot is blocked or unblocked 
+    """Optional. The bot's chat member status was updated in a chat. For private
+    chats, this update is received only when the bot is blocked or unblocked
     by the user."""
 
     chat_member: Option["ChatMemberUpdated"] = Nothing
-    """Optional. A chat member's status was updated in a chat. The bot must be an 
-    administrator in the chat and must explicitly specify `chat_member` in 
+    """Optional. A chat member's status was updated in a chat. The bot must be an
+    administrator in the chat and must explicitly specify `chat_member` in
     the list of allowed_updates to receive these updates."""
 
     chat_join_request: Option["ChatJoinRequest"] = Nothing
-    """Optional. A request to join the chat has been sent. The bot must have the can_invite_users 
+    """Optional. A request to join the chat has been sent. The bot must have the can_invite_users
     administrator right in the chat to receive these updates."""
 
     chat_boost: Option["ChatBoostUpdated"] = Nothing
-    """Optional. A chat boost was added or changed. The bot must be an administrator 
+    """Optional. A chat boost was added or changed. The bot must be an administrator
     in the chat to receive these updates."""
 
     removed_chat_boost: Option["ChatBoostRemoved"] = Nothing
-    """Optional. A boost was removed from a chat. The bot must be an administrator 
+    """Optional. A boost was removed from a chat. The bot must be an administrator
     in the chat to receive these updates."""
 
+    def __eq__(self, other: typing.Any) -> bool:
+        return isinstance(other, self.__class__) and self.update_type == other.update_type
+
     @property
-    def update_type(self) -> Option[UpdateType]:
+    def update_type(self) -> UpdateType:
         """Incoming update type."""
 
-        if update := next(
-            filter(
-                lambda x: bool(x[1]),
-                self.to_dict(exclude_fields={"update_id"}).items(),
-            ),
-            None,
-        ):
-            return Some(UpdateType(update[0]))
-        return Nothing
+        return UpdateType(
+            next(
+                filter(
+                    lambda x: bool(x[1]),
+                    self.to_dict(exclude_fields={"update_id"}).items(),
+                ),
+            )[0],
+        )
 
 
 class WebhookInfo(Model):
@@ -313,23 +356,23 @@ class WebhookInfo(Model):
     """Optional. Currently used webhook IP address."""
 
     last_error_date: Option[datetime] = Nothing
-    """Optional. Unix time for the most recent error that happened when trying 
+    """Optional. Unix time for the most recent error that happened when trying
     to deliver an update via webhook."""
 
     last_error_message: Option[str] = Nothing
-    """Optional. Error message in human-readable format for the most recent error 
+    """Optional. Error message in human-readable format for the most recent error
     that happened when trying to deliver an update via webhook."""
 
     last_synchronization_error_date: Option[datetime] = Nothing
-    """Optional. Unix time of the most recent error that happened when trying to 
+    """Optional. Unix time of the most recent error that happened when trying to
     synchronize available updates with Telegram datacenters."""
 
     max_connections: Option[int] = Nothing
-    """Optional. The maximum allowed number of simultaneous HTTPS connections 
+    """Optional. The maximum allowed number of simultaneous HTTPS connections
     to the webhook for update delivery."""
 
     allowed_updates: Option[list[str]] = Nothing
-    """Optional. A list of update types the bot is subscribed to. Defaults to all 
+    """Optional. A list of update types the bot is subscribed to. Defaults to all
     update types except chat_member."""
 
 
@@ -340,9 +383,9 @@ class User(Model):
     """
 
     id: int
-    """Unique identifier for this user or bot. This number may have more than 32 
-    significant bits and some programming languages may have difficulty/silent 
-    defects in interpreting it. But it has at most 52 significant bits, so a 64-bit 
+    """Unique identifier for this user or bot. This number may have more than 32
+    significant bits and some programming languages may have difficulty/silent
+    defects in interpreting it. But it has at most 52 significant bits, so a 64-bit
     integer or double-precision float type are safe for storing this identifier."""
 
     is_bot: bool
@@ -370,16 +413,19 @@ class User(Model):
     """Optional. True, if the bot can be invited to groups. Returned only in getMe."""
 
     can_read_all_group_messages: Option[bool] = Nothing
-    """Optional. True, if privacy mode is disabled for the bot. Returned only in 
+    """Optional. True, if privacy mode is disabled for the bot. Returned only in
     getMe."""
 
     supports_inline_queries: Option[bool] = Nothing
     """Optional. True, if the bot supports inline queries. Returned only in getMe."""
 
     can_connect_to_business: Option[bool] = Nothing
-    """Optional. True, if the bot can be connected to a Telegram Business account 
+    """Optional. True, if the bot can be connected to a Telegram Business account
     to receive its messages. Returned only in getMe."""
 
+    def __eq__(self, other: typing.Any) -> bool:
+        return isinstance(other, self.__class__) and self.id == other.id
+
     @property
     def default_accent_color(self) -> DefaultAccentColor:
         """User's or bot's accent color (non-premium)."""
@@ -400,9 +446,9 @@ class Chat(Model):
     """
 
     id: int
-    """Unique identifier for this chat. This number may have more than 32 significant 
-    bits and some programming languages may have difficulty/silent defects 
-    in interpreting it. But it has at most 52 significant bits, so a signed 64-bit 
+    """Unique identifier for this chat. This number may have more than 32 significant
+    bits and some programming languages may have difficulty/silent defects
+    in interpreting it. But it has at most 52 significant bits, so a signed 64-bit
     integer or double-precision float type are safe for storing this identifier."""
 
     type: ChatType
@@ -423,6 +469,9 @@ class Chat(Model):
     is_forum: Option[bool] = Nothing
     """Optional. True, if the supergroup chat is a forum (has topics enabled)."""
 
+    def __eq__(self, other: typing.Any) -> bool:
+        return isinstance(other, self.__class__) and self.id == other.id
+
     @property
     def full_name(self) -> Option[str]:
         """Optional. Full name (`first_name` + `last_name`) of the
@@ -438,16 +487,16 @@ class ChatFullInfo(Model):
     """
 
     id: int
-    """Unique identifier for this chat. This number may have more than 32 significant 
-    bits and some programming languages may have difficulty/silent defects 
-    in interpreting it. But it has at most 52 significant bits, so a signed 64-bit 
+    """Unique identifier for this chat. This number may have more than 32 significant
+    bits and some programming languages may have difficulty/silent defects
+    in interpreting it. But it has at most 52 significant bits, so a signed 64-bit
     integer or double-precision float type are safe for storing this identifier."""
 
     type: ChatType
     """Type of the chat, can be either `private`, `group`, `supergroup` or `channel`."""
 
     accent_color_id: int
-    """Identifier of the accent color for the chat name and backgrounds of the chat 
+    """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."""
 
     max_reaction_count: int
@@ -472,7 +521,7 @@ class ChatFullInfo(Model):
     """Optional. Chat photo."""
 
     active_usernames: Option[list[str]] = Nothing
-    """Optional. If non-empty, the list of all active chat usernames; for private 
+    """Optional. If non-empty, the list of all active chat usernames; for private
     chats, supergroups and channels."""
 
     birthdate: Option["Birthdate"] = Nothing
@@ -482,57 +531,57 @@ class ChatFullInfo(Model):
     """Optional. For private chats with business accounts, the intro of the business."""
 
     business_location: Option["BusinessLocation"] = Nothing
-    """Optional. For private chats with business accounts, the location of the 
+    """Optional. For private chats with business accounts, the location of the
     business."""
 
     business_opening_hours: Option["BusinessOpeningHours"] = Nothing
-    """Optional. For private chats with business accounts, the opening hours 
+    """Optional. For private chats with business accounts, the opening hours
     of the business."""
 
     personal_chat: Option["Chat"] = Nothing
     """Optional. For private chats, the personal channel of the user."""
 
     available_reactions: Option[list[Variative["ReactionTypeEmoji", "ReactionTypeCustomEmoji"]]] = Nothing
-    """Optional. List of available reactions allowed in the chat. If omitted, 
+    """Optional. List of available reactions allowed in the chat. If omitted,
     then all emoji reactions are allowed."""
 
     background_custom_emoji_id: Option[str] = Nothing
-    """Optional. Custom emoji identifier of the emoji chosen by the chat for the 
+    """Optional. Custom emoji identifier of the emoji chosen by the chat for the
     reply header and link preview background."""
 
     profile_accent_color_id: Option[int] = Nothing
-    """Optional. Identifier of the accent color for the chat's profile background. 
+    """Optional. Identifier of the accent color for the chat's profile background.
     See profile accent colors for more details."""
 
     profile_background_custom_emoji_id: Option[str] = Nothing
-    """Optional. Custom emoji identifier of the emoji chosen by the chat for its 
+    """Optional. Custom emoji identifier of the emoji chosen by the chat for its
     profile background."""
 
     emoji_status_custom_emoji_id: Option[str] = Nothing
-    """Optional. Custom emoji identifier of the emoji status of the chat or the 
+    """Optional. Custom emoji identifier of the emoji status of the chat or the
     other party in a private chat."""
 
     emoji_status_expiration_date: Option[datetime] = Nothing
-    """Optional. Expiration date of the emoji status of the chat or the other party 
+    """Optional. Expiration date of the emoji status of the chat or the other party
     in a private chat, in Unix time, if any."""
 
     bio: Option[str] = Nothing
     """Optional. Bio of the other party in a private chat."""
 
     has_private_forwards: Option[bool] = Nothing
-    """Optional. True, if privacy settings of the other party in the private chat 
+    """Optional. 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 user."""
 
     has_restricted_voice_and_video_messages: Option[bool] = Nothing
-    """Optional. True, if the privacy settings of the other party restrict sending 
+    """Optional. True, if the privacy settings of the other party restrict sending
     voice and video note messages in the private chat."""
 
     join_to_send_messages: Option[bool] = Nothing
-    """Optional. True, if users need to join the supergroup before they can send 
+    """Optional. True, if users need to join the supergroup before they can send
     messages."""
 
     join_by_request: Option[bool] = Nothing
-    """Optional. True, if all users directly joining the supergroup without using 
+    """Optional. True, if all users directly joining the supergroup without using
     an invite link need to be approved by supergroup administrators."""
 
     description: Option[str] = Nothing
@@ -547,31 +596,35 @@ class ChatFullInfo(Model):
     permissions: Option["ChatPermissions"] = Nothing
     """Optional. Default chat member permissions, for groups and supergroups."""
 
+    can_send_paid_media: Option[bool] = Nothing
+    """Optional. True, if paid media messages can be sent or forwarded to the channel
+    chat. The field is available only for channel chats."""
+
     slow_mode_delay: Option[int] = Nothing
-    """Optional. For supergroups, the minimum allowed delay between consecutive 
+    """Optional. For supergroups, the minimum allowed delay between consecutive
     messages sent by each unprivileged user; in seconds."""
 
     unrestrict_boost_count: Option[int] = Nothing
-    """Optional. For supergroups, the minimum number of boosts that a non-administrator 
+    """Optional. For supergroups, the minimum number of boosts that a non-administrator
     user needs to add in order to ignore slow mode and chat permissions."""
 
     message_auto_delete_time: Option[int] = Nothing
-    """Optional. The time after which all messages sent to the chat will be automatically 
+    """Optional. The time after which all messages sent to the chat will be automatically
     deleted; in seconds."""
 
     has_aggressive_anti_spam_enabled: Option[bool] = Nothing
-    """Optional. True, if aggressive anti-spam checks are enabled in the supergroup. 
+    """Optional. True, if aggressive anti-spam checks are enabled in the supergroup.
     The field is only available to chat administrators."""
 
     has_hidden_members: Option[bool] = Nothing
-    """Optional. True, if non-administrators can only get the list of bots and 
+    """Optional. True, if non-administrators can only get the list of bots and
     administrators in the chat."""
 
     has_protected_content: Option[bool] = Nothing
     """Optional. True, if messages from the chat can't be forwarded to other chats."""
 
     has_visible_history: Option[bool] = Nothing
-    """Optional. True, if new chat members will have access to old messages; available 
+    """Optional. True, if new chat members will have access to old messages; available
     only to chat administrators."""
 
     sticker_set_name: Option[str] = Nothing
@@ -581,15 +634,15 @@ class ChatFullInfo(Model):
     """Optional. True, if the bot can change the group sticker set."""
 
     custom_emoji_sticker_set_name: Option[str] = Nothing
-    """Optional. For supergroups, the name of the group's custom emoji sticker 
+    """Optional. 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."""
 
     linked_chat_id: Option[int] = Nothing
-    """Optional. Unique identifier for the linked chat, i.e. the discussion group 
-    identifier for a channel and vice versa; for supergroups and channel chats. 
-    This identifier may be greater than 32 bits and some programming languages 
-    may have difficulty/silent defects in interpreting it. But it is smaller 
-    than 52 bits, so a signed 64 bit integer or double-precision float type are 
+    """Optional. Unique identifier for the linked chat, i.e. the discussion group
+    identifier for a channel and vice versa; for supergroups and channel chats.
+    This identifier may be greater than 32 bits and some programming languages
+    may have difficulty/silent defects in interpreting it. But it is smaller
+    than 52 bits, so a signed 64 bit integer or double-precision float type are
     safe for storing this identifier."""
 
     location: Option["ChatLocation"] = Nothing
@@ -606,42 +659,42 @@ class Message(MaybeInaccessibleMessage):
     """Unique message identifier inside this chat."""
 
     date: datetime
-    """Date the message was sent in Unix time. It is always a positive number, representing 
+    """Date the message was sent in Unix time. It is always a positive number, representing
     a valid date."""
 
     chat: "Chat"
     """Chat the message belongs to."""
 
     message_thread_id: Option[int] = Nothing
-    """Optional. Unique identifier of a message thread to which the message belongs; 
+    """Optional. Unique identifier of a message thread to which the message belongs;
     for supergroups only."""
 
     from_: Option["User"] = Nothing
-    """Optional. Sender of the message; empty for messages sent to channels. For 
-    backward compatibility, the field contains a fake sender user in non-channel 
+    """Optional. Sender of the message; empty for messages sent to channels. For
+    backward compatibility, the field contains a fake sender user in non-channel
     chats, if the message was sent on behalf of a chat."""
 
     sender_chat: Option["Chat"] = Nothing
-    """Optional. Sender of the message, sent on behalf of a chat. For example, the 
-    channel itself for channel posts, the supergroup itself for messages from 
-    anonymous group administrators, the linked channel for messages automatically 
-    forwarded to the discussion group. For backward compatibility, the field 
-    from contains a fake sender user in non-channel chats, if the message was 
+    """Optional. Sender of the message, sent on behalf of a chat. For example, the
+    channel itself for channel posts, the supergroup itself for messages from
+    anonymous group administrators, the linked channel for messages automatically
+    forwarded to the discussion group. For backward compatibility, the field
+    from contains a fake sender user in non-channel chats, if the message was
     sent on behalf of a chat."""
 
     sender_boost_count: Option[int] = Nothing
-    """Optional. If the sender of the message boosted the chat, the number of boosts 
+    """Optional. If the sender of the message boosted the chat, the number of boosts
     added by the user."""
 
     sender_business_bot: Option["User"] = Nothing
-    """Optional. The bot that actually sent the message on behalf of the business 
-    account. Available only for outgoing messages sent on behalf of the connected 
+    """Optional. 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."""
 
     business_connection_id: Option[str] = Nothing
-    """Optional. 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 
+    """Optional. 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."""
 
     forward_origin: Option[
@@ -653,20 +706,20 @@ class Message(MaybeInaccessibleMessage):
     """Optional. True, if the message is sent to a forum topic."""
 
     is_automatic_forward: Option[bool] = Nothing
-    """Optional. True, if the message is a channel post that was automatically 
+    """Optional. True, if the message is a channel post that was automatically
     forwarded to the connected discussion group."""
 
     reply_to_message: Option["Message"] = Nothing
-    """Optional. For replies in the same chat and message thread, the original 
-    message. Note that the Message object in this field will not contain further 
+    """Optional. For replies in the same chat and message thread, 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."""
 
     external_reply: Option["ExternalReplyInfo"] = Nothing
-    """Optional. Information about the message that is being replied to, which 
+    """Optional. Information about the message that is being replied to, which
     may come from another chat or forum topic."""
 
     quote: Option["TextQuote"] = Nothing
-    """Optional. For replies that quote part of the original message, the quoted 
+    """Optional. For replies that quote part of the original message, the quoted
     part of the message."""
 
     reply_to_story: Option["Story"] = Nothing
@@ -682,31 +735,34 @@ class Message(MaybeInaccessibleMessage):
     """Optional. True, if the message can't be forwarded."""
 
     is_from_offline: Option[bool] = Nothing
-    """Optional. True, if the message was sent by an implicit action, for example, 
+    """Optional. 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."""
 
     media_group_id: Option[str] = Nothing
-    """Optional. The unique identifier of a media message group this message belongs 
+    """Optional. The unique identifier of a media message group this message belongs
     to."""
 
     author_signature: Option[str] = Nothing
-    """Optional. Signature of the post author for messages in channels, or the 
+    """Optional. Signature of the post author for messages in channels, or the
     custom title of an anonymous group administrator."""
 
     text: Option[str] = Nothing
     """Optional. For text messages, the actual UTF-8 text of the message."""
 
     entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. For text messages, special entities like usernames, URLs, bot 
+    """Optional. For text messages, special entities like usernames, URLs, bot
     commands, etc. that appear in the text."""
 
     link_preview_options: Option["LinkPreviewOptions"] = Nothing
-    """Optional. Options used for link preview generation for the message, if 
+    """Optional. Options used for link preview generation for the message, if
     it is a text message and link preview options were changed."""
 
+    effect_id: Option[str] = Nothing
+    """Optional. Unique identifier of the message effect added to the message."""
+
     animation: Option["Animation"] = Nothing
-    """Optional. Message is an animation, information about the animation. For 
-    backward compatibility, when this field is set, the document field will 
+    """Optional. Message is an animation, information about the animation. For
+    backward compatibility, when this field is set, the document field will
     also be set."""
 
     audio: Option["Audio"] = Nothing
@@ -715,6 +771,9 @@ class Message(MaybeInaccessibleMessage):
     document: Option["Document"] = Nothing
     """Optional. Message is a general file, information about the file."""
 
+    paid_media: Option["PaidMediaInfo"] = Nothing
+    """Optional. Message contains paid media; information about the paid media."""
+
     photo: Option[list["PhotoSize"]] = Nothing
     """Optional. Message is a photo, available sizes of the photo."""
 
@@ -734,13 +793,16 @@ class Message(MaybeInaccessibleMessage):
     """Optional. Message is a voice message, information about the file."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption for the animation, audio, document, photo, video or 
-    voice."""
+    """Optional. Caption for the animation, audio, document, paid media, photo,
+    video or voice."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. For messages with a caption, special entities like usernames, 
+    """Optional. For messages with a caption, special entities like usernames,
     URLs, bot commands, etc. that appear in the caption."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. True, if the caption must be shown above the message media."""
+
     has_media_spoiler: Option[bool] = Nothing
     """Optional. True, if the message media is covered by a spoiler animation."""
 
@@ -751,25 +813,25 @@ class Message(MaybeInaccessibleMessage):
     """Optional. Message is a dice with random value."""
 
     game: Option["Game"] = Nothing
-    """Optional. Message is a game, information about the game. More about games: 
+    """Optional. Message is a game, information about the game. More about games:
     https://core.telegram.org/bots/api#games."""
 
     poll: Option["Poll"] = Nothing
     """Optional. Message is a native poll, information about the poll."""
 
     venue: Option["Venue"] = Nothing
-    """Optional. Message is a venue, information about the venue. For backward 
+    """Optional. Message is a venue, information about the venue. For backward
     compatibility, when this field is set, the location field will also be set."""
 
     location: Option["Location"] = Nothing
     """Optional. Message is a shared location, information about the location."""
 
     new_chat_members: Option[list["User"]] = Nothing
-    """Optional. New members that were added to the group or supergroup and information 
+    """Optional. New members that were added to the group or supergroup and information
     about them (the bot itself may be one of these members)."""
 
     left_chat_member: Option["User"] = Nothing
-    """Optional. A member was removed from the group, information about them (this 
+    """Optional. A member was removed from the group, information about them (this
     member may be the bot itself)."""
 
     new_chat_title: Option[str] = Nothing
@@ -785,46 +847,50 @@ class Message(MaybeInaccessibleMessage):
     """Optional. Service message: the group has been created."""
 
     supergroup_chat_created: Option[bool] = Nothing
-    """Optional. Service message: the supergroup has been created. This field 
-    can't be received in a message coming through updates, because bot can't 
-    be a member of a supergroup when it is created. It can only be found in reply_to_message 
+    """Optional. Service message: the supergroup has been created. This field
+    can't be received in a message coming through updates, because bot can't
+    be a member of a supergroup when it is created. It can only be found in reply_to_message
     if someone replies to a very first message in a directly created supergroup."""
 
     channel_chat_created: Option[bool] = Nothing
-    """Optional. Service message: the channel has been created. This field can't 
-    be received in a message coming through updates, because bot can't be a member 
-    of a channel when it is created. It can only be found in reply_to_message 
+    """Optional. Service message: the channel has been created. This field can't
+    be received in a message coming through updates, because bot can't be a member
+    of a channel when it is created. It can only be found in reply_to_message
     if someone replies to a very first message in a channel."""
 
     message_auto_delete_timer_changed: Option["MessageAutoDeleteTimerChanged"] = Nothing
-    """Optional. Service message: auto-delete timer settings changed in the 
+    """Optional. Service message: auto-delete timer settings changed in the
     chat."""
 
     migrate_to_chat_id: Option[int] = Nothing
-    """Optional. The group has been migrated to a supergroup with the specified 
-    identifier. This number may have more than 32 significant bits and some 
-    programming languages may have difficulty/silent defects in interpreting 
-    it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision 
+    """Optional. The group has been migrated to a supergroup with the specified
+    identifier. This number may have more than 32 significant bits and some
+    programming languages may have difficulty/silent defects in interpreting
+    it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision
     float type are safe for storing this identifier."""
 
     migrate_from_chat_id: Option[int] = Nothing
-    """Optional. The supergroup has been migrated from a group with the specified 
-    identifier. This number may have more than 32 significant bits and some 
-    programming languages may have difficulty/silent defects in interpreting 
-    it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision 
+    """Optional. The supergroup has been migrated from a group with the specified
+    identifier. This number may have more than 32 significant bits and some
+    programming languages may have difficulty/silent defects in interpreting
+    it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision
     float type are safe for storing this identifier."""
 
     pinned_message: Option[Variative["Message", "InaccessibleMessage"]] = Nothing
-    """Optional. Specified message was pinned. Note that the Message object in 
-    this field will not contain further reply_to_message fields even if it 
+    """Optional. Specified message was pinned. Note that the Message object in
+    this field will not contain further reply_to_message fields even if it
     itself is a reply."""
 
     invoice: Option["Invoice"] = Nothing
-    """Optional. Message is an invoice for a payment, information about the invoice. 
+    """Optional. Message is an invoice for a payment, information about the invoice.
     More about payments: https://core.telegram.org/bots/api#payments."""
 
     successful_payment: Option["SuccessfulPayment"] = Nothing
-    """Optional. Message is a service message about a successful payment, information 
+    """Optional. Message is a service message about a successful payment, information
+    about the payment. More about payments: https://core.telegram.org/bots/api#payments."""
+
+    refunded_payment: Option["RefundedPayment"] = Nothing
+    """Optional. Message is a service message about a refunded payment, information
     about the payment. More about payments: https://core.telegram.org/bots/api#payments."""
 
     users_shared: Option["UsersShared"] = Nothing
@@ -834,19 +900,19 @@ class Message(MaybeInaccessibleMessage):
     """Optional. Service message: a chat was shared with the bot."""
 
     connected_website: Option[str] = Nothing
-    """Optional. The domain name of the website on which the user has logged in. 
+    """Optional. The domain name of the website on which the user has logged in.
     More about Telegram Login: https://core.telegram.org/widgets/login."""
 
     write_access_allowed: Option["WriteAccessAllowed"] = Nothing
-    """Optional. Service message: the user allowed the bot to write messages after 
-    adding it to the attachment or side menu, launching a Web App from a link, 
+    """Optional. Service message: the user allowed the bot to write messages after
+    adding it to the attachment or side menu, launching a Web App from a link,
     or accepting an explicit request from a Web App sent by the method requestWriteAccess."""
 
     passport_data: Option["PassportData"] = Nothing
     """Optional. Telegram Passport data."""
 
     proximity_alert_triggered: Option["ProximityAlertTriggered"] = Nothing
-    """Optional. Service message. A user in the chat triggered another user's 
+    """Optional. Service message. A user in the chat triggered another user's
     proximity alert while sharing Live Location."""
 
     boost_added: Option["ChatBoostAdded"] = Nothing
@@ -901,9 +967,16 @@ class Message(MaybeInaccessibleMessage):
     """Optional. Service message: data sent by a Web App."""
 
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
-    """Optional. Inline keyboard attached to the message. login_url buttons 
+    """Optional. Inline keyboard attached to the message. login_url buttons
     are represented as ordinary url buttons."""
 
+    def __eq__(self, other: typing.Any) -> bool:
+        return (
+            isinstance(other, self.__class__)
+            and self.message_id == other.message_id
+            and self.chat_id == other.chat_id
+        )
+
     @property
     def content_type(self) -> ContentType:
         """Type of content that the message contains."""
@@ -937,13 +1010,6 @@ def chat_title(self) -> str:
             self.chat.full_name.unwrap() if self.chat.type == ChatType.PRIVATE else self.chat.title.unwrap()
         )
 
-    def __eq__(self, other: typing.Any) -> bool:
-        return (
-            isinstance(other, self.__class__)
-            and self.message_id == other.message_id
-            and self.chat_id == other.chat_id
-        )
-
 
 class MessageId(Model):
     """Object `MessageId`, see the [documentation](https://core.telegram.org/bots/api#messageid).
@@ -968,7 +1034,7 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
     """Unique message identifier inside the chat."""
 
     date: typing.Literal[0]
-    """Always 0. The field can be used to differentiate regular and inaccessible 
+    """Always 0. The field can be used to differentiate regular and inaccessible
     messages."""
 
 
@@ -979,15 +1045,16 @@ class MessageEntity(Model):
     """
 
     type: MessageEntityType
-    """Type of the entity. Currently, can be `mention` (@username), `hashtag` 
-    (#hashtag), `cashtag` ($USD), `bot_command` (/start@jobs_bot), `url` 
-    (https://telegram.org), `email` (do-not-reply@telegram.org), `phone_number` 
-    (+1-212-555-0123), `bold` (bold text), `italic` (italic text), `underline` 
-    (underlined text), `strikethrough` (strikethrough text), `spoiler` 
-    (spoiler message), `blockquote` (block quotation), `code` (monowidth 
-    string), `pre` (monowidth block), `text_link` (for clickable text URLs), 
-    `text_mention` (for users without usernames), `custom_emoji` (for inline 
-    custom emoji stickers)."""
+    """Type of the entity. Currently, can be `mention` (@username), `hashtag`
+    (#hashtag), `cashtag` ($USD), `bot_command` (/start@jobs_bot), `url`
+    (https://telegram.org), `email` (do-not-reply@telegram.org), `phone_number`
+    (+1-212-555-0123), `bold` (bold text), `italic` (italic text), `underline`
+    (underlined text), `strikethrough` (strikethrough text), `spoiler`
+    (spoiler message), `blockquote` (block quotation), `expandable_blockquote`
+    (collapsed-by-default block quotation), `code` (monowidth string),
+    `pre` (monowidth block), `text_link` (for clickable text URLs), `text_mention`
+    (for users without usernames), `custom_emoji` (for inline custom emoji
+    stickers)."""
 
     offset: int
     """Offset in UTF-16 code units to the start of the entity."""
@@ -996,7 +1063,7 @@ class MessageEntity(Model):
     """Length of the entity in UTF-16 code units."""
 
     url: Option[str] = Nothing
-    """Optional. For `text_link` only, URL that will be opened after user taps 
+    """Optional. For `text_link` only, URL that will be opened after user taps
     on the text."""
 
     user: Option["User"] = Nothing
@@ -1006,7 +1073,7 @@ class MessageEntity(Model):
     """Optional. For `pre` only, the programming language of the entity text."""
 
     custom_emoji_id: Option[str] = Nothing
-    """Optional. For `custom_emoji` only, unique identifier of the custom emoji. 
+    """Optional. For `custom_emoji` only, unique identifier of the custom emoji.
     Use getCustomEmojiStickers to get full information about the sticker."""
 
 
@@ -1020,16 +1087,16 @@ class TextQuote(Model):
     """Text of the quoted part of a message that is replied to by the given message."""
 
     position: int
-    """Approximate quote position in the original message in UTF-16 code units 
+    """Approximate quote position in the original message in UTF-16 code units
     as specified by the sender."""
 
     entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. Special entities that appear in the quote. Currently, only bold, 
-    italic, underline, strikethrough, spoiler, and custom_emoji entities 
+    """Optional. Special entities that appear in the quote. Currently, only bold,
+    italic, underline, strikethrough, spoiler, and custom_emoji entities
     are kept in quotes."""
 
     is_manual: Option[bool] = Nothing
-    """Optional. True, if the quote was chosen manually by the message sender. 
+    """Optional. True, if the quote was chosen manually by the message sender.
     Otherwise, the quote was added automatically by the server."""
 
 
@@ -1045,15 +1112,15 @@ class ExternalReplyInfo(Model):
     """Origin of the message replied to by the given message."""
 
     chat: Option["Chat"] = Nothing
-    """Optional. Chat the original message belongs to. Available only if the chat 
+    """Optional. Chat the original message belongs to. Available only if the chat
     is a supergroup or a channel."""
 
     message_id: Option[int] = Nothing
-    """Optional. Unique message identifier inside the original chat. Available 
+    """Optional. Unique message identifier inside the original chat. Available
     only if the original chat is a supergroup or a channel."""
 
     link_preview_options: Option["LinkPreviewOptions"] = Nothing
-    """Optional. Options used for link preview generation for the original message, 
+    """Optional. Options used for link preview generation for the original message,
     if it is a text message."""
 
     animation: Option["Animation"] = Nothing
@@ -1065,6 +1132,9 @@ class ExternalReplyInfo(Model):
     document: Option["Document"] = Nothing
     """Optional. Message is a general file, information about the file."""
 
+    paid_media: Option["PaidMediaInfo"] = Nothing
+    """Optional. Message contains paid media; information about the paid media."""
+
     photo: Option[list["PhotoSize"]] = Nothing
     """Optional. Message is a photo, available sizes of the photo."""
 
@@ -1093,7 +1163,7 @@ class ExternalReplyInfo(Model):
     """Optional. Message is a dice with random value."""
 
     game: Option["Game"] = Nothing
-    """Optional. Message is a game, information about the game. More about games: 
+    """Optional. Message is a game, information about the game. More about games:
     https://core.telegram.org/bots/api#games."""
 
     giveaway: Option["Giveaway"] = Nothing
@@ -1103,7 +1173,7 @@ class ExternalReplyInfo(Model):
     """Optional. A giveaway with public winners was completed."""
 
     invoice: Option["Invoice"] = Nothing
-    """Optional. Message is an invoice for a payment, information about the invoice. 
+    """Optional. Message is an invoice for a payment, information about the invoice.
     More about payments: https://core.telegram.org/bots/api#payments."""
 
     location: Option["Location"] = Nothing
@@ -1123,32 +1193,32 @@ class ReplyParameters(Model):
     """
 
     message_id: int
-    """Identifier of the message that will be replied to in the current chat, or 
+    """Identifier of the message that will be replied to in the current chat, or
     in the chat chat_id if it is specified."""
 
     chat_id: Option[Variative[int, str]] = Nothing
-    """Optional. 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). 
+    """Optional. 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."""
 
     allow_sending_without_reply: Option[bool] = Nothing
-    """Optional. Pass True if the message should be sent even if the specified message 
-    to be replied to is not found. Always False for replies in another chat or 
+    """Optional. Pass True if the message should be sent even if the specified message
+    to be replied to is not found. Always False for replies in another chat or
     forum topic. Always True for messages sent on behalf of a business account."""
 
     quote: Option[str] = Nothing
-    """Optional. 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 
+    """Optional. 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."""
 
     quote_parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the quote. See formatting options 
+    """Optional. Mode for parsing entities in the quote. See formatting options
     for more details."""
 
     quote_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. A JSON-serialized list of special entities that appear in the 
+    """Optional. A JSON-serialized list of special entities that appear in the
     quote. It can be specified instead of quote_parse_mode."""
 
     quote_position: Option[int] = Nothing
@@ -1203,7 +1273,7 @@ class MessageOriginChat(MessageOrigin):
     """Chat that sent the message originally."""
 
     author_signature: Option[str] = Nothing
-    """Optional. For messages originally sent by an anonymous chat administrator, 
+    """Optional. For messages originally sent by an anonymous chat administrator,
     original message author signature."""
 
 
@@ -1239,7 +1309,7 @@ class PhotoSize(Model):
     """Identifier for this file, which can be used to download or reuse the file."""
 
     file_unique_id: str
-    """Unique identifier for this file, which is supposed to be the same over time 
+    """Unique identifier for this file, which is supposed to be the same over time
     and for different bots. Can't be used to download or reuse the file."""
 
     width: int
@@ -1262,31 +1332,31 @@ class Animation(Model):
     """Identifier for this file, which can be used to download or reuse the file."""
 
     file_unique_id: str
-    """Unique identifier for this file, which is supposed to be the same over time 
+    """Unique identifier for this file, which is supposed to be the same over time
     and for different bots. Can't be used to download or reuse the file."""
 
     width: int
-    """Video width as defined by sender."""
+    """Video width as defined by the sender."""
 
     height: int
-    """Video height as defined by sender."""
+    """Video height as defined by the sender."""
 
     duration: int
-    """Duration of the video in seconds as defined by sender."""
+    """Duration of the video in seconds as defined by the sender."""
 
     thumbnail: Option["PhotoSize"] = Nothing
-    """Optional. Animation thumbnail as defined by sender."""
+    """Optional. Animation thumbnail as defined by the sender."""
 
     file_name: Option[str] = Nothing
-    """Optional. Original animation filename as defined by sender."""
+    """Optional. Original animation filename as defined by the sender."""
 
     mime_type: Option[str] = Nothing
-    """Optional. MIME type of the file as defined by sender."""
+    """Optional. MIME type of the file as defined by the sender."""
 
     file_size: Option[int] = Nothing
-    """Optional. File size in bytes. It can be bigger than 2^31 and some programming 
-    languages may have difficulty/silent defects in interpreting it. But 
-    it has at most 52 significant bits, so a signed 64-bit integer or double-precision 
+    """Optional. File size in bytes. It can be bigger than 2^31 and some programming
+    languages may have difficulty/silent defects in interpreting it. But
+    it has at most 52 significant bits, so a signed 64-bit integer or double-precision
     float type are safe for storing this value."""
 
 
@@ -1300,28 +1370,28 @@ class Audio(Model):
     """Identifier for this file, which can be used to download or reuse the file."""
 
     file_unique_id: str
-    """Unique identifier for this file, which is supposed to be the same over time 
+    """Unique identifier for this file, which is supposed to be the same over time
     and for different bots. Can't be used to download or reuse the file."""
 
     duration: int
-    """Duration of the audio in seconds as defined by sender."""
+    """Duration of the audio in seconds as defined by the sender."""
 
     performer: Option[str] = Nothing
-    """Optional. Performer of the audio as defined by sender or by audio tags."""
+    """Optional. Performer of the audio as defined by the sender or by audio tags."""
 
     title: Option[str] = Nothing
-    """Optional. Title of the audio as defined by sender or by audio tags."""
+    """Optional. Title of the audio as defined by the sender or by audio tags."""
 
     file_name: Option[str] = Nothing
-    """Optional. Original filename as defined by sender."""
+    """Optional. Original filename as defined by the sender."""
 
     mime_type: Option[str] = Nothing
-    """Optional. MIME type of the file as defined by sender."""
+    """Optional. MIME type of the file as defined by the sender."""
 
     file_size: Option[int] = Nothing
-    """Optional. File size in bytes. It can be bigger than 2^31 and some programming 
-    languages may have difficulty/silent defects in interpreting it. But 
-    it has at most 52 significant bits, so a signed 64-bit integer or double-precision 
+    """Optional. File size in bytes. It can be bigger than 2^31 and some programming
+    languages may have difficulty/silent defects in interpreting it. But
+    it has at most 52 significant bits, so a signed 64-bit integer or double-precision
     float type are safe for storing this value."""
 
     thumbnail: Option["PhotoSize"] = Nothing
@@ -1338,22 +1408,22 @@ class Document(Model):
     """Identifier for this file, which can be used to download or reuse the file."""
 
     file_unique_id: str
-    """Unique identifier for this file, which is supposed to be the same over time 
+    """Unique identifier for this file, which is supposed to be the same over time
     and for different bots. Can't be used to download or reuse the file."""
 
     thumbnail: Option["PhotoSize"] = Nothing
-    """Optional. Document thumbnail as defined by sender."""
+    """Optional. Document thumbnail as defined by the sender."""
 
     file_name: Option[str] = Nothing
-    """Optional. Original filename as defined by sender."""
+    """Optional. Original filename as defined by the sender."""
 
     mime_type: Option[str] = Nothing
-    """Optional. MIME type of the file as defined by sender."""
+    """Optional. MIME type of the file as defined by the sender."""
 
     file_size: Option[int] = Nothing
-    """Optional. File size in bytes. It can be bigger than 2^31 and some programming 
-    languages may have difficulty/silent defects in interpreting it. But 
-    it has at most 52 significant bits, so a signed 64-bit integer or double-precision 
+    """Optional. File size in bytes. It can be bigger than 2^31 and some programming
+    languages may have difficulty/silent defects in interpreting it. But
+    it has at most 52 significant bits, so a signed 64-bit integer or double-precision
     float type are safe for storing this value."""
 
 
@@ -1380,31 +1450,31 @@ class Video(Model):
     """Identifier for this file, which can be used to download or reuse the file."""
 
     file_unique_id: str
-    """Unique identifier for this file, which is supposed to be the same over time 
+    """Unique identifier for this file, which is supposed to be the same over time
     and for different bots. Can't be used to download or reuse the file."""
 
     width: int
-    """Video width as defined by sender."""
+    """Video width as defined by the sender."""
 
     height: int
-    """Video height as defined by sender."""
+    """Video height as defined by the sender."""
 
     duration: int
-    """Duration of the video in seconds as defined by sender."""
+    """Duration of the video in seconds as defined by the sender."""
 
     thumbnail: Option["PhotoSize"] = Nothing
     """Optional. Video thumbnail."""
 
     file_name: Option[str] = Nothing
-    """Optional. Original filename as defined by sender."""
+    """Optional. Original filename as defined by the sender."""
 
     mime_type: Option[str] = Nothing
-    """Optional. MIME type of the file as defined by sender."""
+    """Optional. MIME type of the file as defined by the sender."""
 
     file_size: Option[int] = Nothing
-    """Optional. File size in bytes. It can be bigger than 2^31 and some programming 
-    languages may have difficulty/silent defects in interpreting it. But 
-    it has at most 52 significant bits, so a signed 64-bit integer or double-precision 
+    """Optional. File size in bytes. It can be bigger than 2^31 and some programming
+    languages may have difficulty/silent defects in interpreting it. But
+    it has at most 52 significant bits, so a signed 64-bit integer or double-precision
     float type are safe for storing this value."""
 
 
@@ -1418,14 +1488,15 @@ class VideoNote(Model):
     """Identifier for this file, which can be used to download or reuse the file."""
 
     file_unique_id: str
-    """Unique identifier for this file, which is supposed to be the same over time 
+    """Unique identifier for this file, which is supposed to be the same over time
     and for different bots. Can't be used to download or reuse the file."""
 
     length: int
-    """Video width and height (diameter of the video message) as defined by sender."""
+    """Video width and height (diameter of the video message) as defined by the
+    sender."""
 
     duration: int
-    """Duration of the video in seconds as defined by sender."""
+    """Duration of the video in seconds as defined by the sender."""
 
     thumbnail: Option["PhotoSize"] = Nothing
     """Optional. Video thumbnail."""
@@ -1444,22 +1515,80 @@ class Voice(Model):
     """Identifier for this file, which can be used to download or reuse the file."""
 
     file_unique_id: str
-    """Unique identifier for this file, which is supposed to be the same over time 
+    """Unique identifier for this file, which is supposed to be the same over time
     and for different bots. Can't be used to download or reuse the file."""
 
     duration: int
-    """Duration of the audio in seconds as defined by sender."""
+    """Duration of the audio in seconds as defined by the sender."""
 
     mime_type: Option[str] = Nothing
-    """Optional. MIME type of the file as defined by sender."""
+    """Optional. MIME type of the file as defined by the sender."""
 
     file_size: Option[int] = Nothing
-    """Optional. File size in bytes. It can be bigger than 2^31 and some programming 
-    languages may have difficulty/silent defects in interpreting it. But 
-    it has at most 52 significant bits, so a signed 64-bit integer or double-precision 
+    """Optional. File size in bytes. It can be bigger than 2^31 and some programming
+    languages may have difficulty/silent defects in interpreting it. But
+    it has at most 52 significant bits, so a signed 64-bit integer or double-precision
     float type are safe for storing this value."""
 
 
+class PaidMediaInfo(Model):
+    """Object `PaidMediaInfo`, see the [documentation](https://core.telegram.org/bots/api#paidmediainfo).
+
+    Describes the paid media added to a message.
+    """
+
+    star_count: int
+    """The number of Telegram Stars that must be paid to buy access to the media."""
+
+    paid_media: list[Variative["PaidMediaPreview", "PaidMediaPhoto", "PaidMediaVideo"]]
+    """Information about the paid media."""
+
+
+class PaidMediaPreview(PaidMedia):
+    """Object `PaidMediaPreview`, see the [documentation](https://core.telegram.org/bots/api#paidmediapreview).
+
+    The paid media isn't available before the payment.
+    """
+
+    type: typing.Literal["preview"]
+    """Type of the paid media, always `preview`."""
+
+    width: Option[int] = Nothing
+    """Optional. Media width as defined by the sender."""
+
+    height: Option[int] = Nothing
+    """Optional. Media height as defined by the sender."""
+
+    duration: Option[int] = Nothing
+    """Optional. Duration of the media in seconds as defined by the sender."""
+
+
+class PaidMediaPhoto(PaidMedia):
+    """Object `PaidMediaPhoto`, see the [documentation](https://core.telegram.org/bots/api#paidmediaphoto).
+
+    The paid media is a photo.
+    """
+
+    type: typing.Literal["photo"]
+    """Type of the paid media, always `photo`."""
+
+    photo: list["PhotoSize"]
+    """The photo."""
+
+
+class PaidMediaVideo(PaidMedia):
+    """Object `PaidMediaVideo`, see the [documentation](https://core.telegram.org/bots/api#paidmediavideo).
+
+    The paid media is a video.
+    """
+
+    type: typing.Literal["video"]
+    """Type of the paid media, always `video`."""
+
+    video: "Video"
+    """The video."""
+
+
 class Contact(Model):
     """Object `Contact`, see the [documentation](https://core.telegram.org/bots/api#contact).
 
@@ -1476,9 +1605,9 @@ class Contact(Model):
     """Optional. Contact's last name."""
 
     user_id: Option[int] = Nothing
-    """Optional. Contact's user identifier in Telegram. This number may have 
-    more than 32 significant bits and some programming languages may have difficulty/silent 
-    defects in interpreting it. But it has at most 52 significant bits, so a 64-bit 
+    """Optional. Contact's user identifier in Telegram. This number may have
+    more than 32 significant bits and some programming languages may have difficulty/silent
+    defects in interpreting it. But it has at most 52 significant bits, so a 64-bit
     integer or double-precision float type are safe for storing this identifier."""
 
     vcard: Option[str] = Nothing
@@ -1495,7 +1624,7 @@ class Dice(Model):
     """Emoji on which the dice throw animation is based."""
 
     value: int
-    """Value of the dice, 1-6 for `🎲`, `🎯` and `🎳` base emoji, 1-5 for `🏀` and `⚽` base 
+    """Value of the dice, 1-6 for `🎲`, `🎯` and `🎳` base emoji, 1-5 for `🏀` and `⚽` base
     emoji, 1-64 for `🎰` base emoji."""
 
 
@@ -1512,25 +1641,25 @@ class PollOption(Model):
     """Number of users that voted for this option."""
 
     text_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. Special entities that appear in the option text. Currently, 
+    """Optional. Special entities that appear in the option text. Currently,
     only custom emoji entities are allowed in poll option texts."""
 
 
 class InputPollOption(Model):
     """Object `InputPollOption`, see the [documentation](https://core.telegram.org/bots/api#inputpolloption).
 
-    This object contains information about one answer option in a poll to send.
+    This object contains information about one answer option in a poll to be sent.
     """
 
     text: str
     """Option text, 1-100 characters."""
 
     text_parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the text. See formatting options 
+    """Optional. Mode for parsing entities in the text. See formatting options
     for more details. Currently, only custom emoji entities are allowed."""
 
     text_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. A JSON-serialized list of special entities that appear in the 
+    """Optional. A JSON-serialized list of special entities that appear in the
     poll option text. It can be specified instead of text_parse_mode."""
 
 
@@ -1544,14 +1673,14 @@ class PollAnswer(Model):
     """Unique poll identifier."""
 
     option_ids: list[int]
-    """0-based identifiers of chosen answer options. May be empty if the vote was 
+    """0-based identifiers of chosen answer options. May be empty if the vote was
     retracted."""
 
     voter_chat: Option["Chat"] = Nothing
     """Optional. The chat that changed the answer to the poll, if the voter is anonymous."""
 
     user: Option["User"] = Nothing
-    """Optional. The user that changed the answer to the poll, if the voter isn't 
+    """Optional. The user that changed the answer to the poll, if the voter isn't
     anonymous."""
 
 
@@ -1586,27 +1715,27 @@ class Poll(Model):
     """True, if the poll allows multiple answers."""
 
     question_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. Special entities that appear in the question. Currently, only 
+    """Optional. Special entities that appear in the question. Currently, only
     custom emoji entities are allowed in poll questions."""
 
     correct_option_id: Option[int] = Nothing
-    """Optional. 0-based identifier of the correct answer option. Available 
-    only for polls in the quiz mode, which are closed, or was sent (not forwarded) 
+    """Optional. 0-based identifier of the correct answer option. Available
+    only for polls in the quiz mode, which are closed, or was sent (not forwarded)
     by the bot or to the private chat with the bot."""
 
     explanation: Option[str] = Nothing
-    """Optional. Text that is shown when a user chooses an incorrect answer or taps 
+    """Optional. Text that is shown when a user chooses an incorrect answer or taps
     on the lamp icon in a quiz-style poll, 0-200 characters."""
 
     explanation_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. Special entities like usernames, URLs, bot commands, etc. that 
+    """Optional. Special entities like usernames, URLs, bot commands, etc. that
     appear in the explanation."""
 
     open_period: Option[int] = Nothing
     """Optional. Amount of time in seconds the poll will be active after creation."""
 
     close_date: Option[datetime] = Nothing
-    """Optional. Point in time (Unix timestamp) when the poll will be automatically 
+    """Optional. Point in time (Unix timestamp) when the poll will be automatically
     closed."""
 
 
@@ -1617,25 +1746,25 @@ class Location(Model):
     """
 
     latitude: float
-    """Latitude as defined by sender."""
+    """Latitude as defined by the sender."""
 
     longitude: float
-    """Longitude as defined by sender."""
+    """Longitude as defined by the sender."""
 
     horizontal_accuracy: Option[float] = Nothing
-    """Optional. The radius of uncertainty for the location, measured in meters; 
+    """Optional. The radius of uncertainty for the location, measured in meters;
     0-1500."""
 
     live_period: Option[int] = Nothing
-    """Optional. Time relative to the message sending date, during which the location 
+    """Optional. Time relative to the message sending date, during which the location
     can be updated; in seconds. For active live locations only."""
 
     heading: Option[int] = Nothing
-    """Optional. The direction in which user is moving, in degrees; 1-360. For 
+    """Optional. The direction in which user is moving, in degrees; 1-360. For
     active live locations only."""
 
     proximity_alert_radius: Option[int] = Nothing
-    """Optional. The maximum distance for proximity alerts about approaching 
+    """Optional. The maximum distance for proximity alerts about approaching
     another chat member, in meters. For sent live locations only."""
 
 
@@ -1658,7 +1787,7 @@ class Venue(Model):
     """Optional. Foursquare identifier of the venue."""
 
     foursquare_type: Option[str] = Nothing
-    """Optional. Foursquare type of the venue. (For example, `arts_entertainment/default`, 
+    """Optional. Foursquare type of the venue. (For example, `arts_entertainment/default`,
     `arts_entertainment/aquarium` or `food/icecream`.)."""
 
     google_place_id: Option[str] = Nothing
@@ -1678,7 +1807,7 @@ class WebAppData(Model):
     """The data. Be aware that a bad client can send arbitrary data in this field."""
 
     button_text: str
-    """Text of the web_app keyboard button from which the Web App was opened. Be 
+    """Text of the web_app keyboard button from which the Web App was opened. Be
     aware that a bad client can send arbitrary data in this field."""
 
 
@@ -1760,7 +1889,7 @@ class BackgroundFillFreeformGradient(BackgroundFill):
     """Type of the background fill, always `freeform_gradient`."""
 
     colors: list[int]
-    """A list of the 3 or 4 base colors that are used to generate the freeform gradient 
+    """A list of the 3 or 4 base colors that are used to generate the freeform gradient
     in the RGB24 format."""
 
 
@@ -1796,7 +1925,7 @@ class BackgroundTypeWallpaper(BackgroundType):
     """Dimming of the background in dark themes, as a percentage; 0-100."""
 
     is_blurred: Option[bool] = Nothing
-    """Optional. True, if the wallpaper is downscaled to fit in a 450x450 square 
+    """Optional. True, if the wallpaper is downscaled to fit in a 450x450 square
     and then box-blurred with radius 12."""
 
     is_moving: Option[bool] = Nothing
@@ -1822,7 +1951,7 @@ class BackgroundTypePattern(BackgroundType):
     """Intensity of the pattern when it is shown above the filled background; 0-100."""
 
     is_inverted: Option[bool] = Nothing
-    """Optional. True, if the background fill must be applied only to the pattern 
+    """Optional. True, if the background fill must be applied only to the pattern
     itself. All other pixels are black in this case. For dark themes only."""
 
     is_moving: Option[bool] = Nothing
@@ -1887,7 +2016,7 @@ class ForumTopicEdited(Model):
     """Optional. New name of the topic, if it was edited."""
 
     icon_custom_emoji_id: Option[str] = Nothing
-    """Optional. New identifier of the custom emoji shown as the topic icon, if 
+    """Optional. New identifier of the custom emoji shown as the topic icon, if
     it was edited; an empty string if the icon was removed."""
 
 
@@ -1919,11 +2048,11 @@ class SharedUser(Model):
     """
 
     user_id: int
-    """Identifier of the shared user. This number may have more than 32 significant 
-    bits and some programming languages may have difficulty/silent defects 
-    in interpreting it. But it has at most 52 significant bits, so 64-bit integers 
-    or double-precision float types are safe for storing these identifiers. 
-    The bot may not have access to the user and could be unable to use this identifier, 
+    """Identifier of the shared user. This number may have more than 32 significant
+    bits and some programming languages may have difficulty/silent defects
+    in interpreting it. But it has at most 52 significant bits, so 64-bit integers
+    or double-precision float types are safe for storing these identifiers.
+    The bot may not have access to the user and could be unable to use this identifier,
     unless the user is already known to the bot by some other means."""
 
     first_name: Option[str] = Nothing
@@ -1936,7 +2065,7 @@ class SharedUser(Model):
     """Optional. Username of the user, if the username was requested by the bot."""
 
     photo: Option[list["PhotoSize"]] = Nothing
-    """Optional. Available sizes of the chat photo, if the photo was requested 
+    """Optional. Available sizes of the chat photo, if the photo was requested
     by the bot."""
 
 
@@ -1963,22 +2092,22 @@ class ChatShared(Model):
     """Identifier of the request."""
 
     chat_id: int
-    """Identifier of the shared chat. This number may have more than 32 significant 
-    bits and some programming languages may have difficulty/silent defects 
-    in interpreting it. But it has at most 52 significant bits, so a 64-bit integer 
-    or double-precision float type are safe for storing this identifier. The 
-    bot may not have access to the chat and could be unable to use this identifier, 
+    """Identifier of the shared chat. This number may have more than 32 significant
+    bits and some programming languages may have difficulty/silent defects
+    in interpreting it. But it has at most 52 significant bits, so a 64-bit integer
+    or double-precision float type are safe for storing this identifier. The
+    bot may not have access to the chat and could be unable to use this identifier,
     unless the chat is already known to the bot by some other means."""
 
     title: Option[str] = Nothing
     """Optional. Title of the chat, if the title was requested by the bot."""
 
     username: Option[str] = Nothing
-    """Optional. Username of the chat, if the username was requested by the bot 
+    """Optional. Username of the chat, if the username was requested by the bot
     and available."""
 
     photo: Option[list["PhotoSize"]] = Nothing
-    """Optional. Available sizes of the chat photo, if the photo was requested 
+    """Optional. Available sizes of the chat photo, if the photo was requested
     by the bot."""
 
 
@@ -1989,15 +2118,15 @@ class WriteAccessAllowed(Model):
     """
 
     from_request: Option[bool] = Nothing
-    """Optional. True, if the access was granted after the user accepted an explicit 
+    """Optional. True, if the access was granted after the user accepted an explicit
     request from a Web App sent by the method requestWriteAccess."""
 
     web_app_name: Option[str] = Nothing
-    """Optional. Name of the Web App, if the access was granted when the Web App was 
+    """Optional. Name of the Web App, if the access was granted when the Web App was
     launched from a link."""
 
     from_attachment_menu: Option[bool] = Nothing
-    """Optional. True, if the access was granted when the bot was added to the attachment 
+    """Optional. True, if the access was granted when the bot was added to the attachment
     or side menu."""
 
 
@@ -2008,7 +2137,7 @@ class VideoChatScheduled(Model):
     """
 
     start_date: datetime
-    """Point in time (Unix timestamp) when the video chat is supposed to be started 
+    """Point in time (Unix timestamp) when the video chat is supposed to be started
     by a chat administrator."""
 
 
@@ -2062,7 +2191,7 @@ class Giveaway(Model):
     """The number of users which are supposed to be selected as winners of the giveaway."""
 
     only_new_members: Option[bool] = Nothing
-    """Optional. True, if only users who join the chats after the giveaway started 
+    """Optional. True, if only users who join the chats after the giveaway started
     should be eligible to win."""
 
     has_public_winners: Option[bool] = Nothing
@@ -2072,13 +2201,13 @@ class Giveaway(Model):
     """Optional. Description of additional giveaway prize."""
 
     country_codes: Option[list[str]] = Nothing
-    """Optional. A list of two-letter 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 
+    """Optional. A list of two-letter 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."""
 
     premium_subscription_month_count: Option[int] = Nothing
-    """Optional. The number of months the Telegram Premium subscription won from 
+    """Optional. The number of months the Telegram Premium subscription won from
     the giveaway will be active for."""
 
 
@@ -2104,22 +2233,22 @@ class GiveawayWinners(Model):
     """List of up to 100 winners of the giveaway."""
 
     additional_chat_count: Option[int] = Nothing
-    """Optional. The number of other chats the user had to join in order to be eligible 
+    """Optional. The number of other chats the user had to join in order to be eligible
     for the giveaway."""
 
     premium_subscription_month_count: Option[int] = Nothing
-    """Optional. The number of months the Telegram Premium subscription won from 
+    """Optional. The number of months the Telegram Premium subscription won from
     the giveaway will be active for."""
 
     unclaimed_prize_count: Option[int] = Nothing
     """Optional. Number of undistributed prizes."""
 
     only_new_members: Option[bool] = Nothing
-    """Optional. True, if only users who had joined the chats after the giveaway 
+    """Optional. True, if only users who had joined the chats after the giveaway
     started were eligible to win."""
 
     was_refunded: Option[bool] = Nothing
-    """Optional. True, if the giveaway was canceled because the payment for it 
+    """Optional. True, if the giveaway was canceled because the payment for it
     was refunded."""
 
     prize_description: Option[str] = Nothing
@@ -2152,21 +2281,21 @@ class LinkPreviewOptions(Model):
     """Optional. True, if the link preview is disabled."""
 
     url: Option[str] = Nothing
-    """Optional. URL to use for the link preview. If empty, then the first URL found 
+    """Optional. URL to use for the link preview. If empty, then the first URL found
     in the message text will be used."""
 
     prefer_small_media: Option[bool] = Nothing
-    """Optional. True, if the media in the link preview is supposed to be shrunk; 
-    ignored if the URL isn't explicitly specified or media size change isn't 
+    """Optional. True, if the media in the link preview is supposed to be shrunk;
+    ignored if the URL isn't explicitly specified or media size change isn't
     supported for the preview."""
 
     prefer_large_media: Option[bool] = Nothing
-    """Optional. True, if the media in the link preview is supposed to be enlarged; 
-    ignored if the URL isn't explicitly specified or media size change isn't 
+    """Optional. True, if the media in the link preview is supposed to be enlarged;
+    ignored if the URL isn't explicitly specified or media size change isn't
     supported for the preview."""
 
     show_above_text: Option[bool] = Nothing
-    """Optional. True, if the link preview must be shown above the message text; 
+    """Optional. True, if the link preview must be shown above the message text;
     otherwise, the link preview will be shown below the message text."""
 
 
@@ -2193,17 +2322,17 @@ class File(Model):
     """Identifier for this file, which can be used to download or reuse the file."""
 
     file_unique_id: str
-    """Unique identifier for this file, which is supposed to be the same over time 
+    """Unique identifier for this file, which is supposed to be the same over time
     and for different bots. Can't be used to download or reuse the file."""
 
     file_size: Option[int] = Nothing
-    """Optional. File size in bytes. It can be bigger than 2^31 and some programming 
-    languages may have difficulty/silent defects in interpreting it. But 
-    it has at most 52 significant bits, so a signed 64-bit integer or double-precision 
+    """Optional. File size in bytes. It can be bigger than 2^31 and some programming
+    languages may have difficulty/silent defects in interpreting it. But
+    it has at most 52 significant bits, so a signed 64-bit integer or double-precision
     float type are safe for storing this value."""
 
     file_path: Option[str] = Nothing
-    """Optional. File path. Use https://api.telegram.org/file/bot<token>/<file_path> 
+    """Optional. File path. Use https://api.telegram.org/file/bot<token>/<file_path>
     to get the file."""
 
 
@@ -2214,7 +2343,7 @@ class WebAppInfo(Model):
     """
 
     url: str
-    """An HTTPS URL of a Web App to be opened with additional data as specified in 
+    """An HTTPS URL of a Web App to be opened with additional data as specified in
     Initializing Web Apps."""
 
 
@@ -2228,32 +2357,32 @@ class ReplyKeyboardMarkup(Model):
     """Array of button rows, each represented by an Array of KeyboardButton objects."""
 
     is_persistent: Option[bool] = Nothing
-    """Optional. Requests clients to always show the keyboard when the regular 
-    keyboard is hidden. Defaults to false, in which case the custom keyboard 
+    """Optional. 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."""
 
     resize_keyboard: Option[bool] = Nothing
-    """Optional. 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 
+    """Optional. 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."""
 
     one_time_keyboard: Option[bool] = Nothing
-    """Optional. Requests clients to hide the keyboard as soon as it's been used. 
-    The keyboard will still be available, but clients will automatically display 
-    the usual letter-keyboard in the chat - the user can press a special button 
+    """Optional. Requests clients to hide the keyboard as soon as it's been used.
+    The keyboard will still be available, but clients will automatically 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."""
 
     input_field_placeholder: Option[str] = Nothing
-    """Optional. The placeholder to be shown in the input field when the keyboard 
+    """Optional. The placeholder to be shown in the input field when the keyboard
     is active; 1-64 characters."""
 
     selective: Option[bool] = Nothing
-    """Optional. Use this parameter if you want to show the keyboard to specific 
-    users only. Targets: 1) users that are @mentioned in the text of the Message 
-    object; 2) if the bot's message is a reply to a message in the same chat and 
-    forum topic, sender of the original message. Example: A user requests to 
-    change the bot's language, bot replies to the request with a keyboard to 
+    """Optional. Use this parameter if you want to show the keyboard to specific
+    users only. Targets: 1) users that are @mentioned in the text of the Message
+    object; 2) if the bot's message is a reply to a message in the same chat and
+    forum topic, sender of the original message. Example: A user requests to
+    change the bot's language, bot replies to the request with a keyboard to
     select the new language. Other users in the group don't see the keyboard."""
 
     @property
@@ -2266,39 +2395,39 @@ def empty_markup(self) -> "ReplyKeyboardRemove":
 class KeyboardButton(Model):
     """Object `KeyboardButton`, see the [documentation](https://core.telegram.org/bots/api#keyboardbutton).
 
-    This object represents one button of the reply keyboard. For simple text buttons, String can be used instead of this object to specify the button text. The optional fields web_app, request_users, request_chat, request_contact, request_location, and request_poll are mutually exclusive.
+    This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, String can be used instead of this object to specify the button text.
     Note: request_users and request_chat options will only work in Telegram versions released after 3 February, 2023. Older clients will display unsupported message.
     """
 
     text: str
-    """Text of the button. If none of the optional fields are used, it will be sent 
+    """Text of the button. If none of the optional fields are used, it will be sent
     as a message when the button is pressed."""
 
     request_users: Option["KeyboardButtonRequestUsers"] = Nothing
-    """Optional. 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` 
+    """Optional. 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."""
 
     request_chat: Option["KeyboardButtonRequestChat"] = Nothing
-    """Optional. If specified, pressing the button will open a list of suitable 
-    chats. Tapping on a chat will send its identifier to the bot in a `chat_shared` 
+    """Optional. If specified, pressing the button will open a list of suitable
+    chats. Tapping on a chat will send its identifier to the bot in a `chat_shared`
     service message. Available in private chats only."""
 
     request_contact: Option[bool] = Nothing
-    """Optional. If True, the user's phone number will be sent as a contact when 
+    """Optional. If True, the user's phone number will be sent as a contact when
     the button is pressed. Available in private chats only."""
 
     request_location: Option[bool] = Nothing
-    """Optional. If True, the user's current location will be sent when the button 
+    """Optional. If True, the user's current location will be sent when the button
     is pressed. Available in private chats only."""
 
     request_poll: Option["KeyboardButtonPollType"] = Nothing
-    """Optional. If specified, the user will be asked to create a poll and send it 
+    """Optional. 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 chats only."""
 
     web_app: Option["WebAppInfo"] = Nothing
-    """Optional. If specified, the described Web App will be launched when the 
-    button is pressed. The Web App will be able to send a `web_app_data` service 
+    """Optional. If specified, the described Web App will be launched when the
+    button is pressed. The Web App will be able to send a `web_app_data` service
     message. Available in private chats only."""
 
 
@@ -2309,19 +2438,19 @@ class KeyboardButtonRequestUsers(Model):
     """
 
     request_id: int
-    """Signed 32-bit identifier of the request that will be received back in the 
+    """Signed 32-bit identifier of the request that will be received back in the
     UsersShared object. Must be unique within the message."""
 
     user_is_bot: Option[bool] = Nothing
-    """Optional. Pass True to request bots, pass False to request regular users. 
+    """Optional. Pass True to request bots, pass False to request regular users.
     If not specified, no additional restrictions are applied."""
 
     user_is_premium: Option[bool] = Nothing
-    """Optional. Pass True to request premium users, pass False to request non-premium 
+    """Optional. Pass True to request premium users, pass False to request non-premium
     users. If not specified, no additional restrictions are applied."""
 
     max_quantity: Option[int] = Nothing
-    """Optional. The maximum number of users to be selected; 1-10. Defaults to 
+    """Optional. The maximum number of users to be selected; 1-10. Defaults to
     1."""
 
     request_name: Option[bool] = Nothing
@@ -2341,38 +2470,38 @@ class KeyboardButtonRequestChat(Model):
     """
 
     request_id: int
-    """Signed 32-bit identifier of the request, which will be received back in 
+    """Signed 32-bit identifier of the request, which will be received back in
     the ChatShared object. Must be unique within the message."""
 
     chat_is_channel: bool
-    """Pass True to request a channel chat, pass False to request a group or a supergroup 
+    """Pass True to request a channel chat, pass False to request a group or a supergroup
     chat."""
 
     chat_is_forum: Option[bool] = Nothing
-    """Optional. Pass True to request a forum supergroup, pass False to request 
+    """Optional. Pass True to request a forum supergroup, pass False to request
     a non-forum chat. If not specified, no additional restrictions are applied."""
 
     chat_has_username: Option[bool] = Nothing
-    """Optional. Pass True to request a supergroup or a channel with a username, 
-    pass False to request a chat without a username. If not specified, no additional 
+    """Optional. Pass True to request a supergroup or a channel with a username,
+    pass False to request a chat without a username. If not specified, no additional
     restrictions are applied."""
 
     chat_is_created: Option[bool] = Nothing
-    """Optional. Pass True to request a chat owned by the user. Otherwise, no additional 
+    """Optional. Pass True to request a chat owned by the user. Otherwise, no additional
     restrictions are applied."""
 
     user_administrator_rights: Option["ChatAdministratorRights"] = Nothing
-    """Optional. A JSON-serialized object listing the required administrator 
-    rights of the user in the chat. The rights must be a superset of bot_administrator_rights. 
+    """Optional. A JSON-serialized object listing the required administrator
+    rights of the user in the chat. The rights must be a superset of bot_administrator_rights.
     If not specified, no additional restrictions are applied."""
 
     bot_administrator_rights: Option["ChatAdministratorRights"] = Nothing
-    """Optional. A JSON-serialized object listing the required administrator 
-    rights of the bot in the chat. The rights must be a subset of user_administrator_rights. 
+    """Optional. A JSON-serialized object listing the required administrator
+    rights of the bot in the chat. The rights must be a subset of user_administrator_rights.
     If not specified, no additional restrictions are applied."""
 
     bot_is_member: Option[bool] = Nothing
-    """Optional. Pass True to request a chat with the bot as a member. Otherwise, 
+    """Optional. Pass True to request a chat with the bot as a member. Otherwise,
     no additional restrictions are applied."""
 
     request_title: Option[bool] = Nothing
@@ -2392,8 +2521,8 @@ class KeyboardButtonPollType(Model):
     """
 
     type: Option[typing.Literal["quiz", "regular"]] = Nothing
-    """Optional. If quiz is passed, the user will be allowed to create only polls 
-    in the quiz mode. If regular is passed, only regular polls will be allowed. 
+    """Optional. If quiz is passed, the user will be allowed to create only polls
+    in the quiz mode. If regular is passed, only regular polls will be allowed.
     Otherwise, the user will be allowed to create a poll of any type."""
 
 
@@ -2404,17 +2533,17 @@ class ReplyKeyboardRemove(Model):
     """
 
     remove_keyboard: bool
-    """Requests clients to remove the custom keyboard (user will not be able to 
-    summon this keyboard; if you want to hide the keyboard from sight but keep 
+    """Requests clients to remove the custom keyboard (user will not be able to
+    summon this keyboard; if you want to hide the keyboard from sight but keep
     it accessible, use one_time_keyboard in ReplyKeyboardMarkup)."""
 
     selective: Option[bool] = Nothing
-    """Optional. Use this parameter if you want to remove the keyboard for specific 
-    users only. Targets: 1) users that are @mentioned in the text of the Message 
-    object; 2) if the bot's message is a reply to a message in the same chat and 
-    forum topic, sender of the original message. Example: A user votes in a poll, 
-    bot returns confirmation message in reply to the vote and removes the keyboard 
-    for that user, while still showing the keyboard with poll options to users 
+    """Optional. Use this parameter if you want to remove the keyboard for specific
+    users only. Targets: 1) users that are @mentioned in the text of the Message
+    object; 2) if the bot's message is a reply to a message in the same chat and
+    forum topic, sender of the original message. Example: A user votes in a poll,
+    bot returns confirmation message in reply to the vote and removes the keyboard
+    for that user, while still showing the keyboard with poll options to users
     who haven't voted yet."""
 
 
@@ -2425,70 +2554,70 @@ class InlineKeyboardMarkup(Model):
     """
 
     inline_keyboard: list[list["InlineKeyboardButton"]]
-    """Array of button rows, each represented by an Array of InlineKeyboardButton 
+    """Array of button rows, each represented by an Array of InlineKeyboardButton
     objects."""
 
 
 class InlineKeyboardButton(Model):
     """Object `InlineKeyboardButton`, see the [documentation](https://core.telegram.org/bots/api#inlinekeyboardbutton).
 
-    This object represents one button of an inline keyboard. You must use exactly one of the optional fields.
+    This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.
     """
 
     text: str
     """Label text on the button."""
 
     url: Option[str] = Nothing
-    """Optional. HTTP or tg:// URL to be opened when the button is pressed. Links 
-    tg://user?id=<user_id> can be used to mention a user by their identifier 
+    """Optional. HTTP or tg:// URL to be opened when the button is pressed. Links
+    tg://user?id=<user_id> can be used to mention a user by their identifier
     without using a username, if this is allowed by their privacy settings."""
 
     callback_data: Option[str] = Nothing
-    """Optional. Data to be sent in a callback query to the bot when button is pressed, 
-    1-64 bytes. Not supported for messages sent on behalf of a Telegram Business 
-    account."""
+    """Optional. Data to be sent in a callback query to the bot when the button is
+    pressed, 1-64 bytes."""
 
     web_app: Option["WebAppInfo"] = Nothing
-    """Optional. Description of the Web App that will be launched when the user 
-    presses the button. The Web App will be able to send an arbitrary message 
-    on behalf of the user using the method answerWebAppQuery. Available only 
-    in private chats between a user and the bot. Not supported for messages sent 
+    """Optional. Description of the Web App that will be launched when the user
+    presses the button. The Web App will be able to send an arbitrary message
+    on behalf of the user using the method answerWebAppQuery. Available only
+    in private chats between a user and the bot. Not supported for messages sent
     on behalf of a Telegram Business account."""
 
     login_url: Option["LoginUrl"] = Nothing
-    """Optional. An HTTPS URL used to automatically authorize the user. Can be 
+    """Optional. An HTTPS URL used to automatically authorize the user. Can be
     used as a replacement for the Telegram Login Widget."""
 
     switch_inline_query: Option[str] = Nothing
-    """Optional. If set, pressing the button will prompt the user to select one 
-    of their chats, open that chat and insert the bot's username and the specified 
-    inline query in the input field. May be empty, in which case just the bot's 
-    username will be inserted. Not supported for messages sent on behalf of 
+    """Optional. If set, pressing the button will prompt the user to select one
+    of their chats, open that chat and insert the bot's username and the specified
+    inline query in the input field. May be empty, in which case just the bot's
+    username will be inserted. Not supported for messages sent on behalf of
     a Telegram Business account."""
 
     switch_inline_query_current_chat: Option[str] = Nothing
-    """Optional. If set, pressing the button will insert the bot's username and 
-    the specified inline query in the current chat's input field. May be empty, 
-    in which case only the bot's username will be inserted. This offers a quick 
-    way for the user to open your bot in inline mode in the same chat - good for selecting 
-    something from multiple options. Not supported in channels and for messages 
+    """Optional. If set, pressing the button will insert the bot's username and
+    the specified inline query in the current chat's input field. May be empty,
+    in which case only the bot's username will be inserted. This offers a quick
+    way for the user to open your bot in inline mode in the same chat - good for selecting
+    something from multiple options. Not supported in channels and for messages
     sent on behalf of a Telegram Business account."""
 
     switch_inline_query_chosen_chat: Option["SwitchInlineQueryChosenChat"] = Nothing
-    """Optional. If set, pressing the button will prompt the user to select one 
-    of their chats of the specified type, open that chat and insert the bot's 
-    username and the specified inline query in the input field. Not supported 
+    """Optional. If set, pressing the button will prompt the user to select one
+    of their chats of the specified type, open that chat and insert the bot's
+    username and the specified inline query in the input field. Not supported
     for messages sent on behalf of a Telegram Business account."""
 
     callback_game: Option["CallbackGame"] = Nothing
-    """Optional. Description of the game that will be launched when the user presses 
-    the button. NOTE: This type of button must always be the first button in the 
+    """Optional. Description of the game that will be launched when the user presses
+    the button. NOTE: This type of button must always be the first button in the
     first row."""
 
     pay: Option[bool] = Nothing
-    """Optional. Specify True, to send a Pay button. NOTE: This type of button must 
-    always be the first button in the first row and can only be used in invoice 
-    messages."""
+    """Optional. Specify True, to send a Pay button. Substrings `⭐` and `XTR` in
+    the buttons's text will be replaced with a Telegram Star icon. NOTE: This
+    type of button must always be the first button in the first row and can only
+    be used in invoice messages."""
 
 
 class LoginUrl(Model):
@@ -2499,24 +2628,24 @@ class LoginUrl(Model):
     """
 
     url: str
-    """An HTTPS URL to be opened with user authorization data added to the query 
-    string when the button is pressed. If the user refuses to provide authorization 
-    data, the original URL without information about the user will be opened. 
-    The data added is the same as described in Receiving authorization data. 
-    NOTE: You must always check the hash of the received data to verify the authentication 
+    """An HTTPS URL to be opened with user authorization data added to the query
+    string when the button is pressed. If the user refuses to provide authorization
+    data, the original URL without information about the user will be opened.
+    The data added is the same as described in Receiving authorization data.
+    NOTE: You must always check the hash of the received data to verify the authentication
     and the integrity of the data as described in Checking authorization."""
 
     forward_text: Option[str] = Nothing
     """Optional. New text of the button in forwarded messages."""
 
     bot_username: Option[str] = Nothing
-    """Optional. Username of a bot, which will be used for user authorization. 
-    See Setting up a bot for more details. If not specified, the current bot's 
-    username will be assumed. The url's domain must be the same as the domain 
+    """Optional. Username of a bot, which will be used for user authorization.
+    See Setting up a bot for more details. If not specified, the current bot's
+    username will be assumed. The url's domain must be the same as the domain
     linked with the bot. See Linking your domain to the bot for more details."""
 
     request_write_access: Option[bool] = Nothing
-    """Optional. Pass True to request the permission for your bot to send messages 
+    """Optional. Pass True to request the permission for your bot to send messages
     to the user."""
 
 
@@ -2527,7 +2656,7 @@ class SwitchInlineQueryChosenChat(Model):
     """
 
     query: Option[str] = Nothing
-    """Optional. The default inline query to be inserted in the input field. If 
+    """Optional. The default inline query to be inserted in the input field. If
     left empty, only the bot's username will be inserted."""
 
     allow_user_chats: Option[bool] = Nothing
@@ -2556,23 +2685,23 @@ class CallbackQuery(Model):
     """Sender."""
 
     chat_instance: str
-    """Global identifier, uniquely corresponding to the chat to which the message 
+    """Global identifier, uniquely corresponding to the chat to which the message
     with the callback button was sent. Useful for high scores in games."""
 
     message: Option[Variative["Message", "InaccessibleMessage"]] = Nothing
-    """Optional. Message sent by the bot with the callback button that originated 
+    """Optional. Message sent by the bot with the callback button that originated
     the query."""
 
     inline_message_id: Option[str] = Nothing
-    """Optional. Identifier of the message sent via the bot in inline mode, that 
+    """Optional. Identifier of the message sent via the bot in inline mode, that
     originated the query."""
 
     data: Option[str] = Nothing
-    """Optional. Data associated with the callback button. Be aware that the message 
+    """Optional. Data associated with the callback button. Be aware that the message
     originated the query can contain no callback buttons with this data."""
 
     game_short_name: Option[str] = Nothing
-    """Optional. Short name of a Game to be returned, serves as the unique identifier 
+    """Optional. Short name of a Game to be returned, serves as the unique identifier
     for the game."""
 
 
@@ -2583,17 +2712,17 @@ class ForceReply(Model):
     """
 
     force_reply: bool
-    """Shows reply interface to the user, as if they manually selected the bot's 
+    """Shows reply interface to the user, as if they manually selected the bot's
     message and tapped 'Reply'."""
 
     input_field_placeholder: Option[str] = Nothing
-    """Optional. The placeholder to be shown in the input field when the reply is 
+    """Optional. The placeholder to be shown in the input field when the reply is
     active; 1-64 characters."""
 
     selective: Option[bool] = Nothing
-    """Optional. Use this parameter if you want to force reply from specific users 
-    only. Targets: 1) users that are @mentioned in the text of the Message object; 
-    2) if the bot's message is a reply to a message in the same chat and forum topic, 
+    """Optional. Use this parameter if you want to force reply from specific users
+    only. Targets: 1) users that are @mentioned in the text of the Message object;
+    2) if the bot's message is a reply to a message in the same chat and forum topic,
     sender of the original message."""
 
 
@@ -2604,21 +2733,21 @@ class ChatPhoto(Model):
     """
 
     small_file_id: str
-    """File identifier of small (160x160) chat photo. This file_id can be used 
+    """File identifier of small (160x160) chat photo. This file_id can be used
     only for photo download and only for as long as the photo is not changed."""
 
     small_file_unique_id: str
-    """Unique file identifier of small (160x160) chat photo, which is supposed 
-    to be the same over time and for different bots. Can't be used to download 
+    """Unique file identifier of small (160x160) chat photo, which is supposed
+    to be the same over time and for different bots. Can't be used to download
     or reuse the file."""
 
     big_file_id: str
-    """File identifier of big (640x640) chat photo. This file_id can be used only 
+    """File identifier of big (640x640) chat photo. This file_id can be used only
     for photo download and only for as long as the photo is not changed."""
 
     big_file_unique_id: str
-    """Unique file identifier of big (640x640) chat photo, which is supposed to 
-    be the same over time and for different bots. Can't be used to download or 
+    """Unique file identifier of big (640x640) chat photo, which is supposed to
+    be the same over time and for different bots. Can't be used to download or
     reuse the file."""
 
 
@@ -2629,7 +2758,7 @@ class ChatInviteLink(Model):
     """
 
     invite_link: str
-    """The invite link. If the link was created by another chat administrator, 
+    """The invite link. If the link was created by another chat administrator,
     then the second part of the link will be replaced with `...`."""
 
     creator: "User"
@@ -2648,11 +2777,11 @@ class ChatInviteLink(Model):
     """Optional. Invite link name."""
 
     expire_date: Option[datetime] = Nothing
-    """Optional. Point in time (Unix timestamp) when the link will expire or has 
+    """Optional. Point in time (Unix timestamp) when the link will expire or has
     been expired."""
 
     member_limit: Option[int] = Nothing
-    """Optional. The maximum number of users that can be members of the chat simultaneously 
+    """Optional. The maximum number of users that can be members of the chat simultaneously
     after joining the chat via this invite link; 1-99999."""
 
     pending_join_request_count: Option[int] = Nothing
@@ -2669,8 +2798,8 @@ class ChatAdministratorRights(Model):
     """True, if the user's presence in the chat is hidden."""
 
     can_manage_chat: bool
-    """True, if the administrator can access the chat event log, get boost list, 
-    see hidden supergroup and channel members, report spam messages and ignore 
+    """True, if the administrator can access the chat event log, get boost list,
+    see hidden supergroup and channel members, report spam messages and ignore
     slow mode. Implied by any other administrator privilege."""
 
     can_delete_messages: bool
@@ -2680,12 +2809,12 @@ class ChatAdministratorRights(Model):
     """True, if the administrator can manage video chats."""
 
     can_restrict_members: bool
-    """True, if the administrator can restrict, ban or unban chat members, or access 
+    """True, if the administrator can restrict, ban or unban chat members, or access
     supergroup statistics."""
 
     can_promote_members: bool
-    """True, if the administrator can add new administrators with a subset of their 
-    own privileges or demote administrators that they have promoted, directly 
+    """True, if the administrator can add new administrators with a subset of their
+    own privileges or demote administrators that they have promoted, directly
     or indirectly (promoted by administrators that were appointed by the user)."""
 
     can_change_info: bool
@@ -2698,26 +2827,26 @@ class ChatAdministratorRights(Model):
     """True, if the administrator can post stories to the chat."""
 
     can_edit_stories: bool
-    """True, if the administrator can edit stories posted by other users, post 
+    """True, if the administrator can edit stories posted by other users, post
     stories to the chat page, pin chat stories, and access the chat's story archive."""
 
     can_delete_stories: bool
     """True, if the administrator can delete stories posted by other users."""
 
     can_post_messages: Option[bool] = Nothing
-    """Optional. True, if the administrator can post messages in the channel, 
+    """Optional. True, if the administrator can post messages in the channel,
     or access channel statistics; for channels only."""
 
     can_edit_messages: Option[bool] = Nothing
-    """Optional. True, if the administrator can edit messages of other users and 
+    """Optional. True, if the administrator can edit messages of other users and
     can pin messages; for channels only."""
 
     can_pin_messages: Option[bool] = Nothing
-    """Optional. True, if the user is allowed to pin messages; for groups and supergroups 
+    """Optional. True, if the user is allowed to pin messages; for groups and supergroups
     only."""
 
     can_manage_topics: Option[bool] = Nothing
-    """Optional. True, if the user is allowed to create, rename, close, and reopen 
+    """Optional. True, if the user is allowed to create, rename, close, and reopen
     forum topics; for supergroups only."""
 
 
@@ -2757,11 +2886,11 @@ class ChatMemberUpdated(Model):
     """New information about the chat member."""
 
     invite_link: Option["ChatInviteLink"] = Nothing
-    """Optional. Chat invite link, which was used by the user to join the chat; for 
+    """Optional. Chat invite link, which was used by the user to join the chat; for
     joining by invite link events only."""
 
     via_join_request: Option[bool] = Nothing
-    """Optional. True, if the user joined the chat after sending a direct join request 
+    """Optional. True, if the user joined the chat after sending a direct join request
     without using an invite link and being approved by an administrator."""
 
     via_chat_folder_invite_link: Option[bool] = Nothing
@@ -2812,8 +2941,8 @@ class ChatMemberAdministrator(ChatMember):
     """True, if the user's presence in the chat is hidden."""
 
     can_manage_chat: bool
-    """True, if the administrator can access the chat event log, get boost list, 
-    see hidden supergroup and channel members, report spam messages and ignore 
+    """True, if the administrator can access the chat event log, get boost list,
+    see hidden supergroup and channel members, report spam messages and ignore
     slow mode. Implied by any other administrator privilege."""
 
     can_delete_messages: bool
@@ -2823,12 +2952,12 @@ class ChatMemberAdministrator(ChatMember):
     """True, if the administrator can manage video chats."""
 
     can_restrict_members: bool
-    """True, if the administrator can restrict, ban or unban chat members, or access 
+    """True, if the administrator can restrict, ban or unban chat members, or access
     supergroup statistics."""
 
     can_promote_members: bool
-    """True, if the administrator can add new administrators with a subset of their 
-    own privileges or demote administrators that they have promoted, directly 
+    """True, if the administrator can add new administrators with a subset of their
+    own privileges or demote administrators that they have promoted, directly
     or indirectly (promoted by administrators that were appointed by the user)."""
 
     can_change_info: bool
@@ -2841,26 +2970,26 @@ class ChatMemberAdministrator(ChatMember):
     """True, if the administrator can post stories to the chat."""
 
     can_edit_stories: bool
-    """True, if the administrator can edit stories posted by other users, post 
+    """True, if the administrator can edit stories posted by other users, post
     stories to the chat page, pin chat stories, and access the chat's story archive."""
 
     can_delete_stories: bool
     """True, if the administrator can delete stories posted by other users."""
 
     can_post_messages: Option[bool] = Nothing
-    """Optional. True, if the administrator can post messages in the channel, 
+    """Optional. True, if the administrator can post messages in the channel,
     or access channel statistics; for channels only."""
 
     can_edit_messages: Option[bool] = Nothing
-    """Optional. True, if the administrator can edit messages of other users and 
+    """Optional. True, if the administrator can edit messages of other users and
     can pin messages; for channels only."""
 
     can_pin_messages: Option[bool] = Nothing
-    """Optional. True, if the user is allowed to pin messages; for groups and supergroups 
+    """Optional. True, if the user is allowed to pin messages; for groups and supergroups
     only."""
 
     can_manage_topics: Option[bool] = Nothing
-    """Optional. True, if the user is allowed to create, rename, close, and reopen 
+    """Optional. True, if the user is allowed to create, rename, close, and reopen
     forum topics; for supergroups only."""
 
     custom_title: Option[str] = Nothing
@@ -2896,7 +3025,7 @@ class ChatMemberRestricted(ChatMember):
     """True, if the user is a member of the chat at the moment of the request."""
 
     can_send_messages: bool
-    """True, if the user is allowed to send text messages, contacts, giveaways, 
+    """True, if the user is allowed to send text messages, contacts, giveaways,
     giveaway winners, invoices, locations and venues."""
 
     can_send_audios: bool
@@ -2921,7 +3050,7 @@ class ChatMemberRestricted(ChatMember):
     """True, if the user is allowed to send polls."""
 
     can_send_other_messages: bool
-    """True, if the user is allowed to send animations, games, stickers and use 
+    """True, if the user is allowed to send animations, games, stickers and use
     inline bots."""
 
     can_add_web_page_previews: bool
@@ -2940,7 +3069,7 @@ class ChatMemberRestricted(ChatMember):
     """True, if the user is allowed to create forum topics."""
 
     until_date: datetime
-    """Date when restrictions will be lifted for this user; Unix time. If 0, then 
+    """Date when restrictions will be lifted for this user; Unix time. If 0, then
     the user is restricted forever."""
 
 
@@ -2970,7 +3099,7 @@ class ChatMemberBanned(ChatMember):
     """Information about the user."""
 
     until_date: datetime
-    """Date when restrictions will be lifted for this user; Unix time. If 0, then 
+    """Date when restrictions will be lifted for this user; Unix time. If 0, then
     the user is banned forever."""
 
 
@@ -2987,12 +3116,12 @@ class ChatJoinRequest(Model):
     """User that sent the join request."""
 
     user_chat_id: int
-    """Identifier of a private chat with the user who sent the join request. This 
-    number may have more than 32 significant bits and some programming languages 
-    may have difficulty/silent defects in interpreting it. But it has at most 
-    52 significant bits, so a 64-bit integer or double-precision float type 
-    are safe for storing this identifier. The bot can use this identifier for 
-    5 minutes to send messages until the join request is processed, assuming 
+    """Identifier of a private chat with the user who sent the join request. This
+    number may have more than 32 significant bits and some programming languages
+    may have difficulty/silent defects in interpreting it. But it has at most
+    52 significant bits, so a 64-bit integer or double-precision float type
+    are safe for storing this identifier. The bot can use this identifier for
+    5 minutes to send messages until the join request is processed, assuming
     no other administrator contacted the user."""
 
     date: datetime
@@ -3018,7 +3147,7 @@ class ChatPermissions(Model):
     """
 
     can_send_messages: Option[bool] = Nothing
-    """Optional. True, if the user is allowed to send text messages, contacts, 
+    """Optional. True, if the user is allowed to send text messages, contacts,
     giveaways, giveaway winners, invoices, locations and venues."""
 
     can_send_audios: Option[bool] = Nothing
@@ -3043,25 +3172,25 @@ class ChatPermissions(Model):
     """Optional. True, if the user is allowed to send polls."""
 
     can_send_other_messages: Option[bool] = Nothing
-    """Optional. True, if the user is allowed to send animations, games, stickers 
+    """Optional. True, if the user is allowed to send animations, games, stickers
     and use inline bots."""
 
     can_add_web_page_previews: Option[bool] = Nothing
     """Optional. True, if the user is allowed to add web page previews to their messages."""
 
     can_change_info: Option[bool] = Nothing
-    """Optional. True, if the user is allowed to change the chat title, photo and 
+    """Optional. True, if the user is allowed to change the chat title, photo and
     other settings. Ignored in public supergroups."""
 
     can_invite_users: Option[bool] = Nothing
     """Optional. True, if the user is allowed to invite new users to the chat."""
 
     can_pin_messages: Option[bool] = Nothing
-    """Optional. True, if the user is allowed to pin messages. Ignored in public 
+    """Optional. True, if the user is allowed to pin messages. Ignored in public
     supergroups."""
 
     can_manage_topics: Option[bool] = Nothing
-    """Optional. True, if the user is allowed to create forum topics. If omitted 
+    """Optional. True, if the user is allowed to create forum topics. If omitted
     defaults to the value of can_pin_messages."""
 
 
@@ -3132,11 +3261,11 @@ class BusinessOpeningHoursInterval(Model):
     """
 
     opening_minute: int
-    """The minute's sequence number in a week, starting on Monday, marking the 
+    """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."""
 
     closing_minute: int
-    """The minute's sequence number in a week, starting on Monday, marking the 
+    """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."""
 
 
@@ -3176,11 +3305,11 @@ class ReactionTypeEmoji(ReactionType):
     """Type of the reaction, always `emoji`."""
 
     emoji: ReactionEmoji
-    """Reaction emoji. Currently, it can be one of `👍`, `👎`, `❤`, `🔥`, `🥰`, `👏`, 
-    `😁`, `🤔`, `🤯`, `😱`, `🤬`, `😢`, `🎉`, `🤩`, `🤮`, `💩`, `🙏`, `👌`, `🕊`, `🤡`, `🥱`, 
-    `🥴`, `😍`, `🐳`, `❤‍🔥`, `🌚`, `🌭`, `💯`, `🤣`, `⚡`, `🍌`, `🏆`, `💔`, `🤨`, `😐`, `🍓`, 
-    `🍾`, `💋`, `🖕`, `😈`, `😴`, `😭`, `🤓`, `👻`, `👨‍💻`, `👀`, `🎃`, `🙈`, `😇`, `😨`, `🤝`, 
-    `✍`, `🤗`, `🫡`, `🎅`, `🎄`, `☃`, `💅`, `🤪`, `🗿`, `🆒`, `💘`, `🙉`, `🦄`, `😘`, `💊`, 
+    """Reaction emoji. Currently, it can be one of `👍`, `👎`, `❤`, `🔥`, `🥰`, `👏`,
+    `😁`, `🤔`, `🤯`, `😱`, `🤬`, `😢`, `🎉`, `🤩`, `🤮`, `💩`, `🙏`, `👌`, `🕊`, `🤡`, `🥱`,
+    `🥴`, `😍`, `🐳`, `❤‍🔥`, `🌚`, `🌭`, `💯`, `🤣`, `⚡`, `🍌`, `🏆`, `💔`, `🤨`, `😐`, `🍓`,
+    `🍾`, `💋`, `🖕`, `😈`, `😴`, `😭`, `🤓`, `👻`, `👨‍💻`, `👀`, `🎃`, `🙈`, `😇`, `😨`, `🤝`,
+    `✍`, `🤗`, `🫡`, `🎅`, `🎄`, `☃`, `💅`, `🤪`, `🗿`, `🆒`, `💘`, `🙉`, `🦄`, `😘`, `💊`,
     `🙊`, `😎`, `👾`, `🤷‍♂`, `🤷`, `🤷‍♀`, `😡`."""
 
 
@@ -3235,7 +3364,7 @@ class MessageReactionUpdated(Model):
     """Optional. The user that changed the reaction, if the user isn't anonymous."""
 
     actor_chat: Option["Chat"] = Nothing
-    """Optional. The chat on behalf of which the reaction was changed, if the user 
+    """Optional. The chat on behalf of which the reaction was changed, if the user
     is anonymous."""
 
 
@@ -3284,7 +3413,7 @@ class BotCommand(Model):
     """
 
     command: str
-    """Text of the command; 1-32 characters. Can contain only lowercase English 
+    """Text of the command; 1-32 characters. Can contain only lowercase English
     letters, digits and underscores."""
 
     description: str
@@ -3341,7 +3470,7 @@ class BotCommandScopeChat(BotCommandScope):
     """Scope type, must be chat."""
 
     chat_id: Variative[int, str]
-    """Unique identifier for the target chat or username of the target supergroup 
+    """Unique identifier for the target chat or username of the target supergroup
     (in the format @supergroupusername)."""
 
 
@@ -3355,7 +3484,7 @@ class BotCommandScopeChatAdministrators(BotCommandScope):
     """Scope type, must be chat_administrators."""
 
     chat_id: Variative[int, str]
-    """Unique identifier for the target chat or username of the target supergroup 
+    """Unique identifier for the target chat or username of the target supergroup
     (in the format @supergroupusername)."""
 
 
@@ -3369,7 +3498,7 @@ class BotCommandScopeChatMember(BotCommandScope):
     """Scope type, must be chat_member."""
 
     chat_id: Variative[int, str]
-    """Unique identifier for the target chat or username of the target supergroup 
+    """Unique identifier for the target chat or username of the target supergroup
     (in the format @supergroupusername)."""
 
     user_id: int
@@ -3429,9 +3558,11 @@ class MenuButtonWebApp(MenuButton):
     """Text on the button."""
 
     web_app: "WebAppInfo"
-    """Description of the Web App that will be launched when the user presses the 
-    button. The Web App will be able to send an arbitrary message on behalf of 
-    the user using the method answerWebAppQuery."""
+    """Description of the Web App that will be launched when the user presses the
+    button. The Web App will be able to send an arbitrary message on behalf of
+    the user using the method answerWebAppQuery. Alternatively, a t.me link
+    to a Web App of the bot can be specified in the object instead of the Web App's
+    URL, in which case the Web App will be opened as if the user pressed the link."""
 
 
 class MenuButtonDefault(MenuButton):
@@ -3480,14 +3611,14 @@ class ChatBoostSourceGiveaway(ChatBoostSource):
     """Source of the boost, always `giveaway`."""
 
     giveaway_message_id: int
-    """Identifier of a message in the chat with the giveaway; the message could 
+    """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."""
 
     user: Option["User"] = Nothing
     """Optional. User that won the prize in the giveaway if any."""
 
     is_unclaimed: Option[bool] = Nothing
-    """Optional. True, if the giveaway was completed, but there was no user to win 
+    """Optional. True, if the giveaway was completed, but there was no user to win
     the prize."""
 
 
@@ -3504,7 +3635,7 @@ class ChatBoost(Model):
     """Point in time (Unix timestamp) when the chat was boosted."""
 
     expiration_date: datetime
-    """Point in time (Unix timestamp) when the boost will automatically expire, 
+    """Point in time (Unix timestamp) when the boost will automatically expire,
     unless the booster's Telegram Premium subscription is prolonged."""
 
     source: Variative["ChatBoostSourcePremium", "ChatBoostSourceGiftCode", "ChatBoostSourceGiveaway"]
@@ -3566,17 +3697,17 @@ class BusinessConnection(Model):
     """Business account user that created the business connection."""
 
     user_chat_id: int
-    """Identifier of a private chat with the user who created the business connection. 
-    This number may have more than 32 significant bits and some programming 
-    languages may have difficulty/silent defects in interpreting it. But 
-    it has at most 52 significant bits, so a 64-bit integer or double-precision 
+    """Identifier of a private chat with the user who created the business connection.
+    This number may have more than 32 significant bits and some programming
+    languages may have difficulty/silent defects in interpreting it. But
+    it has at most 52 significant bits, so a 64-bit integer or double-precision
     float type are safe for storing this identifier."""
 
     date: datetime
     """Date the connection was established in Unix time."""
 
     can_reply: bool
-    """True, if the bot can act on behalf of the business account in chats that were 
+    """True, if the bot can act on behalf of the business account in chats that were
     active in the last 24 hours."""
 
     is_enabled: bool
@@ -3593,7 +3724,7 @@ class BusinessMessagesDeleted(Model):
     """Unique identifier of the business connection."""
 
     chat: "Chat"
-    """Information about a chat in the business account. The bot may not have access 
+    """Information about a chat in the business account. The bot may not have access
     to the chat or the corresponding user."""
 
     message_ids: list[int]
@@ -3607,14 +3738,14 @@ class ResponseParameters(Model):
     """
 
     migrate_to_chat_id: Option[int] = Nothing
-    """Optional. The group has been migrated to a supergroup with the specified 
-    identifier. This number may have more than 32 significant bits and some 
-    programming languages may have difficulty/silent defects in interpreting 
-    it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision 
+    """Optional. The group has been migrated to a supergroup with the specified
+    identifier. This number may have more than 32 significant bits and some
+    programming languages may have difficulty/silent defects in interpreting
+    it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision
     float type are safe for storing this identifier."""
 
     retry_after: Option[int] = Nothing
-    """Optional. In case of exceeding flood control, the number of seconds left 
+    """Optional. In case of exceeding flood control, the number of seconds left
     to wait before the request can be repeated."""
 
 
@@ -3628,23 +3759,26 @@ class InputMediaPhoto(InputMedia):
     """Type of the result, must be photo."""
 
     media: Variative["InputFile", str]
-    """File to send. Pass a file_id to send a file that exists on the Telegram servers 
-    (recommended), pass an HTTP URL for Telegram to get a file from the Internet, 
-    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data 
+    """File to send. Pass a file_id to send a file that exists on the Telegram servers
+    (recommended), pass an HTTP URL for Telegram to get a file from the Internet,
+    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data
     under <file_attach_name> name. More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the photo to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the photo to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the photo caption. See formatting 
+    """Optional. Mode for parsing entities in the photo caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     has_spoiler: Option[bool] = Nothing
     """Optional. Pass True if the photo needs to be covered with a spoiler animation."""
 
@@ -3659,32 +3793,35 @@ class InputMediaVideo(InputMedia):
     """Type of the result, must be video."""
 
     media: Variative["InputFile", str]
-    """File to send. Pass a file_id to send a file that exists on the Telegram servers 
-    (recommended), pass an HTTP URL for Telegram to get a file from the Internet, 
-    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data 
+    """File to send. Pass a file_id to send a file that exists on the Telegram servers
+    (recommended), pass an HTTP URL for Telegram to get a file from the Internet,
+    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data
     under <file_attach_name> name. More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
 
     thumbnail: Option[Variative["InputFile", str]] = Nothing
-    """Optional. 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://<file_attach_name>` 
-    if the thumbnail was uploaded using multipart/form-data under <file_attach_name>. 
+    """Optional. 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://<file_attach_name>`
+    if the thumbnail was uploaded using multipart/form-data under <file_attach_name>.
     More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the video to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the video to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the video caption. See formatting 
+    """Optional. Mode for parsing entities in the video caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     width: Option[int] = Nothing
     """Optional. Video width."""
 
@@ -3711,32 +3848,35 @@ class InputMediaAnimation(InputMedia):
     """Type of the result, must be animation."""
 
     media: Variative["InputFile", str]
-    """File to send. Pass a file_id to send a file that exists on the Telegram servers 
-    (recommended), pass an HTTP URL for Telegram to get a file from the Internet, 
-    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data 
+    """File to send. Pass a file_id to send a file that exists on the Telegram servers
+    (recommended), pass an HTTP URL for Telegram to get a file from the Internet,
+    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data
     under <file_attach_name> name. More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
 
     thumbnail: Option[Variative["InputFile", str]] = Nothing
-    """Optional. 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://<file_attach_name>` 
-    if the thumbnail was uploaded using multipart/form-data under <file_attach_name>. 
+    """Optional. 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://<file_attach_name>`
+    if the thumbnail was uploaded using multipart/form-data under <file_attach_name>.
     More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the animation to be sent, 0-1024 characters after 
+    """Optional. Caption of the animation to be sent, 0-1024 characters after
     entities parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the animation caption. See formatting 
+    """Optional. Mode for parsing entities in the animation caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     width: Option[int] = Nothing
     """Optional. Animation width."""
 
@@ -3760,30 +3900,30 @@ class InputMediaAudio(InputMedia):
     """Type of the result, must be audio."""
 
     media: Variative["InputFile", str]
-    """File to send. Pass a file_id to send a file that exists on the Telegram servers 
-    (recommended), pass an HTTP URL for Telegram to get a file from the Internet, 
-    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data 
+    """File to send. Pass a file_id to send a file that exists on the Telegram servers
+    (recommended), pass an HTTP URL for Telegram to get a file from the Internet,
+    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data
     under <file_attach_name> name. More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
 
     thumbnail: Option[Variative["InputFile", str]] = Nothing
-    """Optional. 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://<file_attach_name>` 
-    if the thumbnail was uploaded using multipart/form-data under <file_attach_name>. 
+    """Optional. 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://<file_attach_name>`
+    if the thumbnail was uploaded using multipart/form-data under <file_attach_name>.
     More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the audio to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the audio to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the audio caption. See formatting 
+    """Optional. Mode for parsing entities in the audio caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
     duration: Option[int] = Nothing
@@ -3806,35 +3946,35 @@ class InputMediaDocument(InputMedia):
     """Type of the result, must be document."""
 
     media: Variative["InputFile", str]
-    """File to send. Pass a file_id to send a file that exists on the Telegram servers 
-    (recommended), pass an HTTP URL for Telegram to get a file from the Internet, 
-    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data 
+    """File to send. Pass a file_id to send a file that exists on the Telegram servers
+    (recommended), pass an HTTP URL for Telegram to get a file from the Internet,
+    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data
     under <file_attach_name> name. More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
 
     thumbnail: Option[Variative["InputFile", str]] = Nothing
-    """Optional. 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://<file_attach_name>` 
-    if the thumbnail was uploaded using multipart/form-data under <file_attach_name>. 
+    """Optional. 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://<file_attach_name>`
+    if the thumbnail was uploaded using multipart/form-data under <file_attach_name>.
     More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the document to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the document to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the document caption. See formatting 
+    """Optional. Mode for parsing entities in the document caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
     disable_content_type_detection: Option[bool] = Nothing
-    """Optional. Disables automatic server-side content type detection for 
-    files uploaded using multipart/form-data. Always True, if the document 
+    """Optional. Disables automatic server-side content type detection for
+    files uploaded using multipart/form-data. Always True, if the document
     is sent as part of an album."""
 
 
@@ -3850,6 +3990,67 @@ class InputFile(typing.NamedTuple):
     data: bytes
     """Bytes of file."""
 
+    @classmethod
+    def from_file(cls, path: str | pathlib.Path) -> typing.Self:
+        path = pathlib.Path(path)
+        return cls(
+            filename=path.name,
+            data=path.read_bytes(),
+        )
+
+
+class InputPaidMediaPhoto(InputPaidMedia):
+    """Object `InputPaidMediaPhoto`, see the [documentation](https://core.telegram.org/bots/api#inputpaidmediaphoto).
+
+    The paid media to send is a photo.
+    """
+
+    type: typing.Literal["photo"]
+    """Type of the media, must be photo."""
+
+    media: Variative["InputFile", str]
+    """File to send. Pass a file_id to send a file that exists on the Telegram servers
+    (recommended), pass an HTTP URL for Telegram to get a file from the Internet,
+    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data
+    under <file_attach_name> name. More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
+
+
+class InputPaidMediaVideo(InputPaidMedia):
+    """Object `InputPaidMediaVideo`, see the [documentation](https://core.telegram.org/bots/api#inputpaidmediavideo).
+
+    The paid media to send is a video.
+    """
+
+    type: typing.Literal["video"]
+    """Type of the media, must be video."""
+
+    media: Variative["InputFile", str]
+    """File to send. Pass a file_id to send a file that exists on the Telegram servers
+    (recommended), pass an HTTP URL for Telegram to get a file from the Internet,
+    or pass `attach://<file_attach_name>` to upload a new one using multipart/form-data
+    under <file_attach_name> name. More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
+
+    thumbnail: Option[Variative["InputFile", str]] = Nothing
+    """Optional. 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://<file_attach_name>`
+    if the thumbnail was uploaded using multipart/form-data under <file_attach_name>.
+    More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
+
+    width: Option[int] = Nothing
+    """Optional. Video width."""
+
+    height: Option[int] = Nothing
+    """Optional. Video height."""
+
+    duration: Option[int] = Nothing
+    """Optional. Video duration in seconds."""
+
+    supports_streaming: Option[bool] = Nothing
+    """Optional. Pass True if the uploaded video is suitable for streaming."""
+
 
 class Sticker(Model):
     """Object `Sticker`, see the [documentation](https://core.telegram.org/bots/api#sticker).
@@ -3861,12 +4062,12 @@ class Sticker(Model):
     """Identifier for this file, which can be used to download or reuse the file."""
 
     file_unique_id: str
-    """Unique identifier for this file, which is supposed to be the same over time 
+    """Unique identifier for this file, which is supposed to be the same over time
     and for different bots. Can't be used to download or reuse the file."""
 
     type: typing.Literal["regular", "mask", "custom_emoji"]
-    """Type of the sticker, currently one of `regular`, `mask`, `custom_emoji`. 
-    The type of the sticker is independent from its format, which is determined 
+    """Type of the sticker, currently one of `regular`, `mask`, `custom_emoji`.
+    The type of the sticker is independent from its format, which is determined
     by the fields is_animated and is_video."""
 
     width: int
@@ -3900,8 +4101,8 @@ class Sticker(Model):
     """Optional. For custom emoji stickers, unique identifier of the custom emoji."""
 
     needs_repainting: Option[bool] = Nothing
-    """Optional. True, if the sticker must be repainted to a text color in messages, 
-    the color of the Telegram Premium badge in emoji status, white color on chat 
+    """Optional. True, if the sticker must be repainted to a text color in messages,
+    the color of the Telegram Premium badge in emoji status, white color on chat
     photos, or another appropriate color in other places."""
 
     file_size: Option[int] = Nothing
@@ -3937,17 +4138,17 @@ class MaskPosition(Model):
     """
 
     point: str
-    """The part of the face relative to which the mask should be placed. One of `forehead`, 
+    """The part of the face relative to which the mask should be placed. One of `forehead`,
     `eyes`, `mouth`, or `chin`."""
 
     x_shift: float
-    """Shift by X-axis measured in widths of the mask scaled to the face size, from 
-    left to right. For example, choosing -1.0 will place mask just to the left 
+    """Shift by X-axis measured in widths of the mask scaled to the face size, from
+    left to right. For example, choosing -1.0 will place mask just to the left
     of the default mask position."""
 
     y_shift: float
-    """Shift by Y-axis measured in heights of the mask scaled to the face size, from 
-    top to bottom. For example, 1.0 will place the mask just below the default 
+    """Shift by Y-axis measured in heights of the mask scaled to the face size, from
+    top to bottom. For example, 1.0 will place the mask just below the default
     mask position."""
 
     scale: float
@@ -3961,26 +4162,26 @@ class InputSticker(Model):
     """
 
     sticker: Variative["InputFile", str]
-    """The added sticker. 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, upload a new one using multipart/form-data, or 
-    pass `attach://<file_attach_name>` to upload a new one using multipart/form-data 
-    under <file_attach_name> name. Animated and video stickers can't be uploaded 
+    """The added sticker. 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, upload a new one using multipart/form-data, or
+    pass `attach://<file_attach_name>` to upload a new one using multipart/form-data
+    under <file_attach_name> name. Animated and video stickers can't be uploaded
     via HTTP URL. More information on Sending Files: https://core.telegram.org/bots/api#sending-files."""
 
     format: str
-    """Format of the added sticker, must be one of `static` for a .WEBP or .PNG image, 
+    """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."""
 
     emoji_list: list[str]
     """List of 1-20 emoji associated with the sticker."""
 
     mask_position: Option["MaskPosition"] = Nothing
-    """Optional. Position where the mask should be placed on faces. For `mask` 
+    """Optional. Position where the mask should be placed on faces. For `mask`
     stickers only."""
 
     keywords: Option[list[str]] = Nothing
-    """Optional. List of 0-20 search keywords for the sticker with total length 
+    """Optional. List of 0-20 search keywords for the sticker with total length
     of up to 64 characters. For `regular` and `custom_emoji` stickers only."""
 
 
@@ -4003,10 +4204,10 @@ class InlineQuery(Model):
     """Offset of the results to be returned, can be controlled by the bot."""
 
     chat_type: Option[ChatType] = Nothing
-    """Optional. Type of the chat from which the inline query was sent. Can be either 
-    `sender` for a private chat with the inline query sender, `private`, `group`, 
-    `supergroup`, or `channel`. The chat type should be always known for requests 
-    sent from official clients and most third-party clients, unless the request 
+    """Optional. Type of the chat from which the inline query was sent. Can be either
+    `sender` for a private chat with the inline query sender, `private`, `group`,
+    `supergroup`, or `channel`. The chat type should be always known for requests
+    sent from official clients and most third-party clients, unless the request
     was sent from a secret chat."""
 
     location: Option["Location"] = Nothing
@@ -4023,20 +4224,20 @@ class InlineQueryResultsButton(Model):
     """Label text on the button."""
 
     web_app: Option["WebAppInfo"] = Nothing
-    """Optional. Description of the Web App that will be launched when the user 
-    presses the button. The Web App will be able to switch back to the inline mode 
+    """Optional. Description of the Web App that will be launched when the user
+    presses the button. The Web App will be able to switch back to the inline mode
     using the method switchInlineQuery inside the Web App."""
 
     start_parameter: Option[str] = Nothing
-    """Optional. Deep-linking parameter for the /start message sent to the bot 
-    when a user presses the button. 1-64 characters, only A-Z, a-z, 0-9, _ and 
-    - are allowed. Example: An inline bot that sends YouTube videos can ask the 
-    user to connect the bot to their YouTube account to adapt search results 
-    accordingly. To do this, it displays a 'Connect your YouTube account' button 
-    above the results, or even before showing any. The user presses the button, 
-    switches to a private chat with the bot and, in doing so, passes a start parameter 
-    that instructs the bot to return an OAuth link. Once done, the bot can offer 
-    a switch_inline button so that the user can easily return to the chat where 
+    """Optional. Deep-linking parameter for the /start message sent to the bot
+    when a user presses the button. 1-64 characters, only A-Z, a-z, 0-9, _ and
+    - are allowed. Example: An inline bot that sends YouTube videos can ask the
+    user to connect the bot to their YouTube account to adapt search results
+    accordingly. To do this, it displays a 'Connect your YouTube account' button
+    above the results, or even before showing any. The user presses the button,
+    switches to a private chat with the bot and, in doing so, passes a start parameter
+    that instructs the bot to return an OAuth link. Once done, the bot can offer
+    a switch_inline button so that the user can easily return to the chat where
     they wanted to use the bot's inline capabilities."""
 
 
@@ -4099,7 +4300,7 @@ class InlineQueryResultPhoto(InlineQueryResult):
     """Unique identifier for this result, 1-64 bytes."""
 
     photo_url: str
-    """A valid URL of the photo. Photo must be in JPEG format. Photo size must not 
+    """A valid URL of the photo. Photo must be in JPEG format. Photo size must not
     exceed 5MB."""
 
     thumbnail_url: str
@@ -4118,17 +4319,20 @@ class InlineQueryResultPhoto(InlineQueryResult):
     """Optional. Short description of the result."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the photo to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the photo to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the photo caption. See formatting 
+    """Optional. Mode for parsing entities in the photo caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
     """Optional. Inline keyboard attached to the message."""
 
@@ -4172,24 +4376,27 @@ class InlineQueryResultGif(InlineQueryResult):
     """Optional. Duration of the GIF in seconds."""
 
     thumbnail_mime_type: Option[typing.Literal["image/jpeg", "image/gif", "video/mp4"]] = Nothing
-    """Optional. MIME type of the thumbnail, must be one of `image/jpeg`, `image/gif`, 
+    """Optional. MIME type of the thumbnail, must be one of `image/jpeg`, `image/gif`,
     or `video/mp4`. Defaults to `image/jpeg`."""
 
     title: Option[str] = Nothing
     """Optional. Title for the result."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the GIF file to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the GIF file to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the caption. See formatting options 
+    """Optional. Mode for parsing entities in the caption. See formatting options
     for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
     """Optional. Inline keyboard attached to the message."""
 
@@ -4233,24 +4440,27 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
     """Optional. Video duration in seconds."""
 
     thumbnail_mime_type: Option[typing.Literal["image/jpeg", "image/gif", "video/mp4"]] = Nothing
-    """Optional. MIME type of the thumbnail, must be one of `image/jpeg`, `image/gif`, 
+    """Optional. MIME type of the thumbnail, must be one of `image/jpeg`, `image/gif`,
     or `video/mp4`. Defaults to `image/jpeg`."""
 
     title: Option[str] = Nothing
     """Optional. Title for the result."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters after 
+    """Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters after
     entities parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the caption. See formatting options 
+    """Optional. Mode for parsing entities in the caption. See formatting options
     for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
     """Optional. Inline keyboard attached to the message."""
 
@@ -4291,17 +4501,20 @@ class InlineQueryResultVideo(InlineQueryResult):
     """Title for the result."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the video to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the video to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the video caption. See formatting 
+    """Optional. Mode for parsing entities in the video caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     video_width: Option[int] = Nothing
     """Optional. Video width."""
 
@@ -4326,8 +4539,8 @@ class InlineQueryResultVideo(InlineQueryResult):
             "InputInvoiceMessageContent",
         ]
     ] = Nothing
-    """Optional. Content of the message to be sent instead of the video. This field 
-    is required if InlineQueryResultVideo is used to send an HTML-page as a 
+    """Optional. Content of the message to be sent instead of the video. This field
+    is required if InlineQueryResultVideo is used to send an HTML-page as a
     result (e.g., a YouTube video)."""
 
 
@@ -4353,11 +4566,11 @@ class InlineQueryResultAudio(InlineQueryResult):
     """Optional. Caption, 0-1024 characters after entities parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the audio caption. See formatting 
+    """Optional. Mode for parsing entities in the audio caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
     performer: Option[str] = Nothing
@@ -4403,11 +4616,11 @@ class InlineQueryResultVoice(InlineQueryResult):
     """Optional. Caption, 0-1024 characters after entities parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the voice message caption. See formatting 
+    """Optional. Mode for parsing entities in the voice message caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
     voice_duration: Option[int] = Nothing
@@ -4450,15 +4663,15 @@ class InlineQueryResultDocument(InlineQueryResult):
     """MIME type of the content of the file, either `application/pdf` or `application/zip`."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the document to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the document to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the document caption. See formatting 
+    """Optional. Mode for parsing entities in the document caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
     description: Option[str] = Nothing
@@ -4510,21 +4723,21 @@ class InlineQueryResultLocation(InlineQueryResult):
     """Location title."""
 
     horizontal_accuracy: Option[float] = Nothing
-    """Optional. The radius of uncertainty for the location, measured in meters; 
+    """Optional. The radius of uncertainty for the location, measured in meters;
     0-1500."""
 
     live_period: Option[int] = Nothing
-    """Optional. Period in seconds during which the location can be updated, should 
-    be between 60 and 86400, or 0x7FFFFFFF for live locations that can be edited 
+    """Optional. Period in seconds during which the location can be updated, should
+    be between 60 and 86400, or 0x7FFFFFFF for live locations that can be edited
     indefinitely."""
 
     heading: Option[int] = Nothing
-    """Optional. For live locations, a direction in which the user is moving, in 
+    """Optional. For live locations, a direction in which the user is moving, in
     degrees. Must be between 1 and 360 if specified."""
 
     proximity_alert_radius: Option[int] = Nothing
-    """Optional. For live locations, a maximum distance for proximity alerts 
-    about approaching another chat member, in meters. Must be between 1 and 
+    """Optional. For live locations, a maximum distance for proximity alerts
+    about approaching another chat member, in meters. Must be between 1 and
     100000 if specified."""
 
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
@@ -4579,7 +4792,7 @@ class InlineQueryResultVenue(InlineQueryResult):
     """Optional. Foursquare identifier of the venue if known."""
 
     foursquare_type: Option[str] = Nothing
-    """Optional. Foursquare type of the venue, if known. (For example, `arts_entertainment/default`, 
+    """Optional. Foursquare type of the venue, if known. (For example, `arts_entertainment/default`,
     `arts_entertainment/aquarium` or `food/icecream`.)."""
 
     google_place_id: Option[str] = Nothing
@@ -4634,7 +4847,7 @@ class InlineQueryResultContact(InlineQueryResult):
     """Optional. Contact's last name."""
 
     vcard: Option[str] = Nothing
-    """Optional. Additional data about the contact in the form of a vCard, 0-2048 
+    """Optional. Additional data about the contact in the form of a vCard, 0-2048
     bytes."""
 
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
@@ -4702,17 +4915,20 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
     """Optional. Short description of the result."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the photo to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the photo to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the photo caption. See formatting 
+    """Optional. Mode for parsing entities in the photo caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
     """Optional. Inline keyboard attached to the message."""
 
@@ -4747,17 +4963,20 @@ class InlineQueryResultCachedGif(InlineQueryResult):
     """Optional. Title for the result."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the GIF file to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the GIF file to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the caption. See formatting options 
+    """Optional. Mode for parsing entities in the caption. See formatting options
     for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
     """Optional. Inline keyboard attached to the message."""
 
@@ -4792,17 +5011,20 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
     """Optional. Title for the result."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters after 
+    """Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters after
     entities parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the caption. See formatting options 
+    """Optional. Mode for parsing entities in the caption. See formatting options
     for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
     """Optional. Inline keyboard attached to the message."""
 
@@ -4870,15 +5092,15 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
     """Optional. Short description of the result."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the document to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the document to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the document caption. See formatting 
+    """Optional. Mode for parsing entities in the document caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
@@ -4918,17 +5140,20 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
     """Optional. Short description of the result."""
 
     caption: Option[str] = Nothing
-    """Optional. Caption of the video to be sent, 0-1024 characters after entities 
+    """Optional. Caption of the video to be sent, 0-1024 characters after entities
     parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the video caption. See formatting 
+    """Optional. Mode for parsing entities in the video caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
+    show_caption_above_media: Option[bool] = Nothing
+    """Optional. Pass True, if the caption must be shown above the message media."""
+
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
     """Optional. Inline keyboard attached to the message."""
 
@@ -4966,11 +5191,11 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
     """Optional. Caption, 0-1024 characters after entities parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the voice message caption. See formatting 
+    """Optional. Mode for parsing entities in the voice message caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
@@ -5007,11 +5232,11 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
     """Optional. Caption, 0-1024 characters after entities parsing."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the audio caption. See formatting 
+    """Optional. Mode for parsing entities in the audio caption. See formatting
     options for more details."""
 
     caption_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in the caption, which can 
+    """Optional. List of special entities that appear in the caption, which can
     be specified instead of parse_mode."""
 
     reply_markup: Option["InlineKeyboardMarkup"] = Nothing
@@ -5039,11 +5264,11 @@ class InputTextMessageContent(InputMessageContent):
     """Text of the message to be sent, 1-4096 characters."""
 
     parse_mode: Option[str] = Nothing
-    """Optional. Mode for parsing entities in the message text. See formatting 
+    """Optional. Mode for parsing entities in the message text. See formatting
     options for more details."""
 
     entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. List of special entities that appear in message text, which can 
+    """Optional. List of special entities that appear in message text, which can
     be specified instead of parse_mode."""
 
     link_preview_options: Option["LinkPreviewOptions"] = Nothing
@@ -5063,21 +5288,21 @@ class InputLocationMessageContent(InputMessageContent):
     """Longitude of the location in degrees."""
 
     horizontal_accuracy: Option[float] = Nothing
-    """Optional. The radius of uncertainty for the location, measured in meters; 
+    """Optional. The radius of uncertainty for the location, measured in meters;
     0-1500."""
 
     live_period: Option[int] = Nothing
-    """Optional. Period in seconds during which the location can be updated, should 
-    be between 60 and 86400, or 0x7FFFFFFF for live locations that can be edited 
+    """Optional. Period in seconds during which the location can be updated, should
+    be between 60 and 86400, or 0x7FFFFFFF for live locations that can be edited
     indefinitely."""
 
     heading: Option[int] = Nothing
-    """Optional. For live locations, a direction in which the user is moving, in 
+    """Optional. For live locations, a direction in which the user is moving, in
     degrees. Must be between 1 and 360 if specified."""
 
     proximity_alert_radius: Option[int] = Nothing
-    """Optional. For live locations, a maximum distance for proximity alerts 
-    about approaching another chat member, in meters. Must be between 1 and 
+    """Optional. For live locations, a maximum distance for proximity alerts
+    about approaching another chat member, in meters. Must be between 1 and
     100000 if specified."""
 
 
@@ -5103,7 +5328,7 @@ class InputVenueMessageContent(InputMessageContent):
     """Optional. Foursquare identifier of the venue, if known."""
 
     foursquare_type: Option[str] = Nothing
-    """Optional. Foursquare type of the venue, if known. (For example, `arts_entertainment/default`, 
+    """Optional. Foursquare type of the venue, if known. (For example, `arts_entertainment/default`,
     `arts_entertainment/aquarium` or `food/icecream`.)."""
 
     google_place_id: Option[str] = Nothing
@@ -5129,7 +5354,7 @@ class InputContactMessageContent(InputMessageContent):
     """Optional. Contact's last name."""
 
     vcard: Option[str] = Nothing
-    """Optional. Additional data about the contact in the form of a vCard, 0-2048 
+    """Optional. Additional data about the contact in the form of a vCard, 0-2048
     bytes."""
 
 
@@ -5146,39 +5371,43 @@ class InputInvoiceMessageContent(InputMessageContent):
     """Product description, 1-255 characters."""
 
     payload: str
-    """Bot-defined invoice payload, 1-128 bytes. This will not be displayed to 
+    """Bot-defined invoice payload, 1-128 bytes. This will not be displayed to
     the user, use for your internal processes."""
 
-    provider_token: str
-    """Payment provider token, obtained via @BotFather."""
-
-    currency: str
-    """Three-letter ISO 4217 currency code, see more on currencies."""
+    currency: Currency
+    """Three-letter ISO 4217 currency code, see more on currencies. Pass `XTR`
+    for payments in Telegram Stars."""
 
     prices: list["LabeledPrice"]
-    """Price breakdown, a JSON-serialized list of components (e.g. product price, 
-    tax, discount, delivery cost, delivery tax, bonus, etc.)."""
+    """Price breakdown, a JSON-serialized list of components (e.g. product price,
+    tax, discount, delivery cost, delivery tax, bonus, etc.). Must contain
+    exactly one item for payments in Telegram Stars."""
+
+    provider_token: Option[str] = Nothing
+    """Optional. Payment provider token, obtained via @BotFather. Pass an empty
+    string for payments in Telegram Stars."""
 
     max_tip_amount: Option[int] = Nothing
-    """Optional. The maximum accepted amount for tips in the smallest units of 
-    the currency (integer, not float/double). For example, for a maximum tip 
-    of US$ 1.45 pass max_tip_amount = 145. See the exp parameter in currencies.json, 
-    it shows the number of digits past the decimal point for each currency (2 
-    for the majority of currencies). Defaults to 0."""
+    """Optional. The maximum accepted amount for tips in the smallest units of
+    the currency (integer, not float/double). For example, for a maximum tip
+    of US$ 1.45 pass max_tip_amount = 145. See the exp parameter in currencies.json,
+    it shows the number of digits past the decimal point for each currency (2
+    for the majority of currencies). Defaults to 0. Not supported for payments
+    in Telegram Stars."""
 
     suggested_tip_amounts: Option[list[int]] = Nothing
-    """Optional. A JSON-serialized array of suggested amounts of tip in the smallest 
-    units of the currency (integer, not float/double). At most 4 suggested 
-    tip amounts can be specified. The suggested tip amounts must be positive, 
+    """Optional. A JSON-serialized array of suggested amounts of tip in the smallest
+    units of the currency (integer, not float/double). At most 4 suggested
+    tip amounts can be specified. The suggested tip amounts must be positive,
     passed in a strictly increased order and must not exceed max_tip_amount."""
 
     provider_data: Option[str] = Nothing
-    """Optional. A JSON-serialized object for data about the invoice, which will 
-    be shared with the payment provider. A detailed description of the required 
+    """Optional. A JSON-serialized object for data about the invoice, which will
+    be shared with the payment provider. A detailed description of the required
     fields should be provided by the payment provider."""
 
     photo_url: Option[str] = Nothing
-    """Optional. URL of the product photo for the invoice. Can be a photo of the goods 
+    """Optional. URL of the product photo for the invoice. Can be a photo of the goods
     or a marketing image for a service."""
 
     photo_size: Option[int] = Nothing
@@ -5191,28 +5420,32 @@ class InputInvoiceMessageContent(InputMessageContent):
     """Optional. Photo height."""
 
     need_name: Option[bool] = Nothing
-    """Optional. Pass True if you require the user's full name to complete the order."""
+    """Optional. Pass True if you require the user's full name to complete the order.
+    Ignored for payments in Telegram Stars."""
 
     need_phone_number: Option[bool] = Nothing
-    """Optional. Pass True if you require the user's phone number to complete the 
-    order."""
+    """Optional. Pass True if you require the user's phone number to complete the
+    order. Ignored for payments in Telegram Stars."""
 
     need_email: Option[bool] = Nothing
-    """Optional. Pass True if you require the user's email address to complete 
-    the order."""
+    """Optional. Pass True if you require the user's email address to complete
+    the order. Ignored for payments in Telegram Stars."""
 
     need_shipping_address: Option[bool] = Nothing
-    """Optional. Pass True if you require the user's shipping address to complete 
-    the order."""
+    """Optional. Pass True if you require the user's shipping address to complete
+    the order. Ignored for payments in Telegram Stars."""
 
     send_phone_number_to_provider: Option[bool] = Nothing
-    """Optional. Pass True if the user's phone number should be sent to provider."""
+    """Optional. Pass True if the user's phone number should be sent to the provider.
+    Ignored for payments in Telegram Stars."""
 
     send_email_to_provider: Option[bool] = Nothing
-    """Optional. Pass True if the user's email address should be sent to provider."""
+    """Optional. Pass True if the user's email address should be sent to the provider.
+    Ignored for payments in Telegram Stars."""
 
     is_flexible: Option[bool] = Nothing
-    """Optional. Pass True if the final price depends on the shipping method."""
+    """Optional. Pass True if the final price depends on the shipping method. Ignored
+    for payments in Telegram Stars."""
 
 
 class ChosenInlineResult(Model):
@@ -5235,8 +5468,8 @@ class ChosenInlineResult(Model):
     """Optional. Sender location, only for bots that require user location."""
 
     inline_message_id: Option[str] = Nothing
-    """Optional. Identifier of the sent inline message. Available only if there 
-    is an inline keyboard attached to the message. Will be also received in callback 
+    """Optional. Identifier of the sent inline message. Available only if there
+    is an inline keyboard attached to the message. Will be also received in callback
     queries and can be used to edit the message."""
 
 
@@ -5247,7 +5480,7 @@ class SentWebAppMessage(Model):
     """
 
     inline_message_id: Option[str] = Nothing
-    """Optional. Identifier of the sent inline message. Available only if there 
+    """Optional. Identifier of the sent inline message. Available only if there
     is an inline keyboard attached to the message."""
 
 
@@ -5261,9 +5494,9 @@ class LabeledPrice(Model):
     """Portion label."""
 
     amount: int
-    """Price of the product in the smallest units of the currency (integer, not 
-    float/double). For example, for a price of US$ 1.45 pass amount = 145. See 
-    the exp parameter in currencies.json, it shows the number of digits past 
+    """Price of the product in the smallest units of the currency (integer, not
+    float/double). For example, for a price of US$ 1.45 pass amount = 145. See
+    the exp parameter in currencies.json, it shows the number of digits past
     the decimal point for each currency (2 for the majority of currencies)."""
 
 
@@ -5282,13 +5515,14 @@ class Invoice(Model):
     start_parameter: str
     """Unique bot deep-linking parameter that can be used to generate this invoice."""
 
-    currency: str
-    """Three-letter ISO 4217 currency code."""
+    currency: Currency
+    """Three-letter ISO 4217 currency code, or `XTR` for payments in Telegram
+    Stars."""
 
     total_amount: int
-    """Total price in the smallest units of the currency (integer, not float/double). 
-    For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter 
-    in currencies.json, it shows the number of digits past the decimal point 
+    """Total price in the smallest units of the currency (integer, not float/double).
+    For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter
+    in currencies.json, it shows the number of digits past the decimal point
     for each currency (2 for the majority of currencies)."""
 
 
@@ -5358,17 +5592,18 @@ class SuccessfulPayment(Model):
     This object contains basic information about a successful payment.
     """
 
-    currency: str
-    """Three-letter ISO 4217 currency code."""
+    currency: Currency
+    """Three-letter ISO 4217 currency code, or `XTR` for payments in Telegram
+    Stars."""
 
     total_amount: int
-    """Total price in the smallest units of the currency (integer, not float/double). 
-    For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter 
-    in currencies.json, it shows the number of digits past the decimal point 
+    """Total price in the smallest units of the currency (integer, not float/double).
+    For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter
+    in currencies.json, it shows the number of digits past the decimal point
     for each currency (2 for the majority of currencies)."""
 
     invoice_payload: str
-    """Bot specified invoice payload."""
+    """Bot-specified invoice payload."""
 
     telegram_payment_charge_id: str
     """Telegram payment identifier."""
@@ -5383,6 +5618,32 @@ class SuccessfulPayment(Model):
     """Optional. Order information provided by the user."""
 
 
+class RefundedPayment(Model):
+    """Object `RefundedPayment`, see the [documentation](https://core.telegram.org/bots/api#refundedpayment).
+
+    This object contains basic information about a refunded payment.
+    """
+
+    currency: typing.Literal["XTR"]
+    """Three-letter ISO 4217 currency code, or `XTR` for payments in Telegram
+    Stars. Currently, always `XTR`."""
+
+    total_amount: int
+    """Total refunded price in the smallest units of the currency (integer, not
+    float/double). For example, for a price of US$ 1.45, total_amount = 145.
+    See the exp parameter in currencies.json, it shows the number of digits
+    past the decimal point for each currency (2 for the majority of currencies)."""
+
+    invoice_payload: str
+    """Bot-specified invoice payload."""
+
+    telegram_payment_charge_id: str
+    """Telegram payment identifier."""
+
+    provider_payment_charge_id: Option[str] = Nothing
+    """Optional. Provider payment identifier."""
+
+
 class ShippingQuery(Model):
     """Object `ShippingQuery`, see the [documentation](https://core.telegram.org/bots/api#shippingquery).
 
@@ -5396,7 +5657,7 @@ class ShippingQuery(Model):
     """User who sent the query."""
 
     invoice_payload: str
-    """Bot specified invoice payload."""
+    """Bot-specified invoice payload."""
 
     shipping_address: "ShippingAddress"
     """User specified shipping address."""
@@ -5414,17 +5675,18 @@ class PreCheckoutQuery(Model):
     from_: "User"
     """User who sent the query."""
 
-    currency: str
-    """Three-letter ISO 4217 currency code."""
+    currency: Currency
+    """Three-letter ISO 4217 currency code, or `XTR` for payments in Telegram
+    Stars."""
 
     total_amount: int
-    """Total price in the smallest units of the currency (integer, not float/double). 
-    For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter 
-    in currencies.json, it shows the number of digits past the decimal point 
+    """Total price in the smallest units of the currency (integer, not float/double).
+    For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter
+    in currencies.json, it shows the number of digits past the decimal point
     for each currency (2 for the majority of currencies)."""
 
     invoice_payload: str
-    """Bot specified invoice payload."""
+    """Bot-specified invoice payload."""
 
     shipping_option_id: Option[str] = Nothing
     """Optional. Identifier of the shipping option chosen by the user."""
@@ -5433,6 +5695,146 @@ class PreCheckoutQuery(Model):
     """Optional. Order information provided by the user."""
 
 
+class RevenueWithdrawalStatePending(RevenueWithdrawalState):
+    """Object `RevenueWithdrawalStatePending`, see the [documentation](https://core.telegram.org/bots/api#revenuewithdrawalstatepending).
+
+    The withdrawal is in progress.
+    """
+
+    type: typing.Literal["pending"]
+    """Type of the state, always `pending`."""
+
+
+class RevenueWithdrawalStateSucceeded(RevenueWithdrawalState):
+    """Object `RevenueWithdrawalStateSucceeded`, see the [documentation](https://core.telegram.org/bots/api#revenuewithdrawalstatesucceeded).
+
+    The withdrawal succeeded.
+    """
+
+    type: typing.Literal["succeeded"]
+    """Type of the state, always `succeeded`."""
+
+    date: datetime
+    """Date the withdrawal was completed in Unix time."""
+
+    url: str
+    """An HTTPS URL that can be used to see transaction details."""
+
+
+class RevenueWithdrawalStateFailed(RevenueWithdrawalState):
+    """Object `RevenueWithdrawalStateFailed`, see the [documentation](https://core.telegram.org/bots/api#revenuewithdrawalstatefailed).
+
+    The withdrawal failed and the transaction was refunded.
+    """
+
+    type: typing.Literal["failed"]
+    """Type of the state, always `failed`."""
+
+
+class TransactionPartnerUser(TransactionPartner):
+    """Object `TransactionPartnerUser`, see the [documentation](https://core.telegram.org/bots/api#transactionpartneruser).
+
+    Describes a transaction with a user.
+    """
+
+    type: typing.Literal["user"]
+    """Type of the transaction partner, always `user`."""
+
+    user: "User"
+    """Information about the user."""
+
+    invoice_payload: Option[str] = Nothing
+    """Optional. Bot-specified invoice payload."""
+
+
+class TransactionPartnerFragment(TransactionPartner):
+    """Object `TransactionPartnerFragment`, see the [documentation](https://core.telegram.org/bots/api#transactionpartnerfragment).
+
+    Describes a withdrawal transaction with Fragment.
+    """
+
+    type: typing.Literal["fragment"]
+    """Type of the transaction partner, always `fragment`."""
+
+    withdrawal_state: Option[
+        Variative[
+            "RevenueWithdrawalStatePending", "RevenueWithdrawalStateSucceeded", "RevenueWithdrawalStateFailed"
+        ]
+    ] = Nothing
+    """Optional. State of the transaction if the transaction is outgoing."""
+
+
+class TransactionPartnerTelegramAds(TransactionPartner):
+    """Object `TransactionPartnerTelegramAds`, see the [documentation](https://core.telegram.org/bots/api#transactionpartnertelegramads).
+
+    Describes a withdrawal transaction to the Telegram Ads platform.
+    """
+
+    type: typing.Literal["telegram_ads"]
+    """Type of the transaction partner, always `telegram_ads`."""
+
+
+class TransactionPartnerOther(TransactionPartner):
+    """Object `TransactionPartnerOther`, see the [documentation](https://core.telegram.org/bots/api#transactionpartnerother).
+
+    Describes a transaction with an unknown source or recipient.
+    """
+
+    type: typing.Literal["other"]
+    """Type of the transaction partner, always `other`."""
+
+
+class StarTransaction(Model):
+    """Object `StarTransaction`, see the [documentation](https://core.telegram.org/bots/api#startransaction).
+
+    Describes a Telegram Star transaction.
+    """
+
+    id: str
+    """Unique identifier of the transaction. Coincides with the identifer of
+    the original transaction for refund transactions. Coincides with SuccessfulPayment.telegram_payment_charge_id
+    for successful incoming payments from users."""
+
+    amount: int
+    """Number of Telegram Stars transferred by the transaction."""
+
+    date: datetime
+    """Date the transaction was created in Unix time."""
+
+    source: Option[
+        Variative[
+            "TransactionPartnerUser",
+            "TransactionPartnerFragment",
+            "TransactionPartnerTelegramAds",
+            "TransactionPartnerOther",
+        ]
+    ] = Nothing
+    """Optional. Source of an incoming transaction (e.g., a user purchasing goods
+    or services, Fragment refunding a failed withdrawal). Only for incoming
+    transactions."""
+
+    receiver: Option[
+        Variative[
+            "TransactionPartnerUser",
+            "TransactionPartnerFragment",
+            "TransactionPartnerTelegramAds",
+            "TransactionPartnerOther",
+        ]
+    ] = Nothing
+    """Optional. Receiver of an outgoing transaction (e.g., a user for a purchase
+    refund, Fragment for a withdrawal). Only for outgoing transactions."""
+
+
+class StarTransactions(Model):
+    """Object `StarTransactions`, see the [documentation](https://core.telegram.org/bots/api#startransactions).
+
+    Contains a list of Telegram Star transactions.
+    """
+
+    transactions: list["StarTransaction"]
+    """The list of transactions."""
+
+
 class PassportData(Model):
     """Object `PassportData`, see the [documentation](https://core.telegram.org/bots/api#passportdata).
 
@@ -5440,7 +5842,7 @@ class PassportData(Model):
     """
 
     data: list["EncryptedPassportElement"]
-    """Array with information about documents and other Telegram Passport elements 
+    """Array with information about documents and other Telegram Passport elements
     that was shared with the bot."""
 
     credentials: "EncryptedCredentials"
@@ -5457,7 +5859,7 @@ class PassportFile(Model):
     """Identifier for this file, which can be used to download or reuse the file."""
 
     file_unique_id: str
-    """Unique identifier for this file, which is supposed to be the same over time 
+    """Unique identifier for this file, which is supposed to be the same over time
     and for different bots. Can't be used to download or reuse the file."""
 
     file_size: int
@@ -5474,55 +5876,55 @@ class EncryptedPassportElement(Model):
     """
 
     type: EncryptedPassportElementType
-    """Element type. One of `personal_details`, `passport`, `driver_license`, 
-    `identity_card`, `internal_passport`, `address`, `utility_bill`, 
-    `bank_statement`, `rental_agreement`, `passport_registration`, 
+    """Element type. One of `personal_details`, `passport`, `driver_license`,
+    `identity_card`, `internal_passport`, `address`, `utility_bill`,
+    `bank_statement`, `rental_agreement`, `passport_registration`,
     `temporary_registration`, `phone_number`, `email`."""
 
     hash: str
     """Base64-encoded element hash for using in PassportElementErrorUnspecified."""
 
     data: Option[str] = Nothing
-    """Optional. Base64-encoded encrypted Telegram Passport element data provided 
-    by the user; available only for `personal_details`, `passport`, `driver_license`, 
-    `identity_card`, `internal_passport` and `address` types. Can be decrypted 
+    """Optional. Base64-encoded encrypted Telegram Passport element data provided
+    by the user; available only for `personal_details`, `passport`, `driver_license`,
+    `identity_card`, `internal_passport` and `address` types. Can be decrypted
     and verified using the accompanying EncryptedCredentials."""
 
     phone_number: Option[str] = Nothing
-    """Optional. User's verified phone number; available only for `phone_number` 
+    """Optional. User's verified phone number; available only for `phone_number`
     type."""
 
     email: Option[str] = Nothing
     """Optional. User's verified email address; available only for `email` type."""
 
     files: Option[list["PassportFile"]] = Nothing
-    """Optional. Array of encrypted files with documents provided by the user; 
-    available only for `utility_bill`, `bank_statement`, `rental_agreement`, 
-    `passport_registration` and `temporary_registration` types. Files 
+    """Optional. Array of encrypted files with documents provided by the user;
+    available only for `utility_bill`, `bank_statement`, `rental_agreement`,
+    `passport_registration` and `temporary_registration` types. Files
     can be decrypted and verified using the accompanying EncryptedCredentials."""
 
     front_side: Option["PassportFile"] = Nothing
-    """Optional. Encrypted file with the front side of the document, provided 
-    by the user; available only for `passport`, `driver_license`, `identity_card` 
-    and `internal_passport`. The file can be decrypted and verified using 
+    """Optional. Encrypted file with the front side of the document, provided
+    by the user; available only for `passport`, `driver_license`, `identity_card`
+    and `internal_passport`. The file can be decrypted and verified using
     the accompanying EncryptedCredentials."""
 
     reverse_side: Option["PassportFile"] = Nothing
-    """Optional. Encrypted file with the reverse side of the document, provided 
-    by the user; available only for `driver_license` and `identity_card`. 
+    """Optional. Encrypted file with the reverse side of the document, provided
+    by the user; available only for `driver_license` and `identity_card`.
     The file can be decrypted and verified using the accompanying EncryptedCredentials."""
 
     selfie: Option["PassportFile"] = Nothing
-    """Optional. Encrypted file with the selfie of the user holding a document, 
-    provided by the user; available if requested for `passport`, `driver_license`, 
-    `identity_card` and `internal_passport`. The file can be decrypted and 
+    """Optional. Encrypted file with the selfie of the user holding a document,
+    provided by the user; available if requested for `passport`, `driver_license`,
+    `identity_card` and `internal_passport`. The file can be decrypted and
     verified using the accompanying EncryptedCredentials."""
 
     translation: Option[list["PassportFile"]] = Nothing
-    """Optional. Array of encrypted files with translated versions of documents 
-    provided by the user; available if requested for `passport`, `driver_license`, 
-    `identity_card`, `internal_passport`, `utility_bill`, `bank_statement`, 
-    `rental_agreement`, `passport_registration` and `temporary_registration` 
+    """Optional. Array of encrypted files with translated versions of documents
+    provided by the user; available if requested for `passport`, `driver_license`,
+    `identity_card`, `internal_passport`, `utility_bill`, `bank_statement`,
+    `rental_agreement`, `passport_registration` and `temporary_registration`
     types. Files can be decrypted and verified using the accompanying EncryptedCredentials."""
 
 
@@ -5533,15 +5935,15 @@ class EncryptedCredentials(Model):
     """
 
     data: str
-    """Base64-encoded encrypted JSON-serialized data with unique user's payload, 
-    data hashes and secrets required for EncryptedPassportElement decryption 
+    """Base64-encoded encrypted JSON-serialized data with unique user's payload,
+    data hashes and secrets required for EncryptedPassportElement decryption
     and authentication."""
 
     hash: str
     """Base64-encoded data hash for data authentication."""
 
     secret: str
-    """Base64-encoded secret, encrypted with the bot's public RSA key, required 
+    """Base64-encoded secret, encrypted with the bot's public RSA key, required
     for data decryption."""
 
 
@@ -5555,14 +5957,15 @@ class PassportElementErrorDataField(PassportElementError):
     """Error source, must be data."""
 
     type: typing.Literal[
+        "personal_details",
         "passport",
         "driver_license",
         "identity_card",
         "internal_passport",
         "address",
     ]
-    """The section of the user's Telegram Passport which has the error, one of `personal_details`, 
-    `passport`, `driver_license`, `identity_card`, `internal_passport`, 
+    """The section of the user's Telegram Passport which has the error, one of `personal_details`,
+    `passport`, `driver_license`, `identity_card`, `internal_passport`,
     `address`."""
 
     field_name: str
@@ -5590,7 +5993,7 @@ class PassportElementErrorFrontSide(PassportElementError):
         "identity_card",
         "internal_passport",
     ]
-    """The section of the user's Telegram Passport which has the issue, one of `passport`, 
+    """The section of the user's Telegram Passport which has the issue, one of `passport`,
     `driver_license`, `identity_card`, `internal_passport`."""
 
     file_hash: str
@@ -5610,7 +6013,7 @@ class PassportElementErrorReverseSide(PassportElementError):
     """Error source, must be reverse_side."""
 
     type: typing.Literal["driver_license", "identity_card"]
-    """The section of the user's Telegram Passport which has the issue, one of `driver_license`, 
+    """The section of the user's Telegram Passport which has the issue, one of `driver_license`,
     `identity_card`."""
 
     file_hash: str
@@ -5635,7 +6038,7 @@ class PassportElementErrorSelfie(PassportElementError):
         "identity_card",
         "internal_passport",
     ]
-    """The section of the user's Telegram Passport which has the issue, one of `passport`, 
+    """The section of the user's Telegram Passport which has the issue, one of `passport`,
     `driver_license`, `identity_card`, `internal_passport`."""
 
     file_hash: str
@@ -5661,8 +6064,8 @@ class PassportElementErrorFile(PassportElementError):
         "passport_registration",
         "temporary_registration",
     ]
-    """The section of the user's Telegram Passport which has the issue, one of `utility_bill`, 
-    `bank_statement`, `rental_agreement`, `passport_registration`, 
+    """The section of the user's Telegram Passport which has the issue, one of `utility_bill`,
+    `bank_statement`, `rental_agreement`, `passport_registration`,
     `temporary_registration`."""
 
     file_hash: str
@@ -5688,8 +6091,8 @@ class PassportElementErrorFiles(PassportElementError):
         "passport_registration",
         "temporary_registration",
     ]
-    """The section of the user's Telegram Passport which has the issue, one of `utility_bill`, 
-    `bank_statement`, `rental_agreement`, `passport_registration`, 
+    """The section of the user's Telegram Passport which has the issue, one of `utility_bill`,
+    `bank_statement`, `rental_agreement`, `passport_registration`,
     `temporary_registration`."""
 
     file_hashes: list[str]
@@ -5719,9 +6122,9 @@ class PassportElementErrorTranslationFile(PassportElementError):
         "passport_registration",
         "temporary_registration",
     ]
-    """Type of element of the user's Telegram Passport which has the issue, one 
-    of `passport`, `driver_license`, `identity_card`, `internal_passport`, 
-    `utility_bill`, `bank_statement`, `rental_agreement`, `passport_registration`, 
+    """Type of element of the user's Telegram Passport which has the issue, one
+    of `passport`, `driver_license`, `identity_card`, `internal_passport`,
+    `utility_bill`, `bank_statement`, `rental_agreement`, `passport_registration`,
     `temporary_registration`."""
 
     file_hash: str
@@ -5751,9 +6154,9 @@ class PassportElementErrorTranslationFiles(PassportElementError):
         "passport_registration",
         "temporary_registration",
     ]
-    """Type of element of the user's Telegram Passport which has the issue, one 
-    of `passport`, `driver_license`, `identity_card`, `internal_passport`, 
-    `utility_bill`, `bank_statement`, `rental_agreement`, `passport_registration`, 
+    """Type of element of the user's Telegram Passport which has the issue, one
+    of `passport`, `driver_license`, `identity_card`, `internal_passport`,
+    `utility_bill`, `bank_statement`, `rental_agreement`, `passport_registration`,
     `temporary_registration`."""
 
     file_hashes: list[str]
@@ -5798,17 +6201,17 @@ class Game(Model):
     """Photo that will be displayed in the game message in chats."""
 
     text: Option[str] = Nothing
-    """Optional. Brief description of the game or high scores included in the game 
-    message. Can be automatically edited to include current high scores for 
-    the game when the bot calls setGameScore, or manually edited using editMessageText. 
+    """Optional. Brief description of the game or high scores included in the game
+    message. Can be automatically edited to include current high scores for
+    the game when the bot calls setGameScore, or manually edited using editMessageText.
     0-4096 characters."""
 
     text_entities: Option[list["MessageEntity"]] = Nothing
-    """Optional. Special entities that appear in text, such as usernames, URLs, 
+    """Optional. Special entities that appear in text, such as usernames, URLs,
     bot commands, etc."""
 
     animation: Option["Animation"] = Nothing
-    """Optional. Animation that will be displayed in the game message in chats. 
+    """Optional. Animation that will be displayed in the game message in chats.
     Upload via BotFather."""
 
 
@@ -5953,6 +6356,9 @@ class GameHighScore(Model):
     "InputMediaPhoto",
     "InputMediaVideo",
     "InputMessageContent",
+    "InputPaidMedia",
+    "InputPaidMediaPhoto",
+    "InputPaidMediaVideo",
     "InputPollOption",
     "InputSticker",
     "InputTextMessageContent",
@@ -5985,6 +6391,11 @@ class GameHighScore(Model):
     "MessageReactionUpdated",
     "Model",
     "OrderInfo",
+    "PaidMedia",
+    "PaidMediaInfo",
+    "PaidMediaPhoto",
+    "PaidMediaPreview",
+    "PaidMediaVideo",
     "PassportData",
     "PassportElementError",
     "PassportElementErrorDataField",
@@ -6007,21 +6418,33 @@ class GameHighScore(Model):
     "ReactionType",
     "ReactionTypeCustomEmoji",
     "ReactionTypeEmoji",
+    "RefundedPayment",
     "ReplyKeyboardMarkup",
     "ReplyKeyboardRemove",
     "ReplyParameters",
     "ResponseParameters",
+    "RevenueWithdrawalState",
+    "RevenueWithdrawalStateFailed",
+    "RevenueWithdrawalStatePending",
+    "RevenueWithdrawalStateSucceeded",
     "SentWebAppMessage",
     "SharedUser",
     "ShippingAddress",
     "ShippingOption",
     "ShippingQuery",
+    "StarTransaction",
+    "StarTransactions",
     "Sticker",
     "StickerSet",
     "Story",
     "SuccessfulPayment",
     "SwitchInlineQueryChosenChat",
     "TextQuote",
+    "TransactionPartner",
+    "TransactionPartnerFragment",
+    "TransactionPartnerOther",
+    "TransactionPartnerTelegramAds",
+    "TransactionPartnerUser",
     "Update",
     "User",
     "UserChatBoosts",
diff --git a/typegen/__init__.py b/typegen/__init__.py
index 50b44ecf..5b02d870 100755
--- a/typegen/__init__.py
+++ b/typegen/__init__.py
@@ -5,11 +5,10 @@
     ConfigObjectLiteralTypes,
     MethodGenerator,
     ObjectGenerator,
-    SchemaJson,
-    convert_schema_to_model,
     find_nicifications,
     generate,
-    get_schema_json,
+    get_bot_api_schema,
+    read_config_default_api_params,
     read_config_literals,
     sort_all,
 )
@@ -34,12 +33,11 @@
     "ObjectField",
     "ObjectGenerator",
     "ObjectSchema",
-    "SchemaJson",
     "TelegramBotAPISchema",
-    "convert_schema_to_model",
     "find_nicifications",
     "generate",
-    "get_schema_json",
+    "get_bot_api_schema",
+    "read_config_default_api_params",
     "read_config_literals",
     "sort_all",
 )
diff --git a/typegen/config_default_api_params.json b/typegen/config_default_api_params.json
new file mode 100644
index 00000000..b1703331
--- /dev/null
+++ b/typegen/config_default_api_params.json
@@ -0,0 +1,16 @@
+{
+    "default_params": [
+        {
+            "name": "parse_mode",
+            "type": "String"
+        },
+        {
+            "name": "question_parse_mode",
+            "type": "String"
+        },
+        {
+            "name": "explanation_parse_mode",
+            "type": "String"
+        }
+    ]
+}
diff --git a/typegen/config_literal_types.json b/typegen/config_literal_types.json
index f97b8b27..493bfa0d 100755
--- a/typegen/config_literal_types.json
+++ b/typegen/config_literal_types.json
@@ -203,6 +203,185 @@
                 }
             ]
         },
+        {
+            "name": "InputInvoiceMessageContent",
+            "fields": [
+                {
+                    "name": "currency",
+                    "enum": "Currency"
+                }
+            ]
+        },
+        {
+            "name": "Invoice",
+            "fields": [
+                {
+                    "name": "currency",
+                    "enum": "Currency"
+                }
+            ]
+        },
+        {
+            "name": "SuccessfulPayment",
+            "fields": [
+                {
+                    "name": "currency",
+                    "enum": "Currency"
+                }
+            ]
+        },
+        {
+            "name": "PreCheckoutQuery",
+            "fields": [
+                {
+                    "name": "currency",
+                    "enum": "Currency"
+                }
+            ]
+        },
+        {
+            "name": "PaidMediaPreview",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "preview"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "PaidMediaPhoto",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "photo"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "PaidMediaVideo",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "video"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "InputPaidMediaPhoto",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "photo"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "InputPaidMediaVideo",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "video"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "TransactionPartnerTelegramAds",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "telegram_ads"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "RevenueWithdrawalStatePending",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "pending"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "RevenueWithdrawalStateSucceeded",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "succeeded"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "RevenueWithdrawalStateFailed",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "failed"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "RefundedPayment",
+            "fields": [
+                {
+                    "name": "currency",
+                    "literals": [
+                        "XTR"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "TransactionPartnerFragment",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "fragment"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "TransactionPartnerUser",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "user"
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "TransactionPartnerOther",
+            "fields": [
+                {
+                    "name": "type",
+                    "literals": [
+                        "other"
+                    ]
+                }
+            ]
+        },
         {
             "name": "EncryptedPassportElement",
             "fields": [
@@ -724,6 +903,7 @@
                 {
                     "name": "type",
                     "literals": [
+                        "personal_details",
                         "passport",
                         "driver_license",
                         "identity_card",
@@ -984,7 +1164,7 @@
                 {
                     "name": "type",
                     "literals": [
-                        "quiz", 
+                        "quiz",
                         "regular"
                     ]
                 }
@@ -1014,7 +1194,7 @@
                 {
                     "name": "sticker_format",
                     "literals": [
-                        "static", 
+                        "static",
                         "animated",
                         "video"
                     ]
@@ -1027,7 +1207,7 @@
                 {
                     "name": "sticker_format",
                     "literals": [
-                        "static", 
+                        "static",
                         "animated",
                         "video"
                     ]
@@ -1035,7 +1215,7 @@
                 {
                     "name": "sticker_type",
                     "literals": [
-                        "regular", 
+                        "regular",
                         "mask",
                         "custom_emoji"
                     ]
@@ -1043,4 +1223,4 @@
             ]
         }
     ]
-}
\ No newline at end of file
+}
diff --git a/typegen/generator.py b/typegen/generator.py
index 4eaf331e..dee07e42 100755
--- a/typegen/generator.py
+++ b/typegen/generator.py
@@ -1,4 +1,6 @@
+import dataclasses
 import datetime
+import keyword
 import os
 import pathlib
 import re
@@ -20,11 +22,11 @@
     from telegrinder.modules import logger
 except ImportError:
     import logging
+
     logger = logging.getLogger("typegen")
 
 ModelT = typing.TypeVar("ModelT", bound=msgspec.structs.Struct)
 
-JSON_DECODER: typing.Final[msgspec.json.Decoder[typing.Any]] = msgspec.json.Decoder(strict=True)
 URL: typing.Final[str] = "https://raw.githubusercontent.com/PaulSonOfLars/telegram-bot-api-spec/main/api.json"
 TAB: typing.Final[str] = "    "
 TYPES: typing.Final[dict[str, str]] = {
@@ -35,16 +37,14 @@
     "Unixtime": "datetime",
 }
 INPUTFILE_DOCSTRING: typing.Final[str] = (
-    "to upload a new one using multipart/form-data"
-    " under <file_attach_name> name."
+    "to upload a new one using multipart/form-data under <file_attach_name> name."
 )
 MAIN_DIR: typing.Final[str] = "typegen"
 
 
-def get_schema_json() -> "SchemaJson":
-    logger.debug(f"Sending a get request {URL!r}")
-    raw = requests.get(URL).text
-    dct: dict[str, typing.Any] = JSON_DECODER.decode(raw)
+def get_bot_api_schema() -> "TelegramBotAPISchema":
+    logger.debug(f"Getting schema from {URL!r}")
+    dct: dict[str, typing.Any] = msgspec.json.decode(requests.get(URL).text)
     dct["methods"] = [d for d in dct["methods"].values()]
     dct["types"] = [d for d in dct["types"].values()]
     logger.debug(
@@ -53,7 +53,7 @@ def get_schema_json() -> "SchemaJson":
             dct["release_date"],
         )
     )
-    return typing.cast(SchemaJson, dct)
+    return msgspec.convert(dct, TelegramBotAPISchema)
 
 
 def find_nicifications(name: str, path: str) -> tuple[str | None, list[str]]:
@@ -74,17 +74,17 @@ def chunks_str(s: str, sep: str = "\n"):
         s += word + " "
         line += len(word)
         if line >= 60:
-            s += sep
+            s = s.strip() + sep
             line = 0
     return s.rstrip()
 
 
+def makesafe(s: str) -> str:
+    return s + "_" if keyword.iskeyword(s) else s
+
+
 def sort_all(path: pathlib.Path) -> None:
-    files = tuple(
-        str(p)
-        for p in path.iterdir()
-        if p.is_file() and p.suffix == ".py"
-    )
+    files = tuple(str(p) for p in path.iterdir() if p.is_file() and p.suffix == ".py")
     os.system(f"sort-all {' '.join(files)}")
 
 
@@ -122,17 +122,14 @@ def camel_to_pascal(s: str) -> str:
     return (s[0].upper() if s[0].islower() else s[0]) + s[1:]
 
 
-def convert_schema_to_model(schema_json: "SchemaJson", model: type[ModelT]) -> ModelT:
-    logger.debug(f"Converting to model {model.__name__!r}...")
-    schema = msgspec.convert(schema_json, type=model)
-    logger.debug(f"Schema successfully converted to model {model.__name__!r}!")
-    return schema
-
-
 def read_config_literals(path: str | None = None) -> "ConfigLiteralTypes":
     path = path or MAIN_DIR + "/config_literal_types.json"
-    with open(path, mode="r", encoding="UTF-8") as f:
-        return JSON_DECODER.decode(f.read())
+    return msgspec.json.decode(pathlib.Path(path).read_text(encoding="UTF-8"), type=ConfigLiteralTypes)
+
+
+def read_config_default_api_params(path: str | None = None) -> "ConfigDefaultAPIParams":
+    path = path or MAIN_DIR + "/config_default_api_params.json"
+    return msgspec.json.decode(pathlib.Path(path).read_text(encoding="UTF-8"), type=ConfigDefaultAPIParams)
 
 
 def is_unixtime_type(name: str, types: list[str], description: str) -> bool:
@@ -143,65 +140,67 @@ def is_unixtime_type(name: str, types: list[str], description: str) -> bool:
     )
 
 
-class SchemaJson(typing.TypedDict):
-    version: str
-    release_date: str
-    changelog: str
-    methods: list[dict[str, typing.Any]]
-    types: list[dict[str, typing.Any]]
+@dataclasses.dataclass(kw_only=True)
+class ConfigLiteralTypes:
+    objects: list["ConfigObjectLiteralTypes"] = dataclasses.field(default_factory=lambda: [])
+    methods: list["ConfigMethodLiteralTypes"] = dataclasses.field(default_factory=lambda: [])
 
 
-class ConfigLiteralTypes(typing.TypedDict, total=True):
-    objects: typing.NotRequired[list["ConfigObjectLiteralTypes"]]
-    methods: typing.NotRequired[list["ConfigMethodLiteralTypes"]]
-
-
-class FieldLiteralTypes(typing.TypedDict, total=True):
-    """Mapping containing field name, reference to enumeration and specific enumeration literals."""
-
+@dataclasses.dataclass(kw_only=True)
+class FieldLiteralTypes:
     name: str
     """Field name."""
 
-    enum: typing.NotRequired[str]
+    enum: str | None = None
     """Optional. Reference to enumeration."""
 
-    literals: typing.NotRequired[list[str | int]]
+    literals: list[str | int] = dataclasses.field(default_factory=lambda: [])
     """Optional. List with enumeration literals."""
 
 
-class ParamLiteralTypes(typing.TypedDict, total=True):
-    """Mapping containing param name, reference to enumeration and specific enumeration literals."""
-
+@dataclasses.dataclass(kw_only=True)
+class ParamLiteralTypes:
     name: str
     """Param name."""
 
-    enum: typing.NotRequired[str]
+    enum: str | None = None
     """Optional. Reference to enumeration."""
 
-    literals: typing.NotRequired[list[str | int]]
+    literals: list[str | int] = dataclasses.field(default_factory=lambda: [])
     """Optional. List with enumeration literals."""
 
 
-class ConfigObjectLiteralTypes(typing.TypedDict):
+@dataclasses.dataclass(kw_only=True)
+class ConfigObjectLiteralTypes:
     """Configuration object fields literal types."""
 
     name: str
     """Object name."""
 
-    fields: list[FieldLiteralTypes]
+    fields: list[FieldLiteralTypes] = dataclasses.field(default_factory=lambda: [])
     """Object fields."""
 
 
-class ConfigMethodLiteralTypes(typing.TypedDict):
+@dataclasses.dataclass(kw_only=True)
+class ConfigMethodLiteralTypes:
     """Configuration method params literal types."""
 
     name: str
     """Method name."""
 
-    params: list[ParamLiteralTypes]
+    params: list[ParamLiteralTypes] = dataclasses.field(default_factory=lambda: [])
     """Method params."""
 
 
+class DefaultAPIParam(typing.TypedDict):
+    name: str
+    type: str
+
+
+class ConfigDefaultAPIParams(typing.TypedDict):
+    default_params: list[DefaultAPIParam]
+
+
 class ABCGenerator(ABC):
     @abstractmethod
     def generate(self, path: str) -> None:
@@ -216,39 +215,27 @@ def __init__(
         config_literal_types: list[ConfigObjectLiteralTypes] | None = None,
     ) -> None:
         self.objects = objects
-        self.rename_field_names = {
-            "from": "from_",
-            "for": "for_",
-            "in": "in_",
-        }
         self.nicification_path = nicification_path
         self.config_literal_types = config_literal_types or []
-        self.parent_types: dict[str, list[str]] = {
-            obj.name: obj.subtypes for obj in objects if obj.subtypes
-        }
+        self.parent_types: dict[str, list[str]] = {obj.name: obj.subtypes for obj in objects if obj.subtypes}
 
-    def get_field_literal_types(
-        self, object_name: str, field_name: str
-    ) -> FieldLiteralTypes | None:
+    def get_field_literal_types(self, object_name: str, field_name: str) -> FieldLiteralTypes | None:
         for cfg in self.config_literal_types:
-            if cfg["name"] == object_name:
-                for ref_field in cfg["fields"]:
-                    if ref_field["name"] == field_name:
+            if cfg.name == object_name:
+                for ref_field in cfg.fields:
+                    if ref_field.name == field_name:
                         return ref_field
         return None
 
-    def make_object_field(
-        self, field: ObjectField, literal_types: FieldLiteralTypes | None = None
-    ) -> str:
-        code = f"{self.rename_field_names.get(field.name, field.name)}: "
+    def make_object_field(self, field: ObjectField, literal_types: FieldLiteralTypes | None = None) -> str:
+        code = makesafe(field.name) + ": "
         field_type = "typing.Any"
 
-        if literal_types is not None and any(x in literal_types for x in ("literals", "enum")):
-            literal_type_hint = literal_types.get("enum") or "typing.Literal[%s]" % ", ".join(
-                f'"{x}"' if isinstance(x, str) else str(x)
-                for x in literal_types.get("literals", [])
+        if literal_types is not None and any((literal_types.enum, literal_types.literals)):
+            literal_type_hint = literal_types.enum or "typing.Literal[%s]" % ", ".join(
+                f'"{x}"' if isinstance(x, str) else str(x) for x in literal_types.literals
             )
-            if len(literal_types.get("literals", [])) > 3:
+            if len(literal_types.literals) > 3:
                 literal_type_hint = literal_type_hint.replace("]", ",]")
             field_type = (
                 f"list[{literal_type_hint}]"
@@ -305,9 +292,7 @@ def make_object(self, object_schema: ObjectSchema) -> str:
         description = (
             "Base object"
             if object_schema.subtypes
-            else (
-                "Object" if base is None or base == object_name else base.split(".")[-1] + " object"
-            )
+            else ("Object" if base is None or base == object_name else base.split(".")[-1] + " object")
         ) + f" `{object_name}`, see the [documentation]({object_schema.href})."
         code += '"""%s\n\n%s\n"""' % (
             description,
@@ -321,9 +306,7 @@ def make_object(self, object_schema: ObjectSchema) -> str:
         code += f"\n"
         if not object_schema.fields and not nicifications:
             if (not object_schema.subtypes and base is None) or not object_schema.subtypes:
-                logger.warning(
-                    f"Object {object_name!r} has not fields, subtypes and nicifications."
-                )
+                logger.warning(f"Object {object_name!r} has not fields, subtypes and nicification.")
             code += f"{TAB}pass" if not object_schema.description else ""
             return code
 
@@ -339,9 +322,7 @@ def make_object(self, object_schema: ObjectSchema) -> str:
         if nicifications:
             for n in nicifications:
                 if object_schema.fields:
-                    for f, t, d in re.findall(
-                        r'(\b\w+\b):\s*(.+)\s*"""(.*?)"""', n, flags=re.DOTALL
-                    ):
+                    for f, t, d in re.findall(r'(\b\w+\b):\s*(.+)\s*"""(.*?)"""', n, flags=re.DOTALL):
                         new_code = f'\n    {f}: {t.strip()}\n    """{d}"""'
                         code = re.sub(r"\n    " + f + r": .+((?:.|\n {4}|\n$)+)", new_code, code)
                         n = n.replace(new_code.strip(), "")
@@ -356,6 +337,7 @@ def generate(self, path: str) -> None:
 
         logger.debug("Generate objects...")
         lines = [
+            "import pathlib\n",
             "import typing\n\n",
             "from fntypes.co import Some, Variative\n",
             "from telegrinder.model import Model\n",
@@ -397,6 +379,7 @@ def __init__(
         methods: list[MethodSchema],
         parent_types: dict[str, list[str]],
         config_literal_types: list[ConfigMethodLiteralTypes] | None = None,
+        config_default_api_params: list[DefaultAPIParam] | None = None,
         *,
         api_version: str | None = None,
         release_date: str | None = None,
@@ -404,6 +387,7 @@ def __init__(
         self.methods = methods
         self.parent_types = parent_types
         self.config_literal_types = config_literal_types or []
+        self.config_default_api_params = config_default_api_params or []
         self.api_version = api_version
         self.release_date = release_date
 
@@ -419,9 +403,7 @@ def make_type_hint(
             return convert_to_python_type(types[0], parent_types, as_forward_ref=False)
         if len([x for x in types if x.startswith("Array of")]) > 1:
             array_of_types = [
-                types.remove(v) or v.removeprefix("Array of")
-                for v in types[:]
-                if v.startswith("Array of")
+                types.remove(v) or v.removeprefix("Array of") for v in types[:] if v.startswith("Array of")
             ]
             types.append("Array of " + ", ".join(array_of_types))
         sep = ", " if is_return_type else " | "
@@ -435,21 +417,28 @@ def get_param_literal_types(
         param_name: str,
     ) -> ParamLiteralTypes | None:
         for cfg in self.config_literal_types:
-            if cfg["name"] == method_name:
-                for param in cfg["params"]:
-                    if param["name"] == param_name:
+            if cfg.name == method_name:
+                for param in cfg.params:
+                    if param.name == param_name:
                         return param
         return None
 
+    def get_default_param(self, name: str) -> DefaultAPIParam | None:
+        name = makesafe(name)
+        for param in self.config_default_api_params:
+            if param["name"] == name:
+                return param
+        return None
+
     def make_method_body(self, method_schema: MethodSchema) -> str:
         field_descriptions = filter(
             None,
             [
                 (
-                    f":param {x.name}: "
+                    f":param {makesafe(x.name)}: "
                     + chunks_str(
                         x.description + ("." if not x.description.endswith(".") else ""),
-                        sep="\\\n" + TAB + TAB,
+                        sep=" \\\n" + TAB + TAB,
                     ).replace('"', "`")
                     if x.description
                     else ""
@@ -474,40 +463,37 @@ def make_method_body(self, method_schema: MethodSchema) -> str:
             + f"{self.make_type_hint(method_schema.returns or [], self.parent_types, is_return_type=True)})"
         )
 
-    def make_method_params(
-        self, method_name: str, params: list[MethodParameter] | None = None
-    ) -> list[str]:
+    def make_method_params(self, method_name: str, params: list[MethodParameter] | None = None) -> list[str]:
         result = []
         if not params:
             return result
 
         for p in (
-            *filter(lambda x: x.required, params),
-            *filter(lambda x: not x.required, params),
+            *filter(lambda x: x.required and not self.get_default_param(x.name), params),
+            *filter(lambda x: not x.required or self.get_default_param(x.name), params),
         ):
             literal_types = self.get_param_literal_types(method_name, p.name)
+            param_name = makesafe(p.name)
 
             if literal_types is not None:
-                tp = (
-                    literal_types["enum"]
-                    if "enum" in literal_types
-                    else "typing.Literal[%s]"
-                    % ", ".join(
-                        f'"{x}"' if isinstance(x, str) else str(x)
-                        for x in literal_types.get("literals", [])
-                    )
+                tp = literal_types.enum or "typing.Literal[%s]" % ", ".join(
+                    f'"{x}"' if isinstance(x, str) else str(x) for x in literal_types.literals
                 )
                 p.types = [tp]
 
-            if p.description and is_unixtime_type(p.name, p.types, p.description):
+            if p.description and is_unixtime_type(param_name, p.types, p.description):
                 p.types.insert(p.types.index("Integer"), "Unixtime")
 
-            code = f"{TAB * 2}{p.name}: "
-            if not p.required:
-                type_hint = self.make_type_hint(p.types)
-                code += f"{type_hint} | None = None"
+            default_param_value = (
+                None if self.get_default_param(param_name) is None else f'default_params["{param_name}"]'
+            )
+            code = f"{TAB * 2}{param_name}: "
+
+            if not p.required or default_param_value:
+                code += f"{self.make_type_hint(p.types)} {'| None ' if not p.required else ''}= {default_param_value}"
             else:
                 code += self.make_type_hint(p.types)
+
             result.append(code)
 
         return result
@@ -545,17 +531,25 @@ def generate(self, path: str) -> None:
                 )
             )
         )
+        default_params_typeddict = 'typing.TypedDict("DefaultParams", {})'.format(
+            "{%s}"
+            % ", ".join(
+                f""""{x['name']}": {convert_to_python_type(x['type'], parent_types=self.parent_types)}"""
+                for x in self.config_default_api_params
+            )
+        )
         lines = [
             "import typing\n",
             "from datetime import datetime\n\n"
             "from fntypes.co import Result, Variative\n"
             "from telegrinder.api.error import APIError\n",
-            "from telegrinder.model import full_result, get_params\n",
+            "from telegrinder.model import ProxiedDict, full_result, get_params\n",
             "from telegrinder.types.enums import *  # noqa: F403\n"
             "from telegrinder.types.objects import *  # noqa: F403\n\n"
             "if typing.TYPE_CHECKING:\n",
             "    from telegrinder.api.abc import ABCAPI\n\n\n",
             "class APIMethods:\n" + docstring,
+            f"\n\n    default_params = ProxiedDict({default_params_typeddict})\n\n",
             '    def __init__(self, api: "ABCAPI") -> None:\n',
             "        self.api = api\n\n",
         ]
@@ -577,54 +571,56 @@ def generate(
     *,
     path_dir: str | None = None,
     path_config_literals: str | None = None,
+    path_config_default_api_params: str | None = None,
     path_nicifications: str | None = None,
-    types_generator: ABCGenerator | None = None,
-    methods_generator: ABCGenerator | None = None,
+    object_generator: ABCGenerator | None = None,
+    method_generator: ABCGenerator | None = None,
 ) -> None:
     path_dir = path_dir or "telegrinder/types"
     if not os.path.exists(path_dir):
         logger.warning(f"Path dir {path_dir!r} not found. Making dir...")
         os.makedirs(path_dir)
 
-    if types_generator is None or methods_generator is None:
-        schema_json = get_schema_json()
-        schema_model = convert_schema_to_model(schema_json, TelegramBotAPISchema)
+    if object_generator is None or method_generator is None:
+        schema = get_bot_api_schema()
         cfg_literal_types = read_config_literals(path_config_literals)
-        object_generator = types_generator or ObjectGenerator(
-            objects=schema_model.objects,
+        object_generator = object_generator or ObjectGenerator(
+            objects=schema.objects,
             nicification_path=MAIN_DIR + (path_nicifications or "/nicifications.py"),
-            config_literal_types=cfg_literal_types.get("objects"),
+            config_literal_types=cfg_literal_types.objects,
         )
-        method_generator = methods_generator or MethodGenerator(
-            methods=schema_model.methods,
+        method_generator = method_generator or MethodGenerator(
+            methods=schema.methods,
             parent_types=(
-                object_generator.parent_types
-                if isinstance(object_generator, ObjectGenerator)
-                else {}
+                object_generator.parent_types if isinstance(object_generator, ObjectGenerator) else {}
             ),
-            config_literal_types=cfg_literal_types.get("methods"),
-            api_version=schema_json["version"],
-            release_date=schema_json["release_date"],
+            config_literal_types=cfg_literal_types.methods,
+            config_default_api_params=read_config_default_api_params(path_config_default_api_params)[
+                "default_params"
+            ],
+            api_version=schema.version,
+            release_date=schema.release_date,
         )
 
     object_generator.generate(path_dir)
     method_generator.generate(path_dir)
     logger.info("Schema has been successfully generated.")
 
-    logger.debug("Run black formatter...")
-    if os.system(f"black {path_dir} --config pyproject.toml") != 0:
-        logger.error("Black formatter failed.")
+    logger.debug("Run sort-all...")
+    sort_all(pathlib.Path(path_dir))
+    logger.info("Sort-all successfully sorted __all__ in files.")
+
+    logger.debug("Run ruff formatter...")
+    if os.system(f"ruff format {path_dir}") != 0:
+        logger.error("Ruff formatter failed.")
     else:
-        logger.info("Black formatter successfully formatted files.")
+        logger.info("Ruff formatter successfully formatted files.")
 
-    logger.debug("Run isort...")
-    if os.system(f"isort {path_dir}") != 0:
-        logger.error("Isort failed.")
+    logger.debug("Run ruff-isort...")
+    if os.system(f"ruff check {path_dir} --select I --select F401 --fix") != 0:
+        logger.error("ruff-isort failed.")
     else:
-        logger.info("Isort successfully sorted imports.")
-    
-    logger.debug("Run sort-all...")
-    sort_all(pathlib.Path(path_dir))
+        logger.info("Ruff-isort successfully sorted imports.")
 
 
 __all__ = (
@@ -636,11 +632,9 @@ def generate(
     "MethodGenerator",
     "ObjectGenerator",
     "ParamLiteralTypes",
-    "SchemaJson",
-    "convert_schema_to_model",
     "find_nicifications",
     "generate",
     "sort_all",
-    "get_schema_json",
+    "get_bot_api_schema",
     "read_config_literals",
 )
diff --git a/typegen/nicifications.py b/typegen/nicifications.py
index 9d5136d2..163246ed 100755
--- a/typegen/nicifications.py
+++ b/typegen/nicifications.py
@@ -1,16 +1,17 @@
 """
 Code in this file is automatically parsed.
 ---
-Nicifications are basically nice features for models which are included in auto-generated models
+Nicifications are basically nice features for models which are included in auto-generate_noded models
 The difference between nicifications and cure types is: cute types can borrow view runtime properties and have context api
 (so they can implement model-specific methods).
 Nicifications can only implement methods/properties working only with model fields.
 """
 
+import pathlib
 import typing
 from datetime import datetime
 
-from fntypes.option import Option, Some
+from fntypes.option import Option
 
 from telegrinder.msgspec_utils import Nothing
 from telegrinder.types import (
@@ -49,6 +50,9 @@ def age(self) -> Option[int]:
 
 
 class _Chat(Chat):
+    def __eq__(self, other: typing.Any) -> bool:
+        return isinstance(other, self.__class__) and self.id == other.id
+
     @property
     def full_name(self) -> Option[str]:
         """Optional. Full name (`first_name` + `last_name`) of the
@@ -74,6 +78,13 @@ def chat_id(self) -> int:
 
 
 class _Message(Message):
+    def __eq__(self, other: typing.Any) -> bool:
+        return (
+            isinstance(other, self.__class__)
+            and self.message_id == other.message_id
+            and self.chat_id == other.chat_id
+        )
+
     @property
     def content_type(self) -> ContentType:
         """Type of content that the message contains."""
@@ -104,20 +115,14 @@ def chat_title(self) -> str:
         Full name, for `private` chat."""
 
         return (
-            self.chat.full_name.unwrap()
-            if self.chat.type == ChatType.PRIVATE
-            else self.chat.title.unwrap()
-        )
-
-    def __eq__(self, other: typing.Any) -> bool:
-        return (
-            isinstance(other, self.__class__)
-            and self.message_id == other.message_id
-            and self.chat_id == other.chat_id
+            self.chat.full_name.unwrap() if self.chat.type == ChatType.PRIVATE else self.chat.title.unwrap()
         )
 
 
 class _User(User):
+    def __eq__(self, other: typing.Any) -> bool:
+        return isinstance(other, self.__class__) and self.id == other.id
+
     @property
     def default_accent_color(self) -> DefaultAccentColor:
         """User's or bot's accent color (non-premium)."""
@@ -132,19 +137,21 @@ def full_name(self) -> str:
 
 
 class _Update(Update):
+    def __eq__(self, other: typing.Any) -> bool:
+        return isinstance(other, self.__class__) and self.update_type == other.update_type
+
     @property
-    def update_type(self) -> Option[UpdateType]:
+    def update_type(self) -> UpdateType:
         """Incoming update type."""
 
-        if update := next(
-            filter(
-                lambda x: bool(x[1]),
-                self.to_dict(exclude_fields={"update_id"}).items(),
-            ),
-            None,
-        ):
-            return Some(UpdateType(update[0]))
-        return Nothing
+        return UpdateType(
+            next(
+                filter(
+                    lambda x: bool(x[1]),
+                    self.to_dict(exclude_fields={"update_id"}).items(),
+                ),
+            )[0],
+        )
 
 
 class _InputFile(typing.NamedTuple):
@@ -154,10 +161,18 @@ class _InputFile(typing.NamedTuple):
     data: bytes
     """Bytes of file."""
 
+    @classmethod
+    def from_file(cls, path: str | pathlib.Path) -> typing.Self:
+        path = pathlib.Path(path)
+        return cls(
+            filename=path.name,
+            data=path.read_bytes(),
+        )
+
 
 class _InaccessibleMessage(InaccessibleMessage):
     date: typing.Literal[0]
-    """Always 0. The field can be used to differentiate regular and inaccessible 
+    """Always 0. The field can be used to differentiate regular and inaccessible
     messages."""
 
 


Test patch:
diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml
index a4a5e28a..d9bc41a0 100644
--- a/.github/workflows/lint-and-test.yml
+++ b/.github/workflows/lint-and-test.yml
@@ -1,6 +1,6 @@
 name: Lint and test
 
-on: [push, pull_request]
+on: [push]
 
 jobs:
   build:
@@ -12,26 +12,24 @@ jobs:
           uses: actions/setup-python@v5
           with:
             python-version: "3.11"
-        
+
         - name: Install poetry
-          run: >
-            pip install pipx
-            pipx install poetry
-        
+          run: pip install --upgrade pip setuptools wheel && pip install --user pipx && pipx install poetry
+
         - name: Poetry check structure
           run: >
             poetry check
-        
+
         - name: Install depends
           run: >
             poetry install
-        
+
         - name: Lint code with ruff
           run: >
-            poetry run ruff check --fix
-                
+            poetry run ruff check . --fix
+
         - name: Check types with basedpyright
           run: >
-            poetry run basedpyright --level ERROR
-        
+            poetry run basedpyright . --level ERROR
+
         # NOTE: add run tests