mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Merge branch 'dev-3.x' into dev-3.x
This commit is contained in:
commit
2da987daef
318 changed files with 6312 additions and 2419 deletions
|
|
@ -1 +1 @@
|
||||||
5.5
|
6.1
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@ exclude_lines =
|
||||||
pragma: no cover
|
pragma: no cover
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
@overload
|
||||||
|
|
|
||||||
17
.github/workflows/label_pr.yaml
vendored
Normal file
17
.github/workflows/label_pr.yaml
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
name: Label new pull request
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
branches:
|
||||||
|
- dev-3.x
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
put-label:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Add 3.x label
|
||||||
|
uses: andymckay/labeler@master
|
||||||
|
with:
|
||||||
|
add-labels: 3.x
|
||||||
95
.github/workflows/pull_request_changelog.yml
vendored
Normal file
95
.github/workflows/pull_request_changelog.yml
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
name: "Check that changes are described"
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- "opened"
|
||||||
|
- "reopened"
|
||||||
|
- "synchronize"
|
||||||
|
- "labeled"
|
||||||
|
- "unlabeled"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
changes-required:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: "!contains(github.event.pull_request.labels.*.name, 'skip news')"
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@master
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
fetch-depth: '0'
|
||||||
|
|
||||||
|
- name: Set up Python 3.10
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
|
||||||
|
- name: Install towncrier
|
||||||
|
run: pip install towncrier
|
||||||
|
|
||||||
|
- name: Check changelog
|
||||||
|
env:
|
||||||
|
BASE_BRANCH: ${{ github.base_ref }}
|
||||||
|
run: |
|
||||||
|
git fetch --no-tags origin +refs/heads/${BASE_BRANCH}:refs/remotes/origin/${BASE_BRANCH}
|
||||||
|
towncrier check --compare-with origin/${BASE_BRANCH}
|
||||||
|
|
||||||
|
- name: Find bot comment
|
||||||
|
if: "always()"
|
||||||
|
uses: peter-evans/find-comment@v2
|
||||||
|
id: fc
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
comment-author: 'github-actions[bot]'
|
||||||
|
body-includes: Changelog
|
||||||
|
|
||||||
|
- name: Ask for changelog
|
||||||
|
if: "failure()"
|
||||||
|
uses: peter-evans/create-or-update-comment@v2
|
||||||
|
with:
|
||||||
|
edit-mode: replace
|
||||||
|
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
body: |
|
||||||
|
# :x: Changelog is required!
|
||||||
|
|
||||||
|
You need to add a brief description of the changes to the `CHANGES` directory.
|
||||||
|
|
||||||
|
For example, you can run `towncrier create <issue>.<type>` to create a file in the change directory and then write a description on that file.
|
||||||
|
|
||||||
|
Read more at [Towncrier docs](https://towncrier.readthedocs.io/en/latest/quickstart.html#creating-news-fragments)
|
||||||
|
|
||||||
|
- name: Changelog found
|
||||||
|
if: "success()"
|
||||||
|
uses: peter-evans/create-or-update-comment@v2
|
||||||
|
with:
|
||||||
|
edit-mode: replace
|
||||||
|
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
body: |
|
||||||
|
# :heavy_check_mark: Changelog found.
|
||||||
|
|
||||||
|
Thank you for adding a description of the changes
|
||||||
|
|
||||||
|
skip-news:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: "contains(github.event.pull_request.labels.*.name, 'skip news')"
|
||||||
|
steps:
|
||||||
|
- name: Find bot comment
|
||||||
|
uses: peter-evans/find-comment@v2
|
||||||
|
id: fc
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
comment-author: 'github-actions[bot]'
|
||||||
|
body-includes: Changelog
|
||||||
|
|
||||||
|
- name: Comment when docs is not needed
|
||||||
|
uses: peter-evans/create-or-update-comment@v2
|
||||||
|
with:
|
||||||
|
edit-mode: replace
|
||||||
|
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
body: |
|
||||||
|
# :corn: Changelog is not needed.
|
||||||
|
|
||||||
|
This PR does not require a changelog because `skip news` label is present.
|
||||||
22
.github/workflows/tests.yml
vendored
22
.github/workflows/tests.yml
vendored
|
|
@ -4,9 +4,29 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev-3.x
|
- dev-3.x
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/tests.yml"
|
||||||
|
- "aiogram/**"
|
||||||
|
- "tests/**"
|
||||||
|
- ".coveragerc"
|
||||||
|
- ".flake8"
|
||||||
|
- "codecov.yaml"
|
||||||
|
- "mypy.ini"
|
||||||
|
- "poetry.lock"
|
||||||
|
- "pyproject.toml"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- dev-3.x
|
- dev-3.x
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/tests.yml"
|
||||||
|
- "aiogram/**"
|
||||||
|
- "tests/**"
|
||||||
|
- ".coveragerc"
|
||||||
|
- ".flake8"
|
||||||
|
- "codecov.yaml"
|
||||||
|
- "mypy.ini"
|
||||||
|
- "poetry.lock"
|
||||||
|
- "pyproject.toml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -24,7 +44,7 @@ jobs:
|
||||||
- '3.10'
|
- '3.10'
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
# Windows is sucks. Force use bash instead of PowerShell
|
# Windows sucks. Force use bash instead of PowerShell
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,20 @@
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v3.2.0
|
rev: v4.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 21.8b0
|
rev: 22.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
files: &files '^(aiogram|tests|examples)'
|
files: &files '^(aiogram|tests|examples)'
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-isort
|
- repo: https://github.com/pre-commit/mirrors-isort
|
||||||
rev: v5.9.3
|
rev: v5.10.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
additional_dependencies: [ toml ]
|
additional_dependencies: [ toml ]
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,4 @@ python:
|
||||||
path: .
|
path: .
|
||||||
extra_requirements:
|
extra_requirements:
|
||||||
- docs
|
- docs
|
||||||
|
- redis
|
||||||
|
|
|
||||||
79
CHANGES.rst
79
CHANGES.rst
|
|
@ -14,6 +14,85 @@ Changelog
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
3.0.0b3 (2022-04-19)
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Added possibility to get command magic result as handler argument
|
||||||
|
`#889 <https://github.com/aiogram/aiogram/issues/889>`_
|
||||||
|
- Added full support of `Telegram Bot API 6.0 <https://core.telegram.org/bots/api-changelog#april-16-2022>`_
|
||||||
|
`#890 <https://github.com/aiogram/aiogram/issues/890>`_
|
||||||
|
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Fixed I18n lazy-proxy. Disabled caching.
|
||||||
|
`#839 <https://github.com/aiogram/aiogram/issues/839>`_
|
||||||
|
- Added parsing of spoiler message entity
|
||||||
|
`#865 <https://github.com/aiogram/aiogram/issues/865>`_
|
||||||
|
- Fixed default `parse_mode` for `Message.copy_to()` method.
|
||||||
|
`#876 <https://github.com/aiogram/aiogram/issues/876>`_
|
||||||
|
- Fixed CallbackData factory parsing IntEnum's
|
||||||
|
`#885 <https://github.com/aiogram/aiogram/issues/885>`_
|
||||||
|
|
||||||
|
|
||||||
|
Misc
|
||||||
|
----
|
||||||
|
|
||||||
|
- Added automated check that pull-request adds a changes description to **CHANGES** directory
|
||||||
|
`#873 <https://github.com/aiogram/aiogram/issues/873>`_
|
||||||
|
- Changed :code:`Message.html_text` and :code:`Message.md_text` attributes behaviour when message has no text.
|
||||||
|
The empty string will be used instead of raising error.
|
||||||
|
`#874 <https://github.com/aiogram/aiogram/issues/874>`_
|
||||||
|
- Used `redis-py` instead of `aioredis` package in due to this packages was merged into single one
|
||||||
|
`#882 <https://github.com/aiogram/aiogram/issues/882>`_
|
||||||
|
- Solved common naming problem with middlewares that confusing too much developers
|
||||||
|
- now you can't see the `middleware` and `middlewares` attributes at the same point
|
||||||
|
because this functionality encapsulated to special interface.
|
||||||
|
`#883 <https://github.com/aiogram/aiogram/issues/883>`_
|
||||||
|
|
||||||
|
|
||||||
|
3.0.0b2 (2022-02-19)
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Added possibility to pass additional arguments into the aiohttp webhook handler to use this
|
||||||
|
arguments inside handlers as the same as it possible in polling mode.
|
||||||
|
`#785 <https://github.com/aiogram/aiogram/issues/785>`_
|
||||||
|
- Added possibility to add handler flags via decorator (like `pytest.mark` decorator but `aiogram.flags`)
|
||||||
|
`#836 <https://github.com/aiogram/aiogram/issues/836>`_
|
||||||
|
- Added :code:`ChatActionSender` utility to automatically sends chat action while long process is running.
|
||||||
|
|
||||||
|
It also can be used as message middleware and can be customized via :code:`chat_action` flag.
|
||||||
|
`#837 <https://github.com/aiogram/aiogram/issues/837>`_
|
||||||
|
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Fixed unexpected behavior of sequences in the StateFilter.
|
||||||
|
`#791 <https://github.com/aiogram/aiogram/issues/791>`_
|
||||||
|
- Fixed exceptions filters
|
||||||
|
`#827 <https://github.com/aiogram/aiogram/issues/827>`_
|
||||||
|
|
||||||
|
|
||||||
|
Misc
|
||||||
|
----
|
||||||
|
|
||||||
|
- Logger name for processing events is changed to :code:`aiogram.events`.
|
||||||
|
`#830 <https://github.com/aiogram/aiogram/issues/830>`_
|
||||||
|
- Added full support of Telegram Bot API 5.6 and 5.7
|
||||||
|
`#835 <https://github.com/aiogram/aiogram/issues/835>`_
|
||||||
|
- **BREAKING**
|
||||||
|
Events isolation mechanism is moved from FSM storages to standalone managers
|
||||||
|
`#838 <https://github.com/aiogram/aiogram/issues/838>`_
|
||||||
|
|
||||||
|
|
||||||
3.0.0b1 (2021-12-12)
|
3.0.0b1 (2021-12-12)
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
Added possibility to pass additional arguments into the aiohttp webhook handler to use this
|
|
||||||
arguments inside handlers as the same as it possible in polling mode.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fixed unexpected behavior of sequences in the StateFilter.
|
|
||||||
7
CHANGES/894.feature.rst
Normal file
7
CHANGES/894.feature.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Added possibility to combine filters or invert result
|
||||||
|
|
||||||
|
Example:
|
||||||
|
.. code-block:: python
|
||||||
|
Text(text="demo") | Command(commands=["demo"])
|
||||||
|
MyFilter() & AnotherFilter()
|
||||||
|
~StateFilter(state='my-state')
|
||||||
1
CHANGES/896.misc.rst
Normal file
1
CHANGES/896.misc.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Restrict including routers with strings
|
||||||
1
CHANGES/901.bugfix.rst
Normal file
1
CHANGES/901.bugfix.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fixed false-positive coercing of Union types in API methods
|
||||||
5
CHANGES/906.bugfix.rst
Normal file
5
CHANGES/906.bugfix.rst
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
Added 3 missing content types:
|
||||||
|
|
||||||
|
* proximity_alert_triggered
|
||||||
|
* supergroup_chat_created
|
||||||
|
* channel_chat_created
|
||||||
1
CHANGES/907.misc.rst
Normal file
1
CHANGES/907.misc.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Changed CommandPatterType to CommandPatternType in `aiogram/dispatcher/filters/command.py`
|
||||||
1
CHANGES/922.feature.rst
Normal file
1
CHANGES/922.feature.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fixed type hints for redis TTL params.
|
||||||
1
CHANGES/929.feature.rst
Normal file
1
CHANGES/929.feature.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Added `full_name` shortcut for `Chat` object
|
||||||
1
CHANGES/936.misc.rst
Normal file
1
CHANGES/936.misc.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Added full support of `Bot API 6.1 <https://core.telegram.org/bots/api-changelog#june-20-2022>`_
|
||||||
1
CHANGES/941.misc.rst
Normal file
1
CHANGES/941.misc.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Removed deprecated :code:`router.<event>_handler` and :code:`router.register_<event>_handler` methods.
|
||||||
1
CHANGES/944.misc.rst
Normal file
1
CHANGES/944.misc.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
`MessageEntity` method `get_text` was removed and `extract` was renamed to `extract_from`
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2017-2019 Alex Root Junior
|
Copyright (c) 2017-2022 Alex Root Junior
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
software and associated documentation files (the "Software"), to deal in the Software
|
software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
|
|
||||||
19
Makefile
19
Makefile
|
|
@ -47,7 +47,7 @@ help:
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install:
|
install:
|
||||||
poetry install -E fast -E redis -E proxy -E i18n -E docs
|
poetry install -E fast -E redis -E proxy -E i18n -E docs --remove-untracked
|
||||||
$(py) pre-commit install
|
$(py) pre-commit install
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
|
|
@ -59,7 +59,7 @@ clean:
|
||||||
rm -rf `find . -name .pytest_cache`
|
rm -rf `find . -name .pytest_cache`
|
||||||
rm -rf *.egg-info
|
rm -rf *.egg-info
|
||||||
rm -f report.html
|
rm -f report.html
|
||||||
rm -f .coverage*
|
rm -f .coverage
|
||||||
rm -rf {build,dist,site,.cache,.mypy_cache,reports}
|
rm -rf {build,dist,site,.cache,.mypy_cache,reports}
|
||||||
|
|
||||||
# =================================================================================================
|
# =================================================================================================
|
||||||
|
|
@ -84,7 +84,7 @@ reformat:
|
||||||
# =================================================================================================
|
# =================================================================================================
|
||||||
.PHONY: test-run-services
|
.PHONY: test-run-services
|
||||||
test-run-services:
|
test-run-services:
|
||||||
docker-compose -f tests/docker-compose.yml -p aiogram3-dev up -d
|
@#docker-compose -f tests/docker-compose.yml -p aiogram3-dev up -d
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: test-run-services
|
test: test-run-services
|
||||||
|
|
@ -94,9 +94,6 @@ test: test-run-services
|
||||||
test-coverage: test-run-services
|
test-coverage: test-run-services
|
||||||
mkdir -p $(reports_dir)/tests/
|
mkdir -p $(reports_dir)/tests/
|
||||||
$(py) pytest --cov=aiogram --cov-config .coveragerc --html=$(reports_dir)/tests/index.html tests/ --redis $(redis_connection)
|
$(py) pytest --cov=aiogram --cov-config .coveragerc --html=$(reports_dir)/tests/index.html tests/ --redis $(redis_connection)
|
||||||
|
|
||||||
.PHONY: test-coverage-report
|
|
||||||
test-coverage-report:
|
|
||||||
$(py) coverage html -d $(reports_dir)/coverage
|
$(py) coverage html -d $(reports_dir)/coverage
|
||||||
|
|
||||||
.PHONY: test-coverage-view
|
.PHONY: test-coverage-view
|
||||||
|
|
@ -142,8 +139,10 @@ towncrier-draft-github:
|
||||||
towncrier build --draft | pandoc - -o dist/release.md
|
towncrier build --draft | pandoc - -o dist/release.md
|
||||||
|
|
||||||
.PHONY: prepare-release
|
.PHONY: prepare-release
|
||||||
prepare-release: bump towncrier-draft-github towncrier-build
|
prepare-release: bump towncrier-build
|
||||||
|
|
||||||
.PHONY: tag-release
|
.PHONY: release
|
||||||
tag-release:
|
release:
|
||||||
git tag v$(poetry version -s)
|
git add .
|
||||||
|
git commit -m "Release $(shell poetry version -s)"
|
||||||
|
git tag v$(shell poetry version -s)
|
||||||
|
|
|
||||||
23
README.rst
23
README.rst
|
|
@ -3,7 +3,7 @@ aiogram |beta badge|
|
||||||
####################
|
####################
|
||||||
|
|
||||||
.. danger::
|
.. danger::
|
||||||
This version still in development!
|
This version is still in development!
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/l/aiogram.svg
|
.. image:: https://img.shields.io/pypi/l/aiogram.svg
|
||||||
:target: https://opensource.org/licenses/MIT
|
:target: https://opensource.org/licenses/MIT
|
||||||
|
|
@ -13,7 +13,7 @@ aiogram |beta badge|
|
||||||
:target: https://pypi.python.org/pypi/aiogram
|
:target: https://pypi.python.org/pypi/aiogram
|
||||||
:alt: Supported python versions
|
:alt: Supported python versions
|
||||||
|
|
||||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.5-blue.svg?logo=telegram
|
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.1-blue.svg?logo=telegram
|
||||||
:target: https://core.telegram.org/bots/api
|
:target: https://core.telegram.org/bots/api
|
||||||
:alt: Telegram Bot API
|
:alt: Telegram Bot API
|
||||||
|
|
||||||
|
|
@ -41,12 +41,12 @@ aiogram |beta badge|
|
||||||
:target: https://app.codecov.io/gh/aiogram/aiogram
|
:target: https://app.codecov.io/gh/aiogram/aiogram
|
||||||
:alt: Codecov
|
:alt: Codecov
|
||||||
|
|
||||||
**aiogram** modern and fully asynchronous framework for
|
**aiogram** is a modern and fully asynchronous framework for
|
||||||
`Telegram Bot API <https://core.telegram.org/bots/api>`_ written in Python 3.8 with
|
`Telegram Bot API <https://core.telegram.org/bots/api>`_ written in Python 3.8 using
|
||||||
`asyncio <https://docs.python.org/3/library/asyncio.html>`_ and
|
`asyncio <https://docs.python.org/3/library/asyncio.html>`_ and
|
||||||
`aiohttp <https://github.com/aio-libs/aiohttp>`_.
|
`aiohttp <https://github.com/aio-libs/aiohttp>`_.
|
||||||
|
|
||||||
It helps you to make your bots faster and simpler.
|
Make your bots faster and more powerful!
|
||||||
|
|
||||||
.. danger::
|
.. danger::
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ It helps you to make your bots faster and simpler.
|
||||||
|
|
||||||
*aiogram* 3.0 has breaking changes.
|
*aiogram* 3.0 has breaking changes.
|
||||||
|
|
||||||
It breaks backwards compatibility by introducing new breaking changes!
|
It breaks backward compatibility by introducing new breaking changes!
|
||||||
|
|
||||||
Features
|
Features
|
||||||
========
|
========
|
||||||
|
|
@ -62,19 +62,20 @@ Features
|
||||||
- Asynchronous (`asyncio docs <https://docs.python.org/3/library/asyncio.html>`_, :pep:`492`)
|
- Asynchronous (`asyncio docs <https://docs.python.org/3/library/asyncio.html>`_, :pep:`492`)
|
||||||
- Has type hints (:pep:`484`) and can be used with `mypy <http://mypy-lang.org/>`_
|
- Has type hints (:pep:`484`) and can be used with `mypy <http://mypy-lang.org/>`_
|
||||||
- Supports `Telegram Bot API 5.3 <https://core.telegram.org/bots/api>`_
|
- Supports `Telegram Bot API 5.3 <https://core.telegram.org/bots/api>`_
|
||||||
- Telegram Bot API integration code was `autogenerated <https://github.com/aiogram/tg-codegen>`_ and can be easy re-generated when API was updated
|
- Telegram Bot API integration code was `autogenerated <https://github.com/aiogram/tg-codegen>`_ and can be easily re-generated when API gets updated
|
||||||
- Updates router (Blueprints)
|
- Updates router (Blueprints)
|
||||||
- Finite State Machine
|
- Has Finite State Machine
|
||||||
- Middlewares (incoming updates and API calls)
|
- Middlewares (incoming updates and API calls)
|
||||||
- Provides `Replies into Webhook <https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates>`_
|
- Provides `Replies into Webhook <https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates>`_
|
||||||
- Integrated I18n/L10n support with GNU Gettext (or Fluent)
|
- Integrated I18n/L10n support with GNU Gettext (or Fluent)
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
Before start using **aiogram** is highly recommend to know how to work
|
It is strongly advised that you have prior experience working
|
||||||
with `asyncio <https://docs.python.org/3/library/asyncio.html>`_.
|
with `asyncio <https://docs.python.org/3/library/asyncio.html>`_
|
||||||
|
before beginning to use **aiogram**.
|
||||||
|
|
||||||
Also if you has questions you can go to our community chats in Telegram:
|
If you have any questions, you can visit our community chats on Telegram:
|
||||||
|
|
||||||
- `English language <https://t.me/aiogram>`_
|
- `English language <https://t.me/aiogram>`_
|
||||||
- `Russian language <https://t.me/aiogram_ru>`_
|
- `Russian language <https://t.me/aiogram_ru>`_
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from .client import session
|
||||||
from .client.bot import Bot
|
from .client.bot import Bot
|
||||||
from .dispatcher import filters, handler
|
from .dispatcher import filters, handler
|
||||||
from .dispatcher.dispatcher import Dispatcher
|
from .dispatcher.dispatcher import Dispatcher
|
||||||
|
from .dispatcher.flags.flag import FlagGenerator
|
||||||
from .dispatcher.middlewares.base import BaseMiddleware
|
from .dispatcher.middlewares.base import BaseMiddleware
|
||||||
from .dispatcher.router import Router
|
from .dispatcher.router import Router
|
||||||
from .utils.magic_filter import MagicFilter
|
from .utils.magic_filter import MagicFilter
|
||||||
|
|
@ -18,6 +19,7 @@ except ImportError: # pragma: no cover
|
||||||
F = MagicFilter()
|
F = MagicFilter()
|
||||||
html = _html_decoration
|
html = _html_decoration
|
||||||
md = _markdown_decoration
|
md = _markdown_decoration
|
||||||
|
flags = FlagGenerator()
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"__api_version__",
|
"__api_version__",
|
||||||
|
|
@ -34,7 +36,8 @@ __all__ = (
|
||||||
"F",
|
"F",
|
||||||
"html",
|
"html",
|
||||||
"md",
|
"md",
|
||||||
|
"flags",
|
||||||
)
|
)
|
||||||
|
|
||||||
__version__ = "3.0.0b1"
|
__version__ = "3.0.0b4"
|
||||||
__api_version__ = "5.5"
|
__api_version__ = "6.1"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,22 +3,9 @@ from __future__ import annotations
|
||||||
import abc
|
import abc
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
from functools import partial
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import (
|
from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Final, Optional, Type, Union, cast
|
||||||
TYPE_CHECKING,
|
|
||||||
Any,
|
|
||||||
AsyncGenerator,
|
|
||||||
Awaitable,
|
|
||||||
Callable,
|
|
||||||
Final,
|
|
||||||
List,
|
|
||||||
Optional,
|
|
||||||
Type,
|
|
||||||
Union,
|
|
||||||
cast,
|
|
||||||
)
|
|
||||||
|
|
||||||
from aiogram.exceptions import (
|
from aiogram.exceptions import (
|
||||||
RestartingTelegram,
|
RestartingTelegram,
|
||||||
|
|
@ -36,26 +23,15 @@ from aiogram.exceptions import (
|
||||||
|
|
||||||
from ...methods import Response, TelegramMethod
|
from ...methods import Response, TelegramMethod
|
||||||
from ...methods.base import TelegramType
|
from ...methods.base import TelegramType
|
||||||
from ...types import UNSET, TelegramObject
|
from ...types import UNSET
|
||||||
from ..telegram import PRODUCTION, TelegramAPIServer
|
from ..telegram import PRODUCTION, TelegramAPIServer
|
||||||
from .middlewares.base import BaseRequestMiddleware
|
from .middlewares.manager import RequestMiddlewareManager
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..bot import Bot
|
from ..bot import Bot
|
||||||
|
|
||||||
_JsonLoads = Callable[..., Any]
|
_JsonLoads = Callable[..., Any]
|
||||||
_JsonDumps = Callable[..., str]
|
_JsonDumps = Callable[..., str]
|
||||||
NextRequestMiddlewareType = Callable[
|
|
||||||
["Bot", TelegramMethod[TelegramObject]], Awaitable[Response[TelegramObject]]
|
|
||||||
]
|
|
||||||
|
|
||||||
RequestMiddlewareType = Union[
|
|
||||||
BaseRequestMiddleware,
|
|
||||||
Callable[
|
|
||||||
[NextRequestMiddlewareType, "Bot", TelegramMethod[TelegramType]],
|
|
||||||
Awaitable[Response[TelegramType]],
|
|
||||||
],
|
|
||||||
]
|
|
||||||
|
|
||||||
DEFAULT_TIMEOUT: Final[float] = 60.0
|
DEFAULT_TIMEOUT: Final[float] = 60.0
|
||||||
|
|
||||||
|
|
@ -80,7 +56,7 @@ class BaseSession(abc.ABC):
|
||||||
self.json_dumps = json_dumps
|
self.json_dumps = json_dumps
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
self.middlewares: List[RequestMiddlewareType[TelegramObject]] = []
|
self.middleware = RequestMiddlewareManager()
|
||||||
|
|
||||||
def check_response(
|
def check_response(
|
||||||
self, method: TelegramMethod[TelegramType], status_code: int, content: str
|
self, method: TelegramMethod[TelegramType], status_code: int, content: str
|
||||||
|
|
@ -185,19 +161,11 @@ class BaseSession(abc.ABC):
|
||||||
return {k: self.clean_json(v) for k, v in value.items() if v is not None}
|
return {k: self.clean_json(v) for k, v in value.items() if v is not None}
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def middleware(
|
|
||||||
self, middleware: RequestMiddlewareType[TelegramObject]
|
|
||||||
) -> RequestMiddlewareType[TelegramObject]:
|
|
||||||
self.middlewares.append(middleware)
|
|
||||||
return middleware
|
|
||||||
|
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET
|
self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET
|
||||||
) -> TelegramType:
|
) -> TelegramType:
|
||||||
middleware = partial(self.make_request, timeout=timeout)
|
middleware = self.middleware.wrap_middlewares(self.make_request, timeout=timeout)
|
||||||
for m in reversed(self.middlewares):
|
return cast(TelegramType, await middleware(bot, method))
|
||||||
middleware = partial(m, middleware) # type: ignore
|
|
||||||
return await middleware(bot, method)
|
|
||||||
|
|
||||||
async def __aenter__(self) -> BaseSession:
|
async def __aenter__(self) -> BaseSession:
|
||||||
return self
|
return self
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,23 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import TYPE_CHECKING, Awaitable, Callable
|
from typing import TYPE_CHECKING, Awaitable, Callable, Union
|
||||||
|
|
||||||
from aiogram.methods import Response, TelegramMethod
|
from aiogram.methods import Response, TelegramMethod
|
||||||
from aiogram.types import TelegramObject
|
from aiogram.methods.base import TelegramType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...bot import Bot
|
from ...bot import Bot
|
||||||
|
|
||||||
|
|
||||||
NextRequestMiddlewareType = Callable[
|
NextRequestMiddlewareType = Callable[
|
||||||
["Bot", TelegramMethod[TelegramObject]], Awaitable[Response[TelegramObject]]
|
["Bot", TelegramMethod[TelegramType]], Awaitable[Response[TelegramType]]
|
||||||
|
]
|
||||||
|
RequestMiddlewareType = Union[
|
||||||
|
"BaseRequestMiddleware",
|
||||||
|
Callable[
|
||||||
|
[NextRequestMiddlewareType[TelegramType], "Bot", TelegramMethod[TelegramType]],
|
||||||
|
Awaitable[Response[TelegramType]],
|
||||||
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,10 +29,10 @@ class BaseRequestMiddleware(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
make_request: NextRequestMiddlewareType,
|
make_request: NextRequestMiddlewareType[TelegramType],
|
||||||
bot: "Bot",
|
bot: "Bot",
|
||||||
method: TelegramMethod[TelegramObject],
|
method: TelegramMethod[TelegramType],
|
||||||
) -> Response[TelegramObject]:
|
) -> Response[TelegramType]:
|
||||||
"""
|
"""
|
||||||
Execute middleware
|
Execute middleware
|
||||||
|
|
||||||
|
|
|
||||||
79
aiogram/client/session/middlewares/manager.py
Normal file
79
aiogram/client/session/middlewares/manager.py
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Awaitable,
|
||||||
|
Callable,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Sequence,
|
||||||
|
Union,
|
||||||
|
overload,
|
||||||
|
)
|
||||||
|
|
||||||
|
from aiogram.client.session.middlewares.base import (
|
||||||
|
NextRequestMiddlewareType,
|
||||||
|
RequestMiddlewareType,
|
||||||
|
)
|
||||||
|
from aiogram.methods import Response
|
||||||
|
from aiogram.methods.base import TelegramMethod, TelegramType
|
||||||
|
from aiogram.types import TelegramObject
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from aiogram import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class RequestMiddlewareManager(Sequence[RequestMiddlewareType[TelegramObject]]):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._middlewares: List[RequestMiddlewareType[TelegramObject]] = []
|
||||||
|
|
||||||
|
def register(
|
||||||
|
self,
|
||||||
|
middleware: RequestMiddlewareType[TelegramObject],
|
||||||
|
) -> RequestMiddlewareType[TelegramObject]:
|
||||||
|
self._middlewares.append(middleware)
|
||||||
|
return middleware
|
||||||
|
|
||||||
|
def unregister(self, middleware: RequestMiddlewareType[TelegramObject]) -> None:
|
||||||
|
self._middlewares.remove(middleware)
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
middleware: Optional[RequestMiddlewareType[TelegramObject]] = None,
|
||||||
|
) -> Union[
|
||||||
|
Callable[[RequestMiddlewareType[TelegramObject]], RequestMiddlewareType[TelegramObject]],
|
||||||
|
RequestMiddlewareType[TelegramObject],
|
||||||
|
]:
|
||||||
|
if middleware is None:
|
||||||
|
return self.register
|
||||||
|
return self.register(middleware)
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __getitem__(self, item: int) -> RequestMiddlewareType[TelegramObject]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __getitem__(self, item: slice) -> Sequence[RequestMiddlewareType[TelegramObject]]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __getitem__(
|
||||||
|
self, item: Union[int, slice]
|
||||||
|
) -> Union[
|
||||||
|
RequestMiddlewareType[TelegramObject], Sequence[RequestMiddlewareType[TelegramObject]]
|
||||||
|
]:
|
||||||
|
return self._middlewares[item]
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self._middlewares)
|
||||||
|
|
||||||
|
def wrap_middlewares(
|
||||||
|
self,
|
||||||
|
callback: Callable[[Bot, TelegramMethod[TelegramType]], Awaitable[Response[TelegramType]]],
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> NextRequestMiddlewareType[TelegramType]:
|
||||||
|
middleware = partial(callback, **kwargs)
|
||||||
|
for m in reversed(self._middlewares):
|
||||||
|
middleware = partial(m, middleware) # type: ignore
|
||||||
|
return middleware
|
||||||
|
|
@ -3,8 +3,7 @@ from typing import TYPE_CHECKING, Any, List, Optional, Type
|
||||||
|
|
||||||
from aiogram import loggers
|
from aiogram import loggers
|
||||||
from aiogram.methods import TelegramMethod
|
from aiogram.methods import TelegramMethod
|
||||||
from aiogram.methods.base import Response
|
from aiogram.methods.base import Response, TelegramType
|
||||||
from aiogram.types import TelegramObject
|
|
||||||
|
|
||||||
from .base import BaseRequestMiddleware, NextRequestMiddlewareType
|
from .base import BaseRequestMiddleware, NextRequestMiddlewareType
|
||||||
|
|
||||||
|
|
@ -25,10 +24,10 @@ class RequestLogging(BaseRequestMiddleware):
|
||||||
|
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
make_request: NextRequestMiddlewareType,
|
make_request: NextRequestMiddlewareType[TelegramType],
|
||||||
bot: "Bot",
|
bot: "Bot",
|
||||||
method: TelegramMethod[TelegramObject],
|
method: TelegramMethod[TelegramType],
|
||||||
) -> Response[TelegramObject]:
|
) -> Response[TelegramType]:
|
||||||
if type(method) not in self.ignore_methods:
|
if type(method) not in self.ignore_methods:
|
||||||
loggers.middlewares.info(
|
loggers.middlewares.info(
|
||||||
"Make request with method=%r by bot id=%d",
|
"Make request with method=%r by bot id=%d",
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ from ..utils.backoff import Backoff, BackoffConfig
|
||||||
from .event.bases import UNHANDLED, SkipHandler
|
from .event.bases import UNHANDLED, SkipHandler
|
||||||
from .event.telegram import TelegramEventObserver
|
from .event.telegram import TelegramEventObserver
|
||||||
from .fsm.middleware import FSMContextMiddleware
|
from .fsm.middleware import FSMContextMiddleware
|
||||||
from .fsm.storage.base import BaseStorage
|
from .fsm.storage.base import BaseEventIsolation, BaseStorage
|
||||||
from .fsm.storage.memory import MemoryStorage
|
from .fsm.storage.memory import DisabledEventIsolation, MemoryStorage
|
||||||
from .fsm.strategy import FSMStrategy
|
from .fsm.strategy import FSMStrategy
|
||||||
from .middlewares.error import ErrorsMiddleware
|
from .middlewares.error import ErrorsMiddleware
|
||||||
from .middlewares.user_context import UserContextMiddleware
|
from .middlewares.user_context import UserContextMiddleware
|
||||||
|
|
@ -35,9 +35,20 @@ class Dispatcher(Router):
|
||||||
self,
|
self,
|
||||||
storage: Optional[BaseStorage] = None,
|
storage: Optional[BaseStorage] = None,
|
||||||
fsm_strategy: FSMStrategy = FSMStrategy.USER_IN_CHAT,
|
fsm_strategy: FSMStrategy = FSMStrategy.USER_IN_CHAT,
|
||||||
isolate_events: bool = False,
|
events_isolation: Optional[BaseEventIsolation] = None,
|
||||||
|
disable_fsm: bool = False,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Root router
|
||||||
|
|
||||||
|
:param storage: Storage for FSM
|
||||||
|
:param fsm_strategy: FSM strategy
|
||||||
|
:param events_isolation: Events isolation
|
||||||
|
:param disable_fsm: Disable FSM, note that if you disable FSM
|
||||||
|
then you should not use storage and events isolation
|
||||||
|
:param kwargs: Other arguments, will be passed as keyword arguments to handlers
|
||||||
|
"""
|
||||||
super(Dispatcher, self).__init__(**kwargs)
|
super(Dispatcher, self).__init__(**kwargs)
|
||||||
|
|
||||||
# Telegram API provides originally only one event type - Update
|
# Telegram API provides originally only one event type - Update
|
||||||
|
|
@ -48,22 +59,46 @@ class Dispatcher(Router):
|
||||||
)
|
)
|
||||||
self.update.register(self._listen_update)
|
self.update.register(self._listen_update)
|
||||||
|
|
||||||
# Error handlers should works is out of all other functions and be registered before all other middlewares
|
# Error handlers should work is out of all other functions
|
||||||
|
# and should be registered before all others middlewares
|
||||||
self.update.outer_middleware(ErrorsMiddleware(self))
|
self.update.outer_middleware(ErrorsMiddleware(self))
|
||||||
|
|
||||||
# User context middleware makes small optimization for all other builtin
|
# User context middleware makes small optimization for all other builtin
|
||||||
# middlewares via caching the user and chat instances in the event context
|
# middlewares via caching the user and chat instances in the event context
|
||||||
self.update.outer_middleware(UserContextMiddleware())
|
self.update.outer_middleware(UserContextMiddleware())
|
||||||
|
|
||||||
# FSM middleware should always be registered after User context middleware
|
# FSM middleware should always be registered after User context middleware
|
||||||
# because here is used context from previous step
|
# because here is used context from previous step
|
||||||
self.fsm = FSMContextMiddleware(
|
self.fsm = FSMContextMiddleware(
|
||||||
storage=storage if storage else MemoryStorage(),
|
storage=storage if storage else MemoryStorage(),
|
||||||
strategy=fsm_strategy,
|
strategy=fsm_strategy,
|
||||||
isolate_events=isolate_events,
|
events_isolation=events_isolation if events_isolation else DisabledEventIsolation(),
|
||||||
)
|
)
|
||||||
self.update.outer_middleware(self.fsm)
|
if not disable_fsm:
|
||||||
|
# Note that when FSM middleware is disabled, the event isolation is also disabled
|
||||||
|
# Because the isolation mechanism is a part of the FSM
|
||||||
|
self.update.outer_middleware(self.fsm)
|
||||||
|
self.shutdown.register(self.fsm.close)
|
||||||
|
|
||||||
|
self.workflow_data: Dict[str, Any] = {}
|
||||||
self._running_lock = Lock()
|
self._running_lock = Lock()
|
||||||
|
|
||||||
|
def __getitem__(self, item: str) -> Any:
|
||||||
|
return self.workflow_data[item]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.workflow_data[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.workflow_data[key]
|
||||||
|
|
||||||
|
def get(self, key: str, /, default: Optional[Any] = None) -> Optional[Any]:
|
||||||
|
return self.workflow_data.get(key, default)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def storage(self) -> BaseStorage:
|
||||||
|
return self.fsm.storage
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent_router(self) -> None:
|
def parent_router(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -97,14 +132,21 @@ class Dispatcher(Router):
|
||||||
|
|
||||||
token = Bot.set_current(bot)
|
token = Bot.set_current(bot)
|
||||||
try:
|
try:
|
||||||
kwargs.update(bot=bot)
|
response = await self.update.wrap_outer_middleware(
|
||||||
response = await self.update.wrap_outer_middleware(self.update.trigger, update, kwargs)
|
self.update.trigger,
|
||||||
|
update,
|
||||||
|
{
|
||||||
|
**self.workflow_data,
|
||||||
|
**kwargs,
|
||||||
|
"bot": bot,
|
||||||
|
},
|
||||||
|
)
|
||||||
handled = response is not UNHANDLED
|
handled = response is not UNHANDLED
|
||||||
return response
|
return response
|
||||||
finally:
|
finally:
|
||||||
finish_time = loop.time()
|
finish_time = loop.time()
|
||||||
duration = (finish_time - start_time) * 1000
|
duration = (finish_time - start_time) * 1000
|
||||||
loggers.dispatcher.info(
|
loggers.event.info(
|
||||||
"Update id=%s is %s. Duration %d ms by bot id=%d",
|
"Update id=%s is %s. Duration %d ms by bot id=%d",
|
||||||
update.update_id,
|
update.update_id,
|
||||||
"handled" if handled else "not handled",
|
"handled" if handled else "not handled",
|
||||||
|
|
@ -213,11 +255,11 @@ class Dispatcher(Router):
|
||||||
try:
|
try:
|
||||||
await bot(result)
|
await bot(result)
|
||||||
except TelegramAPIError as e:
|
except TelegramAPIError as e:
|
||||||
# In due to WebHook mechanism doesn't allows to get response for
|
# In due to WebHook mechanism doesn't allow getting response for
|
||||||
# requests called in answer to WebHook request.
|
# requests called in answer to WebHook request.
|
||||||
# Need to skip unsuccessful responses.
|
# Need to skip unsuccessful responses.
|
||||||
# For debugging here is added logging.
|
# For debugging here is added logging.
|
||||||
loggers.dispatcher.error("Failed to make answer: %s: %s", e.__class__.__name__, e)
|
loggers.event.error("Failed to make answer: %s: %s", e.__class__.__name__, e)
|
||||||
|
|
||||||
async def _process_update(
|
async def _process_update(
|
||||||
self, bot: Bot, update: Update, call_answer: bool = True, **kwargs: Any
|
self, bot: Bot, update: Update, call_answer: bool = True, **kwargs: Any
|
||||||
|
|
@ -238,7 +280,7 @@ class Dispatcher(Router):
|
||||||
return response is not UNHANDLED
|
return response is not UNHANDLED
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
loggers.dispatcher.exception(
|
loggers.event.exception(
|
||||||
"Cause exception while process update id=%d by bot id=%d\n%s: %s",
|
"Cause exception while process update id=%d by bot id=%d\n%s: %s",
|
||||||
update.update_id,
|
update.update_id,
|
||||||
bot.id,
|
bot.id,
|
||||||
|
|
@ -282,7 +324,7 @@ class Dispatcher(Router):
|
||||||
try:
|
try:
|
||||||
return await self.feed_update(bot, update, **kwargs)
|
return await self.feed_update(bot, update, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
loggers.dispatcher.exception(
|
loggers.event.exception(
|
||||||
"Cause exception while process update id=%d by bot id=%d\n%s: %s",
|
"Cause exception while process update id=%d by bot id=%d\n%s: %s",
|
||||||
update.update_id,
|
update.update_id,
|
||||||
bot.id,
|
bot.id,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Callable, List
|
from typing import Any, Callable, List
|
||||||
|
|
||||||
from .handler import CallbackType, HandlerObject, HandlerType
|
from .handler import CallbackType, HandlerObject
|
||||||
|
|
||||||
|
|
||||||
class EventObserver:
|
class EventObserver:
|
||||||
|
|
@ -26,7 +26,7 @@ class EventObserver:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.handlers: List[HandlerObject] = []
|
self.handlers: List[HandlerObject] = []
|
||||||
|
|
||||||
def register(self, callback: HandlerType) -> None:
|
def register(self, callback: CallbackType) -> None:
|
||||||
"""
|
"""
|
||||||
Register callback with filters
|
Register callback with filters
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,19 @@ import contextvars
|
||||||
import inspect
|
import inspect
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Type, Union
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from magic_filter import MagicFilter
|
from magic_filter import MagicFilter
|
||||||
|
|
||||||
from aiogram.dispatcher.filters.base import BaseFilter
|
from aiogram.dispatcher.flags.getter import extract_flags_from_object
|
||||||
from aiogram.dispatcher.handler.base import BaseHandler
|
from aiogram.dispatcher.handler.base import BaseHandler
|
||||||
|
|
||||||
CallbackType = Callable[..., Awaitable[Any]]
|
CallbackType = Callable[..., Any]
|
||||||
SyncFilter = Callable[..., Any]
|
|
||||||
AsyncFilter = Callable[..., Awaitable[Any]]
|
|
||||||
FilterType = Union[SyncFilter, AsyncFilter, BaseFilter, MagicFilter]
|
|
||||||
HandlerType = Union[FilterType, Type[BaseHandler]]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CallableMixin:
|
class CallableMixin:
|
||||||
callback: HandlerType
|
callback: CallbackType
|
||||||
awaitable: bool = field(init=False)
|
awaitable: bool = field(init=False)
|
||||||
spec: inspect.FullArgSpec = field(init=False)
|
spec: inspect.FullArgSpec = field(init=False)
|
||||||
|
|
||||||
|
|
@ -49,7 +45,7 @@ class CallableMixin:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FilterObject(CallableMixin):
|
class FilterObject(CallableMixin):
|
||||||
callback: FilterType
|
callback: CallbackType
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
# TODO: Make possibility to extract and explain magic from filter object.
|
# TODO: Make possibility to extract and explain magic from filter object.
|
||||||
|
|
@ -62,7 +58,7 @@ class FilterObject(CallableMixin):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class HandlerObject(CallableMixin):
|
class HandlerObject(CallableMixin):
|
||||||
callback: HandlerType
|
callback: CallbackType
|
||||||
filters: Optional[List[FilterObject]] = None
|
filters: Optional[List[FilterObject]] = None
|
||||||
flags: Dict[str, Any] = field(default_factory=dict)
|
flags: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
@ -71,6 +67,7 @@ class HandlerObject(CallableMixin):
|
||||||
callback = inspect.unwrap(self.callback)
|
callback = inspect.unwrap(self.callback)
|
||||||
if inspect.isclass(callback) and issubclass(callback, BaseHandler):
|
if inspect.isclass(callback) and issubclass(callback, BaseHandler):
|
||||||
self.awaitable = True
|
self.awaitable = True
|
||||||
|
self.flags.update(extract_flags_from_object(callback))
|
||||||
|
|
||||||
async def check(self, *args: Any, **kwargs: Any) -> Tuple[bool, Dict[str, Any]]:
|
async def check(self, *args: Any, **kwargs: Any) -> Tuple[bool, Dict[str, Any]]:
|
||||||
if not self.filters:
|
if not self.filters:
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,18 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import functools
|
from inspect import isclass
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import (
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional, Tuple, Type
|
||||||
TYPE_CHECKING,
|
|
||||||
Any,
|
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
Generator,
|
|
||||||
List,
|
|
||||||
Optional,
|
|
||||||
Tuple,
|
|
||||||
Type,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
|
from aiogram.dispatcher.middlewares.manager import MiddlewareManager
|
||||||
|
|
||||||
from ...exceptions import FiltersResolveError
|
from ...exceptions import FiltersResolveError
|
||||||
from ...types import TelegramObject
|
from ...types import TelegramObject
|
||||||
from ..filters.base import BaseFilter
|
from ..filters.base import BaseFilter
|
||||||
from .bases import (
|
from .bases import REJECTED, UNHANDLED, MiddlewareType, SkipHandler
|
||||||
REJECTED,
|
from .handler import CallbackType, FilterObject, HandlerObject
|
||||||
UNHANDLED,
|
|
||||||
MiddlewareEventType,
|
|
||||||
MiddlewareType,
|
|
||||||
NextMiddlewareType,
|
|
||||||
SkipHandler,
|
|
||||||
)
|
|
||||||
from .handler import CallbackType, FilterObject, FilterType, HandlerObject, HandlerType
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from aiogram.dispatcher.router import Router
|
from aiogram.dispatcher.router import Router
|
||||||
|
|
@ -48,14 +32,15 @@ class TelegramEventObserver:
|
||||||
|
|
||||||
self.handlers: List[HandlerObject] = []
|
self.handlers: List[HandlerObject] = []
|
||||||
self.filters: List[Type[BaseFilter]] = []
|
self.filters: List[Type[BaseFilter]] = []
|
||||||
self.outer_middlewares: List[MiddlewareType[TelegramObject]] = []
|
|
||||||
self.middlewares: List[MiddlewareType[TelegramObject]] = []
|
self.middleware = MiddlewareManager()
|
||||||
|
self.outer_middleware = MiddlewareManager()
|
||||||
|
|
||||||
# Re-used filters check method from already implemented handler object
|
# Re-used filters check method from already implemented handler object
|
||||||
# with dummy callback which never will be used
|
# with dummy callback which never will be used
|
||||||
self._handler = HandlerObject(callback=lambda: True, filters=[])
|
self._handler = HandlerObject(callback=lambda: True, filters=[])
|
||||||
|
|
||||||
def filter(self, *filters: FilterType, **bound_filters: Any) -> None:
|
def filter(self, *filters: CallbackType, **bound_filters: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Register filter for all handlers of this event observer
|
Register filter for all handlers of this event observer
|
||||||
|
|
||||||
|
|
@ -66,7 +51,13 @@ class TelegramEventObserver:
|
||||||
if self._handler.filters is None:
|
if self._handler.filters is None:
|
||||||
self._handler.filters = []
|
self._handler.filters = []
|
||||||
self._handler.filters.extend(
|
self._handler.filters.extend(
|
||||||
[FilterObject(filter_) for filter_ in chain(resolved_filters, filters)]
|
[
|
||||||
|
FilterObject(filter_) # type: ignore
|
||||||
|
for filter_ in chain(
|
||||||
|
resolved_filters,
|
||||||
|
filters,
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def bind_filter(self, bound_filter: Type[BaseFilter]) -> None:
|
def bind_filter(self, bound_filter: Type[BaseFilter]) -> None:
|
||||||
|
|
@ -75,7 +66,11 @@ class TelegramEventObserver:
|
||||||
|
|
||||||
:param bound_filter:
|
:param bound_filter:
|
||||||
"""
|
"""
|
||||||
if not issubclass(bound_filter, BaseFilter):
|
# TODO: This functionality should be deprecated in the future
|
||||||
|
# in due to bound filter has uncontrollable ordering and
|
||||||
|
# makes debugging process is harder that explicit using filters
|
||||||
|
|
||||||
|
if not isclass(bound_filter) or not issubclass(bound_filter, BaseFilter):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"bound_filter() argument 'bound_filter' must be subclass of BaseFilter"
|
"bound_filter() argument 'bound_filter' must be subclass of BaseFilter"
|
||||||
)
|
)
|
||||||
|
|
@ -97,24 +92,17 @@ class TelegramEventObserver:
|
||||||
yield filter_
|
yield filter_
|
||||||
registry.append(filter_)
|
registry.append(filter_)
|
||||||
|
|
||||||
def _resolve_middlewares(self, *, outer: bool = False) -> List[MiddlewareType[TelegramObject]]:
|
def _resolve_middlewares(self) -> List[MiddlewareType[TelegramObject]]:
|
||||||
"""
|
middlewares: List[MiddlewareType[TelegramObject]] = []
|
||||||
Get all middlewares in a tree
|
for router in reversed(tuple(self.router.chain_head)):
|
||||||
:param *:
|
observer = router.observers[self.event_name]
|
||||||
"""
|
middlewares.extend(observer.middleware)
|
||||||
middlewares = []
|
|
||||||
if outer:
|
|
||||||
middlewares.extend(self.outer_middlewares)
|
|
||||||
else:
|
|
||||||
for router in reversed(tuple(self.router.chain_head)):
|
|
||||||
observer = router.observers[self.event_name]
|
|
||||||
middlewares.extend(observer.middlewares)
|
|
||||||
|
|
||||||
return middlewares
|
return middlewares
|
||||||
|
|
||||||
def resolve_filters(
|
def resolve_filters(
|
||||||
self,
|
self,
|
||||||
filters: Tuple[FilterType, ...],
|
filters: Tuple[CallbackType, ...],
|
||||||
full_config: Dict[str, Any],
|
full_config: Dict[str, Any],
|
||||||
ignore_default: bool = True,
|
ignore_default: bool = True,
|
||||||
) -> List[BaseFilter]:
|
) -> List[BaseFilter]:
|
||||||
|
|
@ -176,11 +164,11 @@ class TelegramEventObserver:
|
||||||
|
|
||||||
def register(
|
def register(
|
||||||
self,
|
self,
|
||||||
callback: HandlerType,
|
callback: CallbackType,
|
||||||
*filters: FilterType,
|
*filters: CallbackType,
|
||||||
flags: Optional[Dict[str, Any]] = None,
|
flags: Optional[Dict[str, Any]] = None,
|
||||||
**bound_filters: Any,
|
**bound_filters: Any,
|
||||||
) -> HandlerType:
|
) -> CallbackType:
|
||||||
"""
|
"""
|
||||||
Register event handler
|
Register event handler
|
||||||
"""
|
"""
|
||||||
|
|
@ -192,29 +180,25 @@ class TelegramEventObserver:
|
||||||
self.handlers.append(
|
self.handlers.append(
|
||||||
HandlerObject(
|
HandlerObject(
|
||||||
callback=callback,
|
callback=callback,
|
||||||
filters=[FilterObject(filter_) for filter_ in chain(resolved_filters, filters)],
|
filters=[
|
||||||
|
FilterObject(filter_) # type: ignore
|
||||||
|
for filter_ in chain(
|
||||||
|
resolved_filters,
|
||||||
|
filters,
|
||||||
|
)
|
||||||
|
],
|
||||||
flags=flags,
|
flags=flags,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _wrap_middleware(
|
|
||||||
cls, middlewares: List[MiddlewareType[MiddlewareEventType]], handler: HandlerType
|
|
||||||
) -> NextMiddlewareType[MiddlewareEventType]:
|
|
||||||
@functools.wraps(handler)
|
|
||||||
def mapper(event: TelegramObject, kwargs: Dict[str, Any]) -> Any:
|
|
||||||
return handler(event, **kwargs)
|
|
||||||
|
|
||||||
middleware = mapper
|
|
||||||
for m in reversed(middlewares):
|
|
||||||
middleware = functools.partial(m, middleware)
|
|
||||||
return middleware
|
|
||||||
|
|
||||||
def wrap_outer_middleware(
|
def wrap_outer_middleware(
|
||||||
self, callback: Any, event: TelegramObject, data: Dict[str, Any]
|
self, callback: Any, event: TelegramObject, data: Dict[str, Any]
|
||||||
) -> Any:
|
) -> Any:
|
||||||
wrapped_outer = self._wrap_middleware(self._resolve_middlewares(outer=True), callback)
|
wrapped_outer = self.middleware.wrap_middlewares(
|
||||||
|
self.outer_middleware,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
return wrapped_outer(event, data)
|
return wrapped_outer(event, data)
|
||||||
|
|
||||||
async def trigger(self, event: TelegramObject, **kwargs: Any) -> Any:
|
async def trigger(self, event: TelegramObject, **kwargs: Any) -> Any:
|
||||||
|
|
@ -233,8 +217,9 @@ class TelegramEventObserver:
|
||||||
if result:
|
if result:
|
||||||
kwargs.update(data, handler=handler)
|
kwargs.update(data, handler=handler)
|
||||||
try:
|
try:
|
||||||
wrapped_inner = self._wrap_middleware(
|
wrapped_inner = self.outer_middleware.wrap_middlewares(
|
||||||
self._resolve_middlewares(), handler.call
|
self._resolve_middlewares(),
|
||||||
|
handler.call,
|
||||||
)
|
)
|
||||||
return await wrapped_inner(event, kwargs)
|
return await wrapped_inner(event, kwargs)
|
||||||
except SkipHandler:
|
except SkipHandler:
|
||||||
|
|
@ -243,7 +228,7 @@ class TelegramEventObserver:
|
||||||
return UNHANDLED
|
return UNHANDLED
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self, *args: FilterType, flags: Optional[Dict[str, Any]] = None, **bound_filters: Any
|
self, *args: CallbackType, flags: Optional[Dict[str, Any]] = None, **bound_filters: Any
|
||||||
) -> Callable[[CallbackType], CallbackType]:
|
) -> Callable[[CallbackType], CallbackType]:
|
||||||
"""
|
"""
|
||||||
Decorator for registering event handlers
|
Decorator for registering event handlers
|
||||||
|
|
@ -254,71 +239,3 @@ class TelegramEventObserver:
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def middleware(
|
|
||||||
self,
|
|
||||||
middleware: Optional[MiddlewareType[TelegramObject]] = None,
|
|
||||||
) -> Union[
|
|
||||||
Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]],
|
|
||||||
MiddlewareType[TelegramObject],
|
|
||||||
]:
|
|
||||||
"""
|
|
||||||
Decorator for registering inner middlewares
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@<event>.middleware() # via decorator (variant 1)
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@<event>.middleware # via decorator (variant 2)
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
async def my_middleware(handler, event, data): ...
|
|
||||||
<event>.middleware(my_middleware) # via method
|
|
||||||
"""
|
|
||||||
|
|
||||||
def wrapper(m: MiddlewareType[TelegramObject]) -> MiddlewareType[TelegramObject]:
|
|
||||||
self.middlewares.append(m)
|
|
||||||
return m
|
|
||||||
|
|
||||||
if middleware is None:
|
|
||||||
return wrapper
|
|
||||||
return wrapper(middleware)
|
|
||||||
|
|
||||||
def outer_middleware(
|
|
||||||
self,
|
|
||||||
middleware: Optional[MiddlewareType[TelegramObject]] = None,
|
|
||||||
) -> Union[
|
|
||||||
Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]],
|
|
||||||
MiddlewareType[TelegramObject],
|
|
||||||
]:
|
|
||||||
"""
|
|
||||||
Decorator for registering outer middlewares
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@<event>.outer_middleware() # via decorator (variant 1)
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@<event>.outer_middleware # via decorator (variant 2)
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
async def my_middleware(handler, event, data): ...
|
|
||||||
<event>.outer_middleware(my_middleware) # via method
|
|
||||||
"""
|
|
||||||
|
|
||||||
def wrapper(m: MiddlewareType[TelegramObject]) -> MiddlewareType[TelegramObject]:
|
|
||||||
self.outer_middlewares.append(m)
|
|
||||||
return m
|
|
||||||
|
|
||||||
if middleware is None:
|
|
||||||
return wrapper
|
|
||||||
return wrapper(middleware)
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,25 @@
|
||||||
from typing import Dict, Tuple, Type
|
from typing import Dict, Tuple, Type
|
||||||
|
|
||||||
from .base import BaseFilter
|
from .base import BaseFilter
|
||||||
|
from .chat_member_updated import (
|
||||||
|
ADMINISTRATOR,
|
||||||
|
CREATOR,
|
||||||
|
IS_ADMIN,
|
||||||
|
IS_MEMBER,
|
||||||
|
IS_NOT_MEMBER,
|
||||||
|
JOIN_TRANSITION,
|
||||||
|
KICKED,
|
||||||
|
LEAVE_TRANSITION,
|
||||||
|
LEFT,
|
||||||
|
MEMBER,
|
||||||
|
PROMOTED_TRANSITION,
|
||||||
|
RESTRICTED,
|
||||||
|
ChatMemberUpdatedFilter,
|
||||||
|
)
|
||||||
from .command import Command, CommandObject
|
from .command import Command, CommandObject
|
||||||
from .content_types import ContentTypesFilter
|
from .content_types import ContentTypesFilter
|
||||||
from .exception import ExceptionMessageFilter, ExceptionTypeFilter
|
from .exception import ExceptionMessageFilter, ExceptionTypeFilter
|
||||||
|
from .logic import and_f, invert_f, or_f
|
||||||
from .magic_data import MagicData
|
from .magic_data import MagicData
|
||||||
from .state import StateFilter
|
from .state import StateFilter
|
||||||
from .text import Text
|
from .text import Text
|
||||||
|
|
@ -19,6 +35,22 @@ __all__ = (
|
||||||
"ExceptionTypeFilter",
|
"ExceptionTypeFilter",
|
||||||
"StateFilter",
|
"StateFilter",
|
||||||
"MagicData",
|
"MagicData",
|
||||||
|
"ChatMemberUpdatedFilter",
|
||||||
|
"CREATOR",
|
||||||
|
"ADMINISTRATOR",
|
||||||
|
"MEMBER",
|
||||||
|
"RESTRICTED",
|
||||||
|
"LEFT",
|
||||||
|
"KICKED",
|
||||||
|
"IS_MEMBER",
|
||||||
|
"IS_ADMIN",
|
||||||
|
"PROMOTED_TRANSITION",
|
||||||
|
"IS_NOT_MEMBER",
|
||||||
|
"JOIN_TRANSITION",
|
||||||
|
"LEAVE_TRANSITION",
|
||||||
|
"and_f",
|
||||||
|
"or_f",
|
||||||
|
"invert_f",
|
||||||
)
|
)
|
||||||
|
|
||||||
_ALL_EVENTS_FILTERS: Tuple[Type[BaseFilter], ...] = (MagicData,)
|
_ALL_EVENTS_FILTERS: Tuple[Type[BaseFilter], ...] = (MagicData,)
|
||||||
|
|
@ -84,10 +116,12 @@ BUILTIN_FILTERS: Dict[str, Tuple[Type[BaseFilter], ...]] = {
|
||||||
"my_chat_member": (
|
"my_chat_member": (
|
||||||
*_ALL_EVENTS_FILTERS,
|
*_ALL_EVENTS_FILTERS,
|
||||||
*_TELEGRAM_EVENTS_FILTERS,
|
*_TELEGRAM_EVENTS_FILTERS,
|
||||||
|
ChatMemberUpdatedFilter,
|
||||||
),
|
),
|
||||||
"chat_member": (
|
"chat_member": (
|
||||||
*_ALL_EVENTS_FILTERS,
|
*_ALL_EVENTS_FILTERS,
|
||||||
*_TELEGRAM_EVENTS_FILTERS,
|
*_TELEGRAM_EVENTS_FILTERS,
|
||||||
|
ChatMemberUpdatedFilter,
|
||||||
),
|
),
|
||||||
"chat_join_request": (
|
"chat_join_request": (
|
||||||
*_ALL_EVENTS_FILTERS,
|
*_ALL_EVENTS_FILTERS,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Union
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from aiogram.dispatcher.filters.logic import _LogicFilter
|
||||||
|
|
||||||
class BaseFilter(ABC, BaseModel):
|
|
||||||
|
class BaseFilter(BaseModel, ABC, _LogicFilter):
|
||||||
"""
|
"""
|
||||||
If you want to register own filters like builtin filters you will need to write subclass
|
If you want to register own filters like builtin filters you will need to write subclass
|
||||||
of this class with overriding the :code:`__call__`
|
of this class with overriding the :code:`__call__`
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Type, TypeVar, Union
|
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Literal, Optional, Type, TypeVar, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from magic_filter import MagicFilter
|
from magic_filter import MagicFilter
|
||||||
|
|
@ -22,9 +22,20 @@ class CallbackDataException(Exception):
|
||||||
|
|
||||||
|
|
||||||
class CallbackData(BaseModel):
|
class CallbackData(BaseModel):
|
||||||
|
"""
|
||||||
|
Base class for callback data wrapper
|
||||||
|
|
||||||
|
This class should be used as super-class of user-defined callbacks.
|
||||||
|
|
||||||
|
The class-keyword :code:`prefix` is required to define prefix
|
||||||
|
and also the argument :code:`sep` can be passed to define separator (default is :code:`:`).
|
||||||
|
"""
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
sep: str
|
__separator__: ClassVar[str]
|
||||||
prefix: str
|
"""Data separator (default is :code:`:`)"""
|
||||||
|
__prefix__: ClassVar[str]
|
||||||
|
"""Callback prefix"""
|
||||||
|
|
||||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||||
if "prefix" not in kwargs:
|
if "prefix" not in kwargs:
|
||||||
|
|
@ -32,12 +43,14 @@ class CallbackData(BaseModel):
|
||||||
f"prefix required, usage example: "
|
f"prefix required, usage example: "
|
||||||
f"`class {cls.__name__}(CallbackData, prefix='my_callback'): ...`"
|
f"`class {cls.__name__}(CallbackData, prefix='my_callback'): ...`"
|
||||||
)
|
)
|
||||||
cls.sep = kwargs.pop("sep", ":")
|
cls.__separator__ = kwargs.pop("sep", ":")
|
||||||
cls.prefix = kwargs.pop("prefix")
|
cls.__prefix__ = kwargs.pop("prefix")
|
||||||
if cls.sep in cls.prefix:
|
if cls.__separator__ in cls.__prefix__:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Separator symbol {cls.sep!r} can not be used inside prefix {cls.prefix!r}"
|
f"Separator symbol {cls.__separator__!r} can not be used "
|
||||||
|
f"inside prefix {cls.__prefix__!r}"
|
||||||
)
|
)
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
|
||||||
def _encode_value(self, key: str, value: Any) -> str:
|
def _encode_value(self, key: str, value: Any) -> str:
|
||||||
if value is None:
|
if value is None:
|
||||||
|
|
@ -52,31 +65,45 @@ class CallbackData(BaseModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
def pack(self) -> str:
|
def pack(self) -> str:
|
||||||
result = [self.prefix]
|
"""
|
||||||
|
Generate callback data string
|
||||||
|
|
||||||
|
:return: valid callback data for Telegram Bot API
|
||||||
|
"""
|
||||||
|
result = [self.__prefix__]
|
||||||
for key, value in self.dict().items():
|
for key, value in self.dict().items():
|
||||||
encoded = self._encode_value(key, value)
|
encoded = self._encode_value(key, value)
|
||||||
if self.sep in encoded:
|
if self.__separator__ in encoded:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Separator symbol {self.sep!r} can not be used in value {key}={encoded!r}"
|
f"Separator symbol {self.__separator__!r} can not be used "
|
||||||
|
f"in value {key}={encoded!r}"
|
||||||
)
|
)
|
||||||
result.append(encoded)
|
result.append(encoded)
|
||||||
callback_data = self.sep.join(result)
|
callback_data = self.__separator__.join(result)
|
||||||
if len(callback_data.encode()) > MAX_CALLBACK_LENGTH:
|
if len(callback_data.encode()) > MAX_CALLBACK_LENGTH:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Resulted callback data is too long! len({callback_data!r}.encode()) > {MAX_CALLBACK_LENGTH}"
|
f"Resulted callback data is too long! "
|
||||||
|
f"len({callback_data!r}.encode()) > {MAX_CALLBACK_LENGTH}"
|
||||||
)
|
)
|
||||||
return callback_data
|
return callback_data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def unpack(cls: Type[T], value: str) -> T:
|
def unpack(cls: Type[T], value: str) -> T:
|
||||||
prefix, *parts = value.split(cls.sep)
|
"""
|
||||||
|
Parse callback data string
|
||||||
|
|
||||||
|
:param value: value from Telegram
|
||||||
|
:return: instance of CallbackData
|
||||||
|
"""
|
||||||
|
prefix, *parts = value.split(cls.__separator__)
|
||||||
names = cls.__fields__.keys()
|
names = cls.__fields__.keys()
|
||||||
if len(parts) != len(names):
|
if len(parts) != len(names):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Callback data {cls.__name__!r} takes {len(names)} arguments but {len(parts)} were given"
|
f"Callback data {cls.__name__!r} takes {len(names)} arguments "
|
||||||
|
f"but {len(parts)} were given"
|
||||||
)
|
)
|
||||||
if prefix != cls.prefix:
|
if prefix != cls.__prefix__:
|
||||||
raise ValueError(f"Bad prefix ({prefix!r} != {cls.prefix!r})")
|
raise ValueError(f"Bad prefix ({prefix!r} != {cls.__prefix__!r})")
|
||||||
payload = {}
|
payload = {}
|
||||||
for k, v in zip(names, parts): # type: str, Optional[str]
|
for k, v in zip(names, parts): # type: str, Optional[str]
|
||||||
if field := cls.__fields__.get(k):
|
if field := cls.__fields__.get(k):
|
||||||
|
|
@ -87,15 +114,30 @@ class CallbackData(BaseModel):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter(cls, rule: Optional[MagicFilter] = None) -> CallbackQueryFilter:
|
def filter(cls, rule: Optional[MagicFilter] = None) -> CallbackQueryFilter:
|
||||||
|
"""
|
||||||
|
Generates a filter for callback query with rule
|
||||||
|
|
||||||
|
:param rule: magic rule
|
||||||
|
:return: instance of filter
|
||||||
|
"""
|
||||||
return CallbackQueryFilter(callback_data=cls, rule=rule)
|
return CallbackQueryFilter(callback_data=cls, rule=rule)
|
||||||
|
|
||||||
class Config:
|
# class Config:
|
||||||
use_enum_values = True
|
# use_enum_values = True
|
||||||
|
|
||||||
|
|
||||||
class CallbackQueryFilter(BaseFilter):
|
class CallbackQueryFilter(BaseFilter):
|
||||||
|
"""
|
||||||
|
This filter helps to handle callback query.
|
||||||
|
|
||||||
|
Should not be used directly, you should create the instance of this filter
|
||||||
|
via callback data instance
|
||||||
|
"""
|
||||||
|
|
||||||
callback_data: Type[CallbackData]
|
callback_data: Type[CallbackData]
|
||||||
|
"""Expected type of callback data"""
|
||||||
rule: Optional[MagicFilter] = None
|
rule: Optional[MagicFilter] = None
|
||||||
|
"""Magic rule"""
|
||||||
|
|
||||||
async def __call__(self, query: CallbackQuery) -> Union[Literal[False], Dict[str, Any]]:
|
async def __call__(self, query: CallbackQuery) -> Union[Literal[False], Dict[str, Any]]:
|
||||||
if not isinstance(query, CallbackQuery) or not query.data:
|
if not isinstance(query, CallbackQuery) or not query.data:
|
||||||
|
|
@ -111,3 +153,4 @@ class CallbackQueryFilter(BaseFilter):
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
use_enum_values = True
|
||||||
|
|
|
||||||
179
aiogram/dispatcher/filters/chat_member_updated.py
Normal file
179
aiogram/dispatcher/filters/chat_member_updated.py
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
from typing import Any, Dict, Optional, TypeVar, Union
|
||||||
|
|
||||||
|
from aiogram.dispatcher.filters import BaseFilter
|
||||||
|
from aiogram.types import ChatMember, ChatMemberUpdated
|
||||||
|
|
||||||
|
MarkerT = TypeVar("MarkerT", bound="_MemberStatusMarker")
|
||||||
|
MarkerGroupT = TypeVar("MarkerGroupT", bound="_MemberStatusGroupMarker")
|
||||||
|
TransitionT = TypeVar("TransitionT", bound="_MemberStatusTransition")
|
||||||
|
|
||||||
|
|
||||||
|
class _MemberStatusMarker:
|
||||||
|
def __init__(self, name: str, *, is_member: Optional[bool] = None) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.is_member = is_member
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
result = self.name.upper()
|
||||||
|
if self.is_member is not None:
|
||||||
|
result = ("+" if self.is_member else "-") + result
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __pos__(self: MarkerT) -> MarkerT:
|
||||||
|
return type(self)(name=self.name, is_member=True)
|
||||||
|
|
||||||
|
def __neg__(self: MarkerT) -> MarkerT:
|
||||||
|
return type(self)(name=self.name, is_member=False)
|
||||||
|
|
||||||
|
def __or__(
|
||||||
|
self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||||
|
) -> "_MemberStatusGroupMarker":
|
||||||
|
if isinstance(other, _MemberStatusMarker):
|
||||||
|
return _MemberStatusGroupMarker(self, other)
|
||||||
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
|
return other | self
|
||||||
|
raise TypeError(
|
||||||
|
f"unsupported operand type(s) for |: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
__ror__ = __or__
|
||||||
|
|
||||||
|
def __rshift__(
|
||||||
|
self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||||
|
) -> "_MemberStatusTransition":
|
||||||
|
old = _MemberStatusGroupMarker(self)
|
||||||
|
if isinstance(other, _MemberStatusMarker):
|
||||||
|
return _MemberStatusTransition(old=old, new=_MemberStatusGroupMarker(other))
|
||||||
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
|
return _MemberStatusTransition(old=old, new=other)
|
||||||
|
raise TypeError(
|
||||||
|
f"unsupported operand type(s) for >>: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __lshift__(
|
||||||
|
self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||||
|
) -> "_MemberStatusTransition":
|
||||||
|
new = _MemberStatusGroupMarker(self)
|
||||||
|
if isinstance(other, _MemberStatusMarker):
|
||||||
|
return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=new)
|
||||||
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
|
return _MemberStatusTransition(old=other, new=new)
|
||||||
|
raise TypeError(
|
||||||
|
f"unsupported operand type(s) for <<: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((self.name, self.is_member))
|
||||||
|
|
||||||
|
def check(self, *, member: ChatMember) -> bool:
|
||||||
|
if self.is_member is not None and member.is_member != self.is_member:
|
||||||
|
return False
|
||||||
|
return self.name == member.status
|
||||||
|
|
||||||
|
|
||||||
|
class _MemberStatusGroupMarker:
|
||||||
|
def __init__(self, *statuses: _MemberStatusMarker) -> None:
|
||||||
|
if not statuses:
|
||||||
|
raise ValueError("Member status group should have at least one status included")
|
||||||
|
self.statuses = frozenset(statuses)
|
||||||
|
|
||||||
|
def __or__(
|
||||||
|
self: MarkerGroupT, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||||
|
) -> MarkerGroupT:
|
||||||
|
if isinstance(other, _MemberStatusMarker):
|
||||||
|
return type(self)(*self.statuses, other)
|
||||||
|
elif isinstance(other, _MemberStatusGroupMarker):
|
||||||
|
return type(self)(*self.statuses, *other.statuses)
|
||||||
|
raise TypeError(
|
||||||
|
f"unsupported operand type(s) for |: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __rshift__(
|
||||||
|
self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||||
|
) -> "_MemberStatusTransition":
|
||||||
|
if isinstance(other, _MemberStatusMarker):
|
||||||
|
return _MemberStatusTransition(old=self, new=_MemberStatusGroupMarker(other))
|
||||||
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
|
return _MemberStatusTransition(old=self, new=other)
|
||||||
|
raise TypeError(
|
||||||
|
f"unsupported operand type(s) for >>: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __lshift__(
|
||||||
|
self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||||
|
) -> "_MemberStatusTransition":
|
||||||
|
if isinstance(other, _MemberStatusMarker):
|
||||||
|
return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=self)
|
||||||
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
|
return _MemberStatusTransition(old=other, new=self)
|
||||||
|
raise TypeError(
|
||||||
|
f"unsupported operand type(s) for <<: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
result = " | ".join(map(str, sorted(self.statuses, key=str)))
|
||||||
|
if len(self.statuses) != 1:
|
||||||
|
return f"({result})"
|
||||||
|
return result
|
||||||
|
|
||||||
|
def check(self, *, member: ChatMember) -> bool:
|
||||||
|
for status in self.statuses:
|
||||||
|
if status.check(member=member):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class _MemberStatusTransition:
|
||||||
|
def __init__(self, *, old: _MemberStatusGroupMarker, new: _MemberStatusGroupMarker) -> None:
|
||||||
|
self.old = old
|
||||||
|
self.new = new
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.old} >> {self.new}"
|
||||||
|
|
||||||
|
def __invert__(self: TransitionT) -> TransitionT:
|
||||||
|
return type(self)(old=self.new, new=self.old)
|
||||||
|
|
||||||
|
def check(self, *, old: ChatMember, new: ChatMember) -> bool:
|
||||||
|
return self.old.check(member=old) and self.new.check(member=new)
|
||||||
|
|
||||||
|
|
||||||
|
CREATOR = _MemberStatusMarker("creator")
|
||||||
|
ADMINISTRATOR = _MemberStatusMarker("administrator")
|
||||||
|
MEMBER = _MemberStatusMarker("member")
|
||||||
|
RESTRICTED = _MemberStatusMarker("restricted")
|
||||||
|
LEFT = _MemberStatusMarker("left")
|
||||||
|
KICKED = _MemberStatusMarker("kicked")
|
||||||
|
|
||||||
|
IS_MEMBER = CREATOR | ADMINISTRATOR | MEMBER | +RESTRICTED
|
||||||
|
IS_ADMIN = CREATOR | ADMINISTRATOR
|
||||||
|
IS_NOT_MEMBER = LEFT | KICKED | -RESTRICTED
|
||||||
|
|
||||||
|
JOIN_TRANSITION = IS_NOT_MEMBER >> IS_MEMBER
|
||||||
|
LEAVE_TRANSITION = ~JOIN_TRANSITION
|
||||||
|
PROMOTED_TRANSITION = (MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR
|
||||||
|
|
||||||
|
|
||||||
|
class ChatMemberUpdatedFilter(BaseFilter):
|
||||||
|
member_status_changed: Union[
|
||||||
|
_MemberStatusMarker,
|
||||||
|
_MemberStatusGroupMarker,
|
||||||
|
_MemberStatusTransition,
|
||||||
|
]
|
||||||
|
"""Accepts the status transition or new status of the member (see usage in docs)"""
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
|
async def __call__(self, member_updated: ChatMemberUpdated) -> Union[bool, Dict[str, Any]]:
|
||||||
|
old = member_updated.old_chat_member
|
||||||
|
new = member_updated.new_chat_member
|
||||||
|
rule = self.member_status_changed
|
||||||
|
|
||||||
|
if isinstance(rule, (_MemberStatusMarker, _MemberStatusGroupMarker)):
|
||||||
|
return rule.check(member=new)
|
||||||
|
if isinstance(rule, _MemberStatusTransition):
|
||||||
|
return rule.check(old=old, new=new)
|
||||||
|
|
||||||
|
# Impossible variant in due to pydantic validation
|
||||||
|
return False # pragma: no cover
|
||||||
|
|
@ -12,7 +12,7 @@ from aiogram.dispatcher.filters import BaseFilter
|
||||||
from aiogram.types import Message
|
from aiogram.types import Message
|
||||||
from aiogram.utils.deep_linking import decode_payload
|
from aiogram.utils.deep_linking import decode_payload
|
||||||
|
|
||||||
CommandPatterType = Union[str, re.Pattern]
|
CommandPatternType = Union[str, re.Pattern]
|
||||||
|
|
||||||
|
|
||||||
class CommandException(Exception):
|
class CommandException(Exception):
|
||||||
|
|
@ -26,7 +26,7 @@ class Command(BaseFilter):
|
||||||
Works only with :class:`aiogram.types.message.Message` events which have the :code:`text`.
|
Works only with :class:`aiogram.types.message.Message` events which have the :code:`text`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
commands: Union[Sequence[CommandPatterType], CommandPatterType]
|
commands: Union[Sequence[CommandPatternType], CommandPatternType]
|
||||||
"""List of commands (string or compiled regexp patterns)"""
|
"""List of commands (string or compiled regexp patterns)"""
|
||||||
commands_prefix: str = "/"
|
commands_prefix: str = "/"
|
||||||
"""Prefix for command. Prefix is always is single char but here you can pass all of allowed prefixes,
|
"""Prefix for command. Prefix is always is single char but here you can pass all of allowed prefixes,
|
||||||
|
|
@ -44,8 +44,8 @@ class Command(BaseFilter):
|
||||||
|
|
||||||
@validator("commands", always=True)
|
@validator("commands", always=True)
|
||||||
def _validate_commands(
|
def _validate_commands(
|
||||||
cls, value: Union[Sequence[CommandPatterType], CommandPatterType]
|
cls, value: Union[Sequence[CommandPatternType], CommandPatternType]
|
||||||
) -> Sequence[CommandPatterType]:
|
) -> Sequence[CommandPatternType]:
|
||||||
if isinstance(value, (str, re.Pattern)):
|
if isinstance(value, (str, re.Pattern)):
|
||||||
value = [value]
|
value = [value]
|
||||||
return value
|
return value
|
||||||
|
|
@ -59,7 +59,10 @@ class Command(BaseFilter):
|
||||||
command = await self.parse_command(text=text, bot=bot)
|
command = await self.parse_command(text=text, bot=bot)
|
||||||
except CommandException:
|
except CommandException:
|
||||||
return False
|
return False
|
||||||
return {"command": command}
|
result = {"command": command}
|
||||||
|
if command.magic_result and isinstance(command.magic_result, dict):
|
||||||
|
result.update(command.magic_result)
|
||||||
|
return result
|
||||||
|
|
||||||
def extract_command(self, text: str) -> CommandObject:
|
def extract_command(self, text: str) -> CommandObject:
|
||||||
# First step: separate command with arguments
|
# First step: separate command with arguments
|
||||||
|
|
@ -87,7 +90,7 @@ class Command(BaseFilter):
|
||||||
raise CommandException("Mention did not match")
|
raise CommandException("Mention did not match")
|
||||||
|
|
||||||
def validate_command(self, command: CommandObject) -> CommandObject:
|
def validate_command(self, command: CommandObject) -> CommandObject:
|
||||||
for allowed_command in cast(Sequence[CommandPatterType], self.commands):
|
for allowed_command in cast(Sequence[CommandPatternType], self.commands):
|
||||||
# Command can be presented as regexp pattern or raw string
|
# Command can be presented as regexp pattern or raw string
|
||||||
# then need to validate that in different ways
|
# then need to validate that in different ways
|
||||||
if isinstance(allowed_command, Pattern): # Regexp
|
if isinstance(allowed_command, Pattern): # Regexp
|
||||||
|
|
@ -110,20 +113,22 @@ class Command(BaseFilter):
|
||||||
self.validate_prefix(command=command)
|
self.validate_prefix(command=command)
|
||||||
await self.validate_mention(bot=bot, command=command)
|
await self.validate_mention(bot=bot, command=command)
|
||||||
command = self.validate_command(command)
|
command = self.validate_command(command)
|
||||||
self.do_magic(command=command)
|
command = self.do_magic(command=command)
|
||||||
return command
|
return command
|
||||||
|
|
||||||
def do_magic(self, command: CommandObject) -> None:
|
def do_magic(self, command: CommandObject) -> Any:
|
||||||
if not self.command_magic:
|
if not self.command_magic:
|
||||||
return
|
return command
|
||||||
if not self.command_magic.resolve(command):
|
result = self.command_magic.resolve(command)
|
||||||
|
if not result:
|
||||||
raise CommandException("Rejected via magic filter")
|
raise CommandException("Rejected via magic filter")
|
||||||
|
return replace(command, magic_result=result)
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(frozen=True)
|
||||||
class CommandObject:
|
class CommandObject:
|
||||||
"""
|
"""
|
||||||
Instance of this object is always has command and it prefix.
|
Instance of this object is always has command and it prefix.
|
||||||
|
|
@ -140,6 +145,7 @@ class CommandObject:
|
||||||
"""Command argument"""
|
"""Command argument"""
|
||||||
regexp_match: Optional[Match[str]] = field(repr=False, default=None)
|
regexp_match: Optional[Match[str]] = field(repr=False, default=None)
|
||||||
"""Will be presented match result if the command is presented as regexp in filter"""
|
"""Will be presented match result if the command is presented as regexp in filter"""
|
||||||
|
magic_result: Optional[Any] = field(repr=False, default=None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mentioned(self) -> bool:
|
def mentioned(self) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from typing import Any, Dict, Pattern, Tuple, Type, Union, cast
|
||||||
from pydantic import validator
|
from pydantic import validator
|
||||||
|
|
||||||
from aiogram.dispatcher.filters import BaseFilter
|
from aiogram.dispatcher.filters import BaseFilter
|
||||||
|
from aiogram.types import TelegramObject
|
||||||
|
|
||||||
|
|
||||||
class ExceptionTypeFilter(BaseFilter):
|
class ExceptionTypeFilter(BaseFilter):
|
||||||
|
|
@ -17,7 +18,9 @@ class ExceptionTypeFilter(BaseFilter):
|
||||||
class Config:
|
class Config:
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
async def __call__(self, exception: Exception) -> Union[bool, Dict[str, Any]]:
|
async def __call__(
|
||||||
|
self, obj: TelegramObject, exception: Exception
|
||||||
|
) -> Union[bool, Dict[str, Any]]:
|
||||||
return isinstance(exception, self.exception)
|
return isinstance(exception, self.exception)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -38,7 +41,9 @@ class ExceptionMessageFilter(BaseFilter):
|
||||||
return re.compile(value)
|
return re.compile(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
async def __call__(self, exception: Exception) -> Union[bool, Dict[str, Any]]:
|
async def __call__(
|
||||||
|
self, obj: TelegramObject, exception: Exception
|
||||||
|
) -> Union[bool, Dict[str, Any]]:
|
||||||
pattern = cast(Pattern[str], self.pattern)
|
pattern = cast(Pattern[str], self.pattern)
|
||||||
result = pattern.match(str(exception))
|
result = pattern.match(str(exception))
|
||||||
if not result:
|
if not result:
|
||||||
|
|
|
||||||
87
aiogram/dispatcher/filters/logic.py
Normal file
87
aiogram/dispatcher/filters/logic.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Union
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from aiogram.dispatcher.event.handler import CallbackType, FilterObject
|
||||||
|
|
||||||
|
|
||||||
|
class _LogicFilter:
|
||||||
|
__call__: Callable[..., Awaitable[Union[bool, Dict[str, Any]]]]
|
||||||
|
|
||||||
|
def __and__(self, other: "CallbackType") -> "_AndFilter":
|
||||||
|
return and_f(self, other)
|
||||||
|
|
||||||
|
def __or__(self, other: "CallbackType") -> "_OrFilter":
|
||||||
|
return or_f(self, other)
|
||||||
|
|
||||||
|
def __invert__(self) -> "_InvertFilter":
|
||||||
|
return invert_f(self)
|
||||||
|
|
||||||
|
def __await__(self): # type: ignore # pragma: no cover
|
||||||
|
# Is needed only for inspection and this method is never be called
|
||||||
|
return self.__call__
|
||||||
|
|
||||||
|
|
||||||
|
class _InvertFilter(_LogicFilter):
|
||||||
|
__slots__ = ("target",)
|
||||||
|
|
||||||
|
def __init__(self, target: "FilterObject") -> None:
|
||||||
|
self.target = target
|
||||||
|
|
||||||
|
async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]:
|
||||||
|
return not bool(await self.target.call(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
class _AndFilter(_LogicFilter):
|
||||||
|
__slots__ = ("targets",)
|
||||||
|
|
||||||
|
def __init__(self, *targets: "FilterObject") -> None:
|
||||||
|
self.targets = targets
|
||||||
|
|
||||||
|
async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]:
|
||||||
|
final_result = {}
|
||||||
|
|
||||||
|
for target in self.targets:
|
||||||
|
result = await target.call(*args, **kwargs)
|
||||||
|
if not result:
|
||||||
|
return False
|
||||||
|
if isinstance(result, dict):
|
||||||
|
final_result.update(result)
|
||||||
|
|
||||||
|
if final_result:
|
||||||
|
return final_result
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class _OrFilter(_LogicFilter):
|
||||||
|
__slots__ = ("targets",)
|
||||||
|
|
||||||
|
def __init__(self, *targets: "FilterObject") -> None:
|
||||||
|
self.targets = targets
|
||||||
|
|
||||||
|
async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]:
|
||||||
|
for target in self.targets:
|
||||||
|
result = await target.call(*args, **kwargs)
|
||||||
|
if not result:
|
||||||
|
continue
|
||||||
|
if isinstance(result, dict):
|
||||||
|
return result
|
||||||
|
return bool(result)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def and_f(target1: "CallbackType", target2: "CallbackType") -> _AndFilter:
|
||||||
|
from aiogram.dispatcher.event.handler import FilterObject
|
||||||
|
|
||||||
|
return _AndFilter(FilterObject(target1), FilterObject(target2))
|
||||||
|
|
||||||
|
|
||||||
|
def or_f(target1: "CallbackType", target2: "CallbackType") -> _OrFilter:
|
||||||
|
from aiogram.dispatcher.event.handler import FilterObject
|
||||||
|
|
||||||
|
return _OrFilter(FilterObject(target1), FilterObject(target2))
|
||||||
|
|
||||||
|
|
||||||
|
def invert_f(target: "CallbackType") -> _InvertFilter:
|
||||||
|
from aiogram.dispatcher.event.handler import FilterObject
|
||||||
|
|
||||||
|
return _InvertFilter(FilterObject(target))
|
||||||
|
|
@ -12,9 +12,7 @@ class MagicData(BaseFilter):
|
||||||
class Config:
|
class Config:
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
async def __call__(self, event: TelegramObject, *args: Any, **kwargs: Any) -> bool:
|
async def __call__(self, event: TelegramObject, *args: Any, **kwargs: Any) -> Any:
|
||||||
return bool(
|
return self.magic_data.resolve(
|
||||||
self.magic_data.resolve(
|
AttrDict({"event": event, **{k: v for k, v in enumerate(args)}, **kwargs})
|
||||||
AttrDict({"event": event, **{k: v for k, v in enumerate(args)}, **kwargs})
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from aiogram.dispatcher.filters import BaseFilter
|
||||||
from aiogram.types import CallbackQuery, InlineQuery, Message, Poll
|
from aiogram.types import CallbackQuery, InlineQuery, Message, Poll
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from aiogram.utils.i18n.lazy_proxy import LazyProxy
|
from aiogram.utils.i18n.lazy_proxy import LazyProxy # NOQA
|
||||||
|
|
||||||
TextType = Union[str, "LazyProxy"]
|
TextType = Union[str, "LazyProxy"]
|
||||||
|
|
||||||
|
|
|
||||||
0
aiogram/dispatcher/flags/__init__.py
Normal file
0
aiogram/dispatcher/flags/__init__.py
Normal file
79
aiogram/dispatcher/flags/flag.py
Normal file
79
aiogram/dispatcher/flags/flag.py
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast, overload
|
||||||
|
|
||||||
|
from magic_filter import AttrDict
|
||||||
|
|
||||||
|
from aiogram.dispatcher.flags.getter import extract_flags_from_object
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Flag:
|
||||||
|
name: str
|
||||||
|
value: Any
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class FlagDecorator:
|
||||||
|
flag: Flag
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _with_flag(cls, flag: Flag) -> "FlagDecorator":
|
||||||
|
return cls(flag)
|
||||||
|
|
||||||
|
def _with_value(self, value: Any) -> "FlagDecorator":
|
||||||
|
new_flag = Flag(self.flag.name, value)
|
||||||
|
return self._with_flag(new_flag)
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __call__(self, value: Callable[..., Any], /) -> Callable[..., Any]: # type: ignore
|
||||||
|
pass
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __call__(self, value: Any, /) -> "FlagDecorator":
|
||||||
|
pass
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __call__(self, **kwargs: Any) -> "FlagDecorator":
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
value: Optional[Any] = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> Union[Callable[..., Any], "FlagDecorator"]:
|
||||||
|
if value and kwargs:
|
||||||
|
raise ValueError("The arguments `value` and **kwargs can not be used together")
|
||||||
|
|
||||||
|
if value is not None and callable(value):
|
||||||
|
value.aiogram_flag = {
|
||||||
|
**extract_flags_from_object(value),
|
||||||
|
self.flag.name: self.flag.value,
|
||||||
|
}
|
||||||
|
return cast(Callable[..., Any], value)
|
||||||
|
return self._with_value(AttrDict(kwargs) if value is None else value)
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
|
class _ChatActionFlagProtocol(FlagDecorator):
|
||||||
|
def __call__( # type: ignore[override]
|
||||||
|
self,
|
||||||
|
action: str = ...,
|
||||||
|
interval: float = ...,
|
||||||
|
initial_sleep: float = ...,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> FlagDecorator:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FlagGenerator:
|
||||||
|
def __getattr__(self, name: str) -> FlagDecorator:
|
||||||
|
if name[0] == "_":
|
||||||
|
raise AttributeError("Flag name must NOT start with underscore")
|
||||||
|
return FlagDecorator(Flag(name, True))
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
chat_action: _ChatActionFlagProtocol
|
||||||
56
aiogram/dispatcher/flags/getter.py
Normal file
56
aiogram/dispatcher/flags/getter.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, Optional, Union, cast
|
||||||
|
|
||||||
|
from magic_filter import AttrDict, MagicFilter
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from aiogram.dispatcher.event.handler import HandlerObject
|
||||||
|
|
||||||
|
|
||||||
|
def extract_flags_from_object(obj: Any) -> Dict[str, Any]:
|
||||||
|
if not hasattr(obj, "aiogram_flag"):
|
||||||
|
return {}
|
||||||
|
return cast(Dict[str, Any], obj.aiogram_flag)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_flags(handler: Union["HandlerObject", Dict[str, Any]]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Extract flags from handler or middleware context data
|
||||||
|
|
||||||
|
:param handler: handler object or data
|
||||||
|
:return: dictionary with all handler flags
|
||||||
|
"""
|
||||||
|
if isinstance(handler, dict) and "handler" in handler:
|
||||||
|
handler = handler["handler"]
|
||||||
|
if not hasattr(handler, "flags"):
|
||||||
|
return {}
|
||||||
|
return handler.flags # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def get_flag(
|
||||||
|
handler: Union["HandlerObject", Dict[str, Any]],
|
||||||
|
name: str,
|
||||||
|
*,
|
||||||
|
default: Optional[Any] = None,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Get flag by name
|
||||||
|
|
||||||
|
:param handler: handler object or data
|
||||||
|
:param name: name of the flag
|
||||||
|
:param default: default value (None)
|
||||||
|
:return: value of the flag or default
|
||||||
|
"""
|
||||||
|
flags = extract_flags(handler)
|
||||||
|
return flags.get(name, default)
|
||||||
|
|
||||||
|
|
||||||
|
def check_flags(handler: Union["HandlerObject", Dict[str, Any]], magic: MagicFilter) -> Any:
|
||||||
|
"""
|
||||||
|
Check flags via magic filter
|
||||||
|
|
||||||
|
:param handler: handler object or data
|
||||||
|
:param magic: instance of the magic
|
||||||
|
:return: the result of magic filter check
|
||||||
|
"""
|
||||||
|
flags = extract_flags(handler)
|
||||||
|
return magic.resolve(AttrDict(flags))
|
||||||
|
|
@ -2,7 +2,12 @@ from typing import Any, Awaitable, Callable, Dict, Optional, cast
|
||||||
|
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
from aiogram.dispatcher.fsm.context import FSMContext
|
from aiogram.dispatcher.fsm.context import FSMContext
|
||||||
from aiogram.dispatcher.fsm.storage.base import DEFAULT_DESTINY, BaseStorage, StorageKey
|
from aiogram.dispatcher.fsm.storage.base import (
|
||||||
|
DEFAULT_DESTINY,
|
||||||
|
BaseEventIsolation,
|
||||||
|
BaseStorage,
|
||||||
|
StorageKey,
|
||||||
|
)
|
||||||
from aiogram.dispatcher.fsm.strategy import FSMStrategy, apply_strategy
|
from aiogram.dispatcher.fsm.strategy import FSMStrategy, apply_strategy
|
||||||
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
||||||
from aiogram.types import TelegramObject
|
from aiogram.types import TelegramObject
|
||||||
|
|
@ -12,12 +17,12 @@ class FSMContextMiddleware(BaseMiddleware):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
storage: BaseStorage,
|
storage: BaseStorage,
|
||||||
|
events_isolation: BaseEventIsolation,
|
||||||
strategy: FSMStrategy = FSMStrategy.USER_IN_CHAT,
|
strategy: FSMStrategy = FSMStrategy.USER_IN_CHAT,
|
||||||
isolate_events: bool = True,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.storage = storage
|
self.storage = storage
|
||||||
self.strategy = strategy
|
self.strategy = strategy
|
||||||
self.isolate_events = isolate_events
|
self.events_isolation = events_isolation
|
||||||
|
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -30,9 +35,8 @@ class FSMContextMiddleware(BaseMiddleware):
|
||||||
data["fsm_storage"] = self.storage
|
data["fsm_storage"] = self.storage
|
||||||
if context:
|
if context:
|
||||||
data.update({"state": context, "raw_state": await context.get_state()})
|
data.update({"state": context, "raw_state": await context.get_state()})
|
||||||
if self.isolate_events:
|
async with self.events_isolation.lock(bot=bot, key=context.key):
|
||||||
async with self.storage.lock(bot=bot, key=context.key):
|
return await handler(event, data)
|
||||||
return await handler(event, data)
|
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
|
|
||||||
def resolve_event_context(
|
def resolve_event_context(
|
||||||
|
|
@ -81,3 +85,7 @@ class FSMContextMiddleware(BaseMiddleware):
|
||||||
destiny=destiny,
|
destiny=destiny,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def close(self) -> None:
|
||||||
|
await self.storage.close()
|
||||||
|
await self.events_isolation.close()
|
||||||
|
|
|
||||||
|
|
@ -24,19 +24,6 @@ class BaseStorage(ABC):
|
||||||
Base class for all FSM storages
|
Base class for all FSM storages
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]:
|
|
||||||
"""
|
|
||||||
Isolate events with lock.
|
|
||||||
Will be used as context manager
|
|
||||||
|
|
||||||
:param bot: instance of the current bot
|
|
||||||
:param key: storage key
|
|
||||||
:return: An async generator
|
|
||||||
"""
|
|
||||||
yield None
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def set_state(self, bot: Bot, key: StorageKey, state: StateType = None) -> None:
|
async def set_state(self, bot: Bot, key: StorageKey, state: StateType = None) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -101,3 +88,22 @@ class BaseStorage(ABC):
|
||||||
Close storage (database connection, file or etc.)
|
Close storage (database connection, file or etc.)
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BaseEventIsolation(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]:
|
||||||
|
"""
|
||||||
|
Isolate events with lock.
|
||||||
|
Will be used as context manager
|
||||||
|
|
||||||
|
:param bot: instance of the current bot
|
||||||
|
:param key: storage key
|
||||||
|
:return: An async generator
|
||||||
|
"""
|
||||||
|
yield None
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def close(self) -> None:
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,22 @@ from asyncio import Lock
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, AsyncGenerator, DefaultDict, Dict, Optional
|
from typing import Any, AsyncGenerator, DefaultDict, Dict, Hashable, Optional
|
||||||
|
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
from aiogram.dispatcher.fsm.state import State
|
from aiogram.dispatcher.fsm.state import State
|
||||||
from aiogram.dispatcher.fsm.storage.base import BaseStorage, StateType, StorageKey
|
from aiogram.dispatcher.fsm.storage.base import (
|
||||||
|
BaseEventIsolation,
|
||||||
|
BaseStorage,
|
||||||
|
StateType,
|
||||||
|
StorageKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MemoryStorageRecord:
|
class MemoryStorageRecord:
|
||||||
data: Dict[str, Any] = field(default_factory=dict)
|
data: Dict[str, Any] = field(default_factory=dict)
|
||||||
state: Optional[str] = None
|
state: Optional[str] = None
|
||||||
lock: Lock = field(default_factory=Lock)
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryStorage(BaseStorage):
|
class MemoryStorage(BaseStorage):
|
||||||
|
|
@ -34,11 +38,6 @@ class MemoryStorage(BaseStorage):
|
||||||
async def close(self) -> None:
|
async def close(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]:
|
|
||||||
async with self.storage[key].lock:
|
|
||||||
yield None
|
|
||||||
|
|
||||||
async def set_state(self, bot: Bot, key: StorageKey, state: StateType = None) -> None:
|
async def set_state(self, bot: Bot, key: StorageKey, state: StateType = None) -> None:
|
||||||
self.storage[key].state = state.state if isinstance(state, State) else state
|
self.storage[key].state = state.state if isinstance(state, State) else state
|
||||||
|
|
||||||
|
|
@ -50,3 +49,27 @@ class MemoryStorage(BaseStorage):
|
||||||
|
|
||||||
async def get_data(self, bot: Bot, key: StorageKey) -> Dict[str, Any]:
|
async def get_data(self, bot: Bot, key: StorageKey) -> Dict[str, Any]:
|
||||||
return self.storage[key].data.copy()
|
return self.storage[key].data.copy()
|
||||||
|
|
||||||
|
|
||||||
|
class DisabledEventIsolation(BaseEventIsolation):
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]:
|
||||||
|
yield
|
||||||
|
|
||||||
|
async def close(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleEventIsolation(BaseEventIsolation):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
# TODO: Unused locks cleaner is needed
|
||||||
|
self._locks: DefaultDict[Hashable, Lock] = defaultdict(Lock)
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]:
|
||||||
|
lock = self._locks[key]
|
||||||
|
async with lock:
|
||||||
|
yield
|
||||||
|
|
||||||
|
async def close(self) -> None:
|
||||||
|
self._locks.clear()
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,20 @@ from abc import ABC, abstractmethod
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing import Any, AsyncGenerator, Dict, Literal, Optional, cast
|
from typing import Any, AsyncGenerator, Dict, Literal, Optional, cast
|
||||||
|
|
||||||
from aioredis import ConnectionPool, Redis
|
from redis.asyncio.client import Redis
|
||||||
|
from redis.asyncio.connection import ConnectionPool
|
||||||
|
from redis.asyncio.lock import Lock
|
||||||
|
from redis.typing import ExpiryT
|
||||||
|
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
from aiogram.dispatcher.fsm.state import State
|
from aiogram.dispatcher.fsm.state import State
|
||||||
from aiogram.dispatcher.fsm.storage.base import DEFAULT_DESTINY, BaseStorage, StateType, StorageKey
|
from aiogram.dispatcher.fsm.storage.base import (
|
||||||
|
DEFAULT_DESTINY,
|
||||||
|
BaseEventIsolation,
|
||||||
|
BaseStorage,
|
||||||
|
StateType,
|
||||||
|
StorageKey,
|
||||||
|
)
|
||||||
|
|
||||||
DEFAULT_REDIS_LOCK_KWARGS = {"timeout": 60}
|
DEFAULT_REDIS_LOCK_KWARGS = {"timeout": 60}
|
||||||
|
|
||||||
|
|
@ -82,8 +91,8 @@ class RedisStorage(BaseStorage):
|
||||||
self,
|
self,
|
||||||
redis: Redis,
|
redis: Redis,
|
||||||
key_builder: Optional[KeyBuilder] = None,
|
key_builder: Optional[KeyBuilder] = None,
|
||||||
state_ttl: Optional[int] = None,
|
state_ttl: Optional[ExpiryT] = None,
|
||||||
data_ttl: Optional[int] = None,
|
data_ttl: Optional[ExpiryT] = None,
|
||||||
lock_kwargs: Optional[Dict[str, Any]] = None,
|
lock_kwargs: Optional[Dict[str, Any]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -121,18 +130,11 @@ class RedisStorage(BaseStorage):
|
||||||
redis = Redis(connection_pool=pool)
|
redis = Redis(connection_pool=pool)
|
||||||
return cls(redis=redis, **kwargs)
|
return cls(redis=redis, **kwargs)
|
||||||
|
|
||||||
async def close(self) -> None:
|
def create_isolation(self, **kwargs: Any) -> "RedisEventIsolation":
|
||||||
await self.redis.close() # type: ignore
|
return RedisEventIsolation(redis=self.redis, key_builder=self.key_builder, **kwargs)
|
||||||
|
|
||||||
@asynccontextmanager
|
async def close(self) -> None:
|
||||||
async def lock(
|
await self.redis.close()
|
||||||
self,
|
|
||||||
bot: Bot,
|
|
||||||
key: StorageKey,
|
|
||||||
) -> AsyncGenerator[None, None]:
|
|
||||||
redis_key = self.key_builder.build(key, "lock")
|
|
||||||
async with self.redis.lock(name=redis_key, **self.lock_kwargs):
|
|
||||||
yield None
|
|
||||||
|
|
||||||
async def set_state(
|
async def set_state(
|
||||||
self,
|
self,
|
||||||
|
|
@ -146,8 +148,8 @@ class RedisStorage(BaseStorage):
|
||||||
else:
|
else:
|
||||||
await self.redis.set(
|
await self.redis.set(
|
||||||
redis_key,
|
redis_key,
|
||||||
state.state if isinstance(state, State) else state, # type: ignore[arg-type]
|
cast(str, state.state if isinstance(state, State) else state),
|
||||||
ex=self.state_ttl, # type: ignore[arg-type]
|
ex=self.state_ttl,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_state(
|
async def get_state(
|
||||||
|
|
@ -174,7 +176,7 @@ class RedisStorage(BaseStorage):
|
||||||
await self.redis.set(
|
await self.redis.set(
|
||||||
redis_key,
|
redis_key,
|
||||||
bot.session.json_dumps(data),
|
bot.session.json_dumps(data),
|
||||||
ex=self.data_ttl, # type: ignore[arg-type]
|
ex=self.data_ttl,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_data(
|
async def get_data(
|
||||||
|
|
@ -189,3 +191,43 @@ class RedisStorage(BaseStorage):
|
||||||
if isinstance(value, bytes):
|
if isinstance(value, bytes):
|
||||||
value = value.decode("utf-8")
|
value = value.decode("utf-8")
|
||||||
return cast(Dict[str, Any], bot.session.json_loads(value))
|
return cast(Dict[str, Any], bot.session.json_loads(value))
|
||||||
|
|
||||||
|
|
||||||
|
class RedisEventIsolation(BaseEventIsolation):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
redis: Redis,
|
||||||
|
key_builder: Optional[KeyBuilder] = None,
|
||||||
|
lock_kwargs: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> None:
|
||||||
|
if key_builder is None:
|
||||||
|
key_builder = DefaultKeyBuilder()
|
||||||
|
self.redis = redis
|
||||||
|
self.key_builder = key_builder
|
||||||
|
self.lock_kwargs = lock_kwargs or {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_url(
|
||||||
|
cls,
|
||||||
|
url: str,
|
||||||
|
connection_kwargs: Optional[Dict[str, Any]] = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> "RedisEventIsolation":
|
||||||
|
if connection_kwargs is None:
|
||||||
|
connection_kwargs = {}
|
||||||
|
pool = ConnectionPool.from_url(url, **connection_kwargs)
|
||||||
|
redis = Redis(connection_pool=pool)
|
||||||
|
return cls(redis=redis, **kwargs)
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lock(
|
||||||
|
self,
|
||||||
|
bot: Bot,
|
||||||
|
key: StorageKey,
|
||||||
|
) -> AsyncGenerator[None, None]:
|
||||||
|
redis_key = self.key_builder.build(key, "lock")
|
||||||
|
async with self.redis.lock(name=redis_key, **self.lock_kwargs, lock_class=Lock):
|
||||||
|
yield None
|
||||||
|
|
||||||
|
async def close(self) -> None:
|
||||||
|
pass
|
||||||
|
|
|
||||||
61
aiogram/dispatcher/middlewares/manager.py
Normal file
61
aiogram/dispatcher/middlewares/manager.py
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import functools
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Sequence, Union, overload
|
||||||
|
|
||||||
|
from aiogram.dispatcher.event.bases import MiddlewareEventType, MiddlewareType, NextMiddlewareType
|
||||||
|
from aiogram.dispatcher.event.handler import CallbackType
|
||||||
|
from aiogram.types import TelegramObject
|
||||||
|
|
||||||
|
|
||||||
|
class MiddlewareManager(Sequence[MiddlewareType[TelegramObject]]):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._middlewares: List[MiddlewareType[TelegramObject]] = []
|
||||||
|
|
||||||
|
def register(
|
||||||
|
self,
|
||||||
|
middleware: MiddlewareType[TelegramObject],
|
||||||
|
) -> MiddlewareType[TelegramObject]:
|
||||||
|
self._middlewares.append(middleware)
|
||||||
|
return middleware
|
||||||
|
|
||||||
|
def unregister(self, middleware: MiddlewareType[TelegramObject]) -> None:
|
||||||
|
self._middlewares.remove(middleware)
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
middleware: Optional[MiddlewareType[TelegramObject]] = None,
|
||||||
|
) -> Union[
|
||||||
|
Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]],
|
||||||
|
MiddlewareType[TelegramObject],
|
||||||
|
]:
|
||||||
|
if middleware is None:
|
||||||
|
return self.register
|
||||||
|
return self.register(middleware)
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __getitem__(self, item: int) -> MiddlewareType[TelegramObject]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __getitem__(self, item: slice) -> Sequence[MiddlewareType[TelegramObject]]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __getitem__(
|
||||||
|
self, item: Union[int, slice]
|
||||||
|
) -> Union[MiddlewareType[TelegramObject], Sequence[MiddlewareType[TelegramObject]]]:
|
||||||
|
return self._middlewares[item]
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self._middlewares)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def wrap_middlewares(
|
||||||
|
middlewares: Sequence[MiddlewareType[MiddlewareEventType]], handler: CallbackType
|
||||||
|
) -> NextMiddlewareType[MiddlewareEventType]:
|
||||||
|
@functools.wraps(handler)
|
||||||
|
def handler_wrapper(event: TelegramObject, kwargs: Dict[str, Any]) -> Any:
|
||||||
|
return handler(event, **kwargs)
|
||||||
|
|
||||||
|
middleware = handler_wrapper
|
||||||
|
for m in reversed(middlewares):
|
||||||
|
middleware = functools.partial(m, middleware)
|
||||||
|
return middleware
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Any, Dict, Generator, List, Optional, Set, Union
|
from typing import Any, Dict, Final, Generator, List, Optional, Set, Union
|
||||||
|
|
||||||
from ..types import TelegramObject
|
from ..types import TelegramObject
|
||||||
from ..utils.imports import import_module
|
|
||||||
from ..utils.warnings import CodeHasNoEffect
|
from ..utils.warnings import CodeHasNoEffect
|
||||||
from .event.bases import REJECTED, UNHANDLED
|
from .event.bases import REJECTED, UNHANDLED
|
||||||
from .event.event import EventObserver
|
from .event.event import EventObserver
|
||||||
from .event.telegram import TelegramEventObserver
|
from .event.telegram import TelegramEventObserver
|
||||||
from .filters import BUILTIN_FILTERS
|
from .filters import BUILTIN_FILTERS
|
||||||
|
|
||||||
INTERNAL_UPDATE_TYPES = frozenset({"update", "error"})
|
INTERNAL_UPDATE_TYPES: Final[frozenset[str]] = frozenset({"update", "error"})
|
||||||
|
|
||||||
|
|
||||||
class Router:
|
class Router:
|
||||||
|
|
@ -210,8 +209,6 @@ class Router:
|
||||||
:param router:
|
:param router:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if isinstance(router, str): # Resolve import string
|
|
||||||
router = import_module(router)
|
|
||||||
if not isinstance(router, Router):
|
if not isinstance(router, Router):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"router should be instance of Router not {type(router).__class__.__name__}"
|
f"router should be instance of Router not {type(router).__class__.__name__}"
|
||||||
|
|
@ -244,135 +241,3 @@ class Router:
|
||||||
await self.shutdown.trigger(*args, **kwargs)
|
await self.shutdown.trigger(*args, **kwargs)
|
||||||
for router in self.sub_routers:
|
for router in self.sub_routers:
|
||||||
await router.emit_shutdown(*args, **kwargs)
|
await router.emit_shutdown(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
|
||||||
def message_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.message_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.message(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
@property
|
|
||||||
def edited_message_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.edited_message_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.edited_message(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.edited_message
|
|
||||||
|
|
||||||
@property
|
|
||||||
def channel_post_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.channel_post_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.channel_post(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.channel_post
|
|
||||||
|
|
||||||
@property
|
|
||||||
def edited_channel_post_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.edited_channel_post_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.edited_channel_post(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.edited_channel_post
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inline_query_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.inline_query_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.inline_query(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.inline_query
|
|
||||||
|
|
||||||
@property
|
|
||||||
def chosen_inline_result_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.chosen_inline_result_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.chosen_inline_result(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.chosen_inline_result
|
|
||||||
|
|
||||||
@property
|
|
||||||
def callback_query_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.callback_query_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.callback_query(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.callback_query
|
|
||||||
|
|
||||||
@property
|
|
||||||
def shipping_query_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.shipping_query_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.shipping_query(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.shipping_query
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pre_checkout_query_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.pre_checkout_query_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.pre_checkout_query(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.pre_checkout_query
|
|
||||||
|
|
||||||
@property
|
|
||||||
def poll_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.poll_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.poll(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.poll
|
|
||||||
|
|
||||||
@property
|
|
||||||
def poll_answer_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.poll_answer_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.poll_answer(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.poll_answer
|
|
||||||
|
|
||||||
@property
|
|
||||||
def errors_handler(self) -> TelegramEventObserver:
|
|
||||||
warnings.warn(
|
|
||||||
"`Router.errors_handler(...)` is deprecated and will be removed in version 3.2 "
|
|
||||||
"use `Router.errors(...)`",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.errors
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ def setup_application(app: Application, dispatcher: Dispatcher, /, **kwargs: Any
|
||||||
"app": app,
|
"app": app,
|
||||||
"dispatcher": dispatcher,
|
"dispatcher": dispatcher,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
**dispatcher.workflow_data,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def on_startup(*a: Any, **kw: Any) -> None: # pragma: no cover
|
async def on_startup(*a: Any, **kw: Any) -> None: # pragma: no cover
|
||||||
|
|
@ -134,7 +135,7 @@ class BaseRequestHandler(ABC):
|
||||||
bot=bot, update=await request.json(loads=bot.session.json_loads)
|
bot=bot, update=await request.json(loads=bot.session.json_loads)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return web.json_response({})
|
return web.json_response({}, dumps=bot.session.json_dumps)
|
||||||
|
|
||||||
async def _handle_request(self, bot: Bot, request: web.Request) -> web.Response:
|
async def _handle_request(self, bot: Bot, request: web.Request) -> web.Response:
|
||||||
result = await self.dispatcher.feed_webhook_update(
|
result = await self.dispatcher.feed_webhook_update(
|
||||||
|
|
@ -143,8 +144,8 @@ class BaseRequestHandler(ABC):
|
||||||
**self.data,
|
**self.data,
|
||||||
)
|
)
|
||||||
if result:
|
if result:
|
||||||
return web.json_response(result)
|
return web.json_response(result, dumps=bot.session.json_dumps)
|
||||||
return web.json_response({})
|
return web.json_response({}, dumps=bot.session.json_dumps)
|
||||||
|
|
||||||
async def handle(self, request: web.Request) -> web.Response:
|
async def handle(self, request: web.Request) -> web.Response:
|
||||||
bot = await self.resolve_bot(request)
|
bot = await self.resolve_bot(request)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
dispatcher = logging.getLogger("aiogram.dispatcher")
|
dispatcher = logging.getLogger("aiogram.dispatcher")
|
||||||
|
event = logging.getLogger("aiogram.event")
|
||||||
middlewares = logging.getLogger("aiogram.middlewares")
|
middlewares = logging.getLogger("aiogram.middlewares")
|
||||||
webhook = logging.getLogger("aiogram.webhook")
|
webhook = logging.getLogger("aiogram.webhook")
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from .answer_callback_query import AnswerCallbackQuery
|
||||||
from .answer_inline_query import AnswerInlineQuery
|
from .answer_inline_query import AnswerInlineQuery
|
||||||
from .answer_pre_checkout_query import AnswerPreCheckoutQuery
|
from .answer_pre_checkout_query import AnswerPreCheckoutQuery
|
||||||
from .answer_shipping_query import AnswerShippingQuery
|
from .answer_shipping_query import AnswerShippingQuery
|
||||||
|
from .answer_web_app_query import AnswerWebAppQuery
|
||||||
from .approve_chat_join_request import ApproveChatJoinRequest
|
from .approve_chat_join_request import ApproveChatJoinRequest
|
||||||
from .ban_chat_member import BanChatMember
|
from .ban_chat_member import BanChatMember
|
||||||
from .ban_chat_sender_chat import BanChatSenderChat
|
from .ban_chat_sender_chat import BanChatSenderChat
|
||||||
|
|
@ -10,6 +11,7 @@ from .base import Request, Response, TelegramMethod
|
||||||
from .close import Close
|
from .close import Close
|
||||||
from .copy_message import CopyMessage
|
from .copy_message import CopyMessage
|
||||||
from .create_chat_invite_link import CreateChatInviteLink
|
from .create_chat_invite_link import CreateChatInviteLink
|
||||||
|
from .create_invoice_link import CreateInvoiceLink
|
||||||
from .create_new_sticker_set import CreateNewStickerSet
|
from .create_new_sticker_set import CreateNewStickerSet
|
||||||
from .decline_chat_join_request import DeclineChatJoinRequest
|
from .decline_chat_join_request import DeclineChatJoinRequest
|
||||||
from .delete_chat_photo import DeleteChatPhoto
|
from .delete_chat_photo import DeleteChatPhoto
|
||||||
|
|
@ -31,10 +33,12 @@ from .get_chat_administrators import GetChatAdministrators
|
||||||
from .get_chat_member import GetChatMember
|
from .get_chat_member import GetChatMember
|
||||||
from .get_chat_member_count import GetChatMemberCount
|
from .get_chat_member_count import GetChatMemberCount
|
||||||
from .get_chat_members_count import GetChatMembersCount
|
from .get_chat_members_count import GetChatMembersCount
|
||||||
|
from .get_chat_menu_button import GetChatMenuButton
|
||||||
from .get_file import GetFile
|
from .get_file import GetFile
|
||||||
from .get_game_high_scores import GetGameHighScores
|
from .get_game_high_scores import GetGameHighScores
|
||||||
from .get_me import GetMe
|
from .get_me import GetMe
|
||||||
from .get_my_commands import GetMyCommands
|
from .get_my_commands import GetMyCommands
|
||||||
|
from .get_my_default_administrator_rights import GetMyDefaultAdministratorRights
|
||||||
from .get_sticker_set import GetStickerSet
|
from .get_sticker_set import GetStickerSet
|
||||||
from .get_updates import GetUpdates
|
from .get_updates import GetUpdates
|
||||||
from .get_user_profile_photos import GetUserProfilePhotos
|
from .get_user_profile_photos import GetUserProfilePhotos
|
||||||
|
|
@ -66,12 +70,14 @@ from .send_video_note import SendVideoNote
|
||||||
from .send_voice import SendVoice
|
from .send_voice import SendVoice
|
||||||
from .set_chat_administrator_custom_title import SetChatAdministratorCustomTitle
|
from .set_chat_administrator_custom_title import SetChatAdministratorCustomTitle
|
||||||
from .set_chat_description import SetChatDescription
|
from .set_chat_description import SetChatDescription
|
||||||
|
from .set_chat_menu_button import SetChatMenuButton
|
||||||
from .set_chat_permissions import SetChatPermissions
|
from .set_chat_permissions import SetChatPermissions
|
||||||
from .set_chat_photo import SetChatPhoto
|
from .set_chat_photo import SetChatPhoto
|
||||||
from .set_chat_sticker_set import SetChatStickerSet
|
from .set_chat_sticker_set import SetChatStickerSet
|
||||||
from .set_chat_title import SetChatTitle
|
from .set_chat_title import SetChatTitle
|
||||||
from .set_game_score import SetGameScore
|
from .set_game_score import SetGameScore
|
||||||
from .set_my_commands import SetMyCommands
|
from .set_my_commands import SetMyCommands
|
||||||
|
from .set_my_default_administrator_rights import SetMyDefaultAdministratorRights
|
||||||
from .set_passport_data_errors import SetPassportDataErrors
|
from .set_passport_data_errors import SetPassportDataErrors
|
||||||
from .set_sticker_position_in_set import SetStickerPositionInSet
|
from .set_sticker_position_in_set import SetStickerPositionInSet
|
||||||
from .set_sticker_set_thumb import SetStickerSetThumb
|
from .set_sticker_set_thumb import SetStickerSetThumb
|
||||||
|
|
@ -150,6 +156,10 @@ __all__ = (
|
||||||
"SetMyCommands",
|
"SetMyCommands",
|
||||||
"DeleteMyCommands",
|
"DeleteMyCommands",
|
||||||
"GetMyCommands",
|
"GetMyCommands",
|
||||||
|
"SetChatMenuButton",
|
||||||
|
"GetChatMenuButton",
|
||||||
|
"SetMyDefaultAdministratorRights",
|
||||||
|
"GetMyDefaultAdministratorRights",
|
||||||
"EditMessageText",
|
"EditMessageText",
|
||||||
"EditMessageCaption",
|
"EditMessageCaption",
|
||||||
"EditMessageMedia",
|
"EditMessageMedia",
|
||||||
|
|
@ -165,7 +175,9 @@ __all__ = (
|
||||||
"DeleteStickerFromSet",
|
"DeleteStickerFromSet",
|
||||||
"SetStickerSetThumb",
|
"SetStickerSetThumb",
|
||||||
"AnswerInlineQuery",
|
"AnswerInlineQuery",
|
||||||
|
"AnswerWebAppQuery",
|
||||||
"SendInvoice",
|
"SendInvoice",
|
||||||
|
"CreateInvoiceLink",
|
||||||
"AnswerShippingQuery",
|
"AnswerShippingQuery",
|
||||||
"AnswerPreCheckoutQuery",
|
"AnswerPreCheckoutQuery",
|
||||||
"SetPassportDataErrors",
|
"SetPassportDataErrors",
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class AddStickerToSet(TelegramMethod[bool]):
|
class AddStickerToSet(TelegramMethod[bool]):
|
||||||
"""
|
"""
|
||||||
Use this method to add a new sticker to a set created by the bot. You **must** use exactly one of the fields *png_sticker* or *tgs_sticker*. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns :code:`True` on success.
|
Use this method to add a new sticker to a set created by the bot. You **must** use exactly one of the fields *png_sticker*, *tgs_sticker*, or *webm_sticker*. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns :code:`True` on success.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#addstickertoset
|
Source: https://core.telegram.org/bots/api#addstickertoset
|
||||||
"""
|
"""
|
||||||
|
|
@ -25,17 +25,20 @@ class AddStickerToSet(TelegramMethod[bool]):
|
||||||
emojis: str
|
emojis: str
|
||||||
"""One or more emoji corresponding to the sticker"""
|
"""One or more emoji corresponding to the sticker"""
|
||||||
png_sticker: Optional[Union[InputFile, str]] = None
|
png_sticker: Optional[Union[InputFile, str]] = None
|
||||||
"""**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » <sending-files>`"""
|
"""**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
tgs_sticker: Optional[InputFile] = None
|
tgs_sticker: Optional[InputFile] = None
|
||||||
"""**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/animated_stickers#technical-requirements <https://core.telegram.org/animated_stickers#technical-requirements>`_`https://core.telegram.org/animated_stickers#technical-requirements <https://core.telegram.org/animated_stickers#technical-requirements>`_ for technical requirements"""
|
"""**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements <https://core.telegram.org/stickers#animated-sticker-requirements>`_`https://core.telegram.org/stickers#animated-sticker-requirements <https://core.telegram.org/stickers#animated-sticker-requirements>`_ for technical requirements"""
|
||||||
|
webm_sticker: Optional[InputFile] = None
|
||||||
|
"""**WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements <https://core.telegram.org/stickers#video-sticker-requirements>`_`https://core.telegram.org/stickers#video-sticker-requirements <https://core.telegram.org/stickers#video-sticker-requirements>`_ for technical requirements"""
|
||||||
mask_position: Optional[MaskPosition] = None
|
mask_position: Optional[MaskPosition] = None
|
||||||
"""A JSON-serialized object for position where the mask should be placed on faces"""
|
"""A JSON-serialized object for position where the mask should be placed on faces"""
|
||||||
|
|
||||||
def build_request(self, bot: Bot) -> Request:
|
def build_request(self, bot: Bot) -> Request:
|
||||||
data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker"})
|
data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker", "webm_sticker"})
|
||||||
|
|
||||||
files: Dict[str, InputFile] = {}
|
files: Dict[str, InputFile] = {}
|
||||||
prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker)
|
prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker)
|
||||||
prepare_file(data=data, files=files, name="tgs_sticker", value=self.tgs_sticker)
|
prepare_file(data=data, files=files, name="tgs_sticker", value=self.tgs_sticker)
|
||||||
|
prepare_file(data=data, files=files, name="webm_sticker", value=self.webm_sticker)
|
||||||
|
|
||||||
return Request(method="addStickerToSet", data=data, files=files)
|
return Request(method="addStickerToSet", data=data, files=files)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class AnswerCallbackQuery(TelegramMethod[bool]):
|
||||||
"""
|
"""
|
||||||
Use this method to send answers to callback queries sent from `inline keyboards <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_. The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. On success, :code:`True` is returned.
|
Use this method to send answers to callback queries sent from `inline keyboards <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_. The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. On success, :code:`True` is returned.
|
||||||
|
|
||||||
Alternatively, the user can be redirected to the specified Game URL. For this option to work, you must first create a game for your bot via `@Botfather <https://t.me/botfather>`_ and accept the terms. Otherwise, you may use links like :code:`t.me/your_bot?start=XXXX` that open your bot with a parameter.
|
Alternatively, the user can be redirected to the specified Game URL. For this option to work, you must first create a game for your bot via `@BotFather <https://t.me/botfather>`_ and accept the terms. Otherwise, you may use links like :code:`t.me/your_bot?start=XXXX` that open your bot with a parameter.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#answercallbackquery
|
Source: https://core.telegram.org/bots/api#answercallbackquery
|
||||||
"""
|
"""
|
||||||
|
|
@ -26,7 +26,7 @@ class AnswerCallbackQuery(TelegramMethod[bool]):
|
||||||
show_alert: Optional[bool] = None
|
show_alert: Optional[bool] = None
|
||||||
"""If :code:`True`, an alert will be shown by the client instead of a notification at the top of the chat screen. Defaults to *false*."""
|
"""If :code:`True`, an alert will be shown by the client instead of a notification at the top of the chat screen. Defaults to *false*."""
|
||||||
url: Optional[str] = None
|
url: Optional[str] = None
|
||||||
"""URL that will be opened by the user's client. If you have created a :class:`aiogram.types.game.Game` and accepted the conditions via `@Botfather <https://t.me/botfather>`_, specify the URL that opens your game — note that this will only work if the query comes from a `https://core.telegram.org/bots/api#inlinekeyboardbutton <https://core.telegram.org/bots/api#inlinekeyboardbutton>`_ *callback_game* button."""
|
"""URL that will be opened by the user's client. If you have created a :class:`aiogram.types.game.Game` and accepted the conditions via `@BotFather <https://t.me/botfather>`_, specify the URL that opens your game - note that this will only work if the query comes from a `https://core.telegram.org/bots/api#inlinekeyboardbutton <https://core.telegram.org/bots/api#inlinekeyboardbutton>`_ *callback_game* button."""
|
||||||
cache_time: Optional[int] = None
|
cache_time: Optional[int] = None
|
||||||
"""The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0."""
|
"""The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0."""
|
||||||
|
|
||||||
|
|
|
||||||
33
aiogram/methods/answer_web_app_query.py
Normal file
33
aiogram/methods/answer_web_app_query.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict
|
||||||
|
|
||||||
|
from ..types import InlineQueryResult, SentWebAppMessage
|
||||||
|
from .base import Request, TelegramMethod, prepare_parse_mode
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..client.bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class AnswerWebAppQuery(TelegramMethod[SentWebAppMessage]):
|
||||||
|
"""
|
||||||
|
Use this method to set the result of an interaction with a `Web App <https://core.telegram.org/bots/webapps>`_ and send a corresponding message on behalf of the user to the chat from which the query originated. On success, a :class:`aiogram.types.sent_web_app_message.SentWebAppMessage` object is returned.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#answerwebappquery
|
||||||
|
"""
|
||||||
|
|
||||||
|
__returning__ = SentWebAppMessage
|
||||||
|
|
||||||
|
web_app_query_id: str
|
||||||
|
"""Unique identifier for the query to be answered"""
|
||||||
|
result: InlineQueryResult
|
||||||
|
"""A JSON-serialized object describing the message to be sent"""
|
||||||
|
|
||||||
|
def build_request(self, bot: Bot) -> Request:
|
||||||
|
data: Dict[str, Any] = self.dict()
|
||||||
|
|
||||||
|
prepare_parse_mode(
|
||||||
|
bot, data["result"], parse_mode_property="parse_mode", entities_property="entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
return Request(method="answerWebAppQuery", data=data)
|
||||||
|
|
@ -46,6 +46,7 @@ class TelegramMethod(abc.ABC, BaseModel, Generic[TelegramType]):
|
||||||
allow_population_by_field_name = True
|
allow_population_by_field_name = True
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
smart_union = True # https://github.com/aiogram/aiogram/issues/901
|
||||||
|
|
||||||
@root_validator(pre=True)
|
@root_validator(pre=True)
|
||||||
def remove_unset(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
def remove_unset(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
|
@ -84,6 +85,8 @@ class TelegramMethod(abc.ABC, BaseModel, Generic[TelegramType]):
|
||||||
async def emit(self, bot: Bot) -> TelegramType:
|
async def emit(self, bot: Bot) -> TelegramType:
|
||||||
return await bot(self)
|
return await bot(self)
|
||||||
|
|
||||||
|
as_ = emit
|
||||||
|
|
||||||
def __await__(self) -> Generator[Any, None, TelegramType]:
|
def __await__(self) -> Generator[Any, None, TelegramType]:
|
||||||
from aiogram.client.bot import Bot
|
from aiogram.client.bot import Bot
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ class CopyMessage(TelegramMethod[MessageId]):
|
||||||
"""A JSON-serialized list of special entities that appear in the new caption, which can be specified instead of *parse_mode*"""
|
"""A JSON-serialized list of special entities that appear in the new caption, which can be specified instead of *parse_mode*"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class CreateChatInviteLink(TelegramMethod[ChatInviteLink]):
|
||||||
expire_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None
|
expire_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None
|
||||||
"""Point in time (Unix timestamp) when the link will expire"""
|
"""Point in time (Unix timestamp) when the link will expire"""
|
||||||
member_limit: Optional[int] = None
|
member_limit: Optional[int] = None
|
||||||
"""Maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999"""
|
"""The maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999"""
|
||||||
creates_join_request: Optional[bool] = None
|
creates_join_request: Optional[bool] = None
|
||||||
""":code:`True`, if users joining the chat via the link need to be approved by chat administrators. If :code:`True`, *member_limit* can't be specified"""
|
""":code:`True`, if users joining the chat via the link need to be approved by chat administrators. If :code:`True`, *member_limit* can't be specified"""
|
||||||
|
|
||||||
|
|
|
||||||
65
aiogram/methods/create_invoice_link.py
Normal file
65
aiogram/methods/create_invoice_link.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from ..types import LabeledPrice
|
||||||
|
from .base import Request, TelegramMethod
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..client.bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class CreateInvoiceLink(TelegramMethod[str]):
|
||||||
|
"""
|
||||||
|
Use this method to create a link for an invoice. Returns the created invoice link as *String* on success.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#createinvoicelink
|
||||||
|
"""
|
||||||
|
|
||||||
|
__returning__ = str
|
||||||
|
|
||||||
|
title: str
|
||||||
|
"""Product name, 1-32 characters"""
|
||||||
|
description: str
|
||||||
|
"""Product description, 1-255 characters"""
|
||||||
|
payload: str
|
||||||
|
"""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 <https://t.me/botfather>`_"""
|
||||||
|
currency: str
|
||||||
|
"""Three-letter ISO 4217 currency code, see `more on currencies <https://core.telegram.org/bots/payments#supported-currencies>`_"""
|
||||||
|
prices: List[LabeledPrice]
|
||||||
|
"""Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)"""
|
||||||
|
max_tip_amount: Optional[int] = None
|
||||||
|
"""The maximum accepted amount for tips in the *smallest units* of the currency (integer, **not** float/double). For example, for a maximum tip of :code:`US$ 1.45` pass :code:`max_tip_amount = 145`. See the *exp* parameter in `currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). Defaults to 0"""
|
||||||
|
suggested_tip_amounts: Optional[List[int]] = None
|
||||||
|
"""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 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: Optional[str] = None
|
||||||
|
"""JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider."""
|
||||||
|
photo_url: Optional[str] = None
|
||||||
|
"""URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service."""
|
||||||
|
photo_size: Optional[int] = None
|
||||||
|
"""Photo size in bytes"""
|
||||||
|
photo_width: Optional[int] = None
|
||||||
|
"""Photo width"""
|
||||||
|
photo_height: Optional[int] = None
|
||||||
|
"""Photo height"""
|
||||||
|
need_name: Optional[bool] = None
|
||||||
|
"""Pass :code:`True`, if you require the user's full name to complete the order"""
|
||||||
|
need_phone_number: Optional[bool] = None
|
||||||
|
"""Pass :code:`True`, if you require the user's phone number to complete the order"""
|
||||||
|
need_email: Optional[bool] = None
|
||||||
|
"""Pass :code:`True`, if you require the user's email address to complete the order"""
|
||||||
|
need_shipping_address: Optional[bool] = None
|
||||||
|
"""Pass :code:`True`, if you require the user's shipping address to complete the order"""
|
||||||
|
send_phone_number_to_provider: Optional[bool] = None
|
||||||
|
"""Pass :code:`True`, if the user's phone number should be sent to the provider"""
|
||||||
|
send_email_to_provider: Optional[bool] = None
|
||||||
|
"""Pass :code:`True`, if the user's email address should be sent to the provider"""
|
||||||
|
is_flexible: Optional[bool] = None
|
||||||
|
"""Pass :code:`True`, if the final price depends on the shipping method"""
|
||||||
|
|
||||||
|
def build_request(self, bot: Bot) -> Request:
|
||||||
|
data: Dict[str, Any] = self.dict()
|
||||||
|
|
||||||
|
return Request(method="createInvoiceLink", data=data)
|
||||||
|
|
@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class CreateNewStickerSet(TelegramMethod[bool]):
|
class CreateNewStickerSet(TelegramMethod[bool]):
|
||||||
"""
|
"""
|
||||||
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. You **must** use exactly one of the fields *png_sticker* or *tgs_sticker*. Returns :code:`True` on success.
|
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. You **must** use exactly one of the fields *png_sticker*, *tgs_sticker*, or *webm_sticker*. Returns :code:`True` on success.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#createnewstickerset
|
Source: https://core.telegram.org/bots/api#createnewstickerset
|
||||||
"""
|
"""
|
||||||
|
|
@ -21,25 +21,28 @@ class CreateNewStickerSet(TelegramMethod[bool]):
|
||||||
user_id: int
|
user_id: int
|
||||||
"""User identifier of created sticker set owner"""
|
"""User identifier of created sticker set owner"""
|
||||||
name: str
|
name: str
|
||||||
"""Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in *'_by_<bot username>'*. *<bot_username>* is case insensitive. 1-64 characters."""
|
"""Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only English letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in :code:`"_by_<bot_username>"`. :code:`<bot_username>` is case insensitive. 1-64 characters."""
|
||||||
title: str
|
title: str
|
||||||
"""Sticker set title, 1-64 characters"""
|
"""Sticker set title, 1-64 characters"""
|
||||||
emojis: str
|
emojis: str
|
||||||
"""One or more emoji corresponding to the sticker"""
|
"""One or more emoji corresponding to the sticker"""
|
||||||
png_sticker: Optional[Union[InputFile, str]] = None
|
png_sticker: Optional[Union[InputFile, str]] = None
|
||||||
"""**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » <sending-files>`"""
|
"""**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
tgs_sticker: Optional[InputFile] = None
|
tgs_sticker: Optional[InputFile] = None
|
||||||
"""**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/animated_stickers#technical-requirements <https://core.telegram.org/animated_stickers#technical-requirements>`_`https://core.telegram.org/animated_stickers#technical-requirements <https://core.telegram.org/animated_stickers#technical-requirements>`_ for technical requirements"""
|
"""**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements <https://core.telegram.org/stickers#animated-sticker-requirements>`_`https://core.telegram.org/stickers#animated-sticker-requirements <https://core.telegram.org/stickers#animated-sticker-requirements>`_ for technical requirements"""
|
||||||
|
webm_sticker: Optional[InputFile] = None
|
||||||
|
"""**WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements <https://core.telegram.org/stickers#video-sticker-requirements>`_`https://core.telegram.org/stickers#video-sticker-requirements <https://core.telegram.org/stickers#video-sticker-requirements>`_ for technical requirements"""
|
||||||
contains_masks: Optional[bool] = None
|
contains_masks: Optional[bool] = None
|
||||||
"""Pass :code:`True`, if a set of mask stickers should be created"""
|
"""Pass :code:`True`, if a set of mask stickers should be created"""
|
||||||
mask_position: Optional[MaskPosition] = None
|
mask_position: Optional[MaskPosition] = None
|
||||||
"""A JSON-serialized object for position where the mask should be placed on faces"""
|
"""A JSON-serialized object for position where the mask should be placed on faces"""
|
||||||
|
|
||||||
def build_request(self, bot: Bot) -> Request:
|
def build_request(self, bot: Bot) -> Request:
|
||||||
data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker"})
|
data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker", "webm_sticker"})
|
||||||
|
|
||||||
files: Dict[str, InputFile] = {}
|
files: Dict[str, InputFile] = {}
|
||||||
prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker)
|
prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker)
|
||||||
prepare_file(data=data, files=files, name="tgs_sticker", value=self.tgs_sticker)
|
prepare_file(data=data, files=files, name="tgs_sticker", value=self.tgs_sticker)
|
||||||
|
prepare_file(data=data, files=files, name="webm_sticker", value=self.webm_sticker)
|
||||||
|
|
||||||
return Request(method="createNewStickerSet", data=data, files=files)
|
return Request(method="createNewStickerSet", data=data, files=files)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class EditChatInviteLink(TelegramMethod[ChatInviteLink]):
|
||||||
expire_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None
|
expire_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None
|
||||||
"""Point in time (Unix timestamp) when the link will expire"""
|
"""Point in time (Unix timestamp) when the link will expire"""
|
||||||
member_limit: Optional[int] = None
|
member_limit: Optional[int] = None
|
||||||
"""Maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999"""
|
"""The maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999"""
|
||||||
creates_join_request: Optional[bool] = None
|
creates_join_request: Optional[bool] = None
|
||||||
""":code:`True`, if users joining the chat via the link need to be approved by chat administrators. If :code:`True`, *member_limit* can't be specified"""
|
""":code:`True`, if users joining the chat via the link need to be approved by chat administrators. If :code:`True`, *member_limit* can't be specified"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class EditMessageLiveLocation(TelegramMethod[Union[Message, bool]]):
|
||||||
heading: Optional[int] = None
|
heading: Optional[int] = None
|
||||||
"""Direction in which the user is moving, in degrees. Must be between 1 and 360 if specified."""
|
"""Direction in which the user is moving, in degrees. Must be between 1 and 360 if specified."""
|
||||||
proximity_alert_radius: Optional[int] = None
|
proximity_alert_radius: Optional[int] = None
|
||||||
"""Maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified."""
|
"""The maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified."""
|
||||||
reply_markup: Optional[InlineKeyboardMarkup] = None
|
reply_markup: Optional[InlineKeyboardMarkup] = None
|
||||||
"""A JSON-serialized object for a new `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_."""
|
"""A JSON-serialized object for a new `inline keyboard <https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating>`_."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ class ForwardMessage(TelegramMethod[Message]):
|
||||||
"""Message identifier in the chat specified in *from_chat_id*"""
|
"""Message identifier in the chat specified in *from_chat_id*"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the forwarded message from forwarding and saving"""
|
||||||
|
|
||||||
def build_request(self, bot: Bot) -> Request:
|
def build_request(self, bot: Bot) -> Request:
|
||||||
data: Dict[str, Any] = self.dict()
|
data: Dict[str, Any] = self.dict()
|
||||||
|
|
|
||||||
27
aiogram/methods/get_chat_menu_button.py
Normal file
27
aiogram/methods/get_chat_menu_button.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
|
||||||
|
|
||||||
|
from ..types import MenuButton, MenuButtonCommands, MenuButtonDefault, MenuButtonWebApp
|
||||||
|
from .base import Request, TelegramMethod
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..client.bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class GetChatMenuButton(TelegramMethod[MenuButton]):
|
||||||
|
"""
|
||||||
|
Use this method to get the current value of the bot's menu button in a private chat, or the default menu button. Returns :class:`aiogram.types.menu_button.MenuButton` on success.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#getchatmenubutton
|
||||||
|
"""
|
||||||
|
|
||||||
|
__returning__ = Union[MenuButtonDefault, MenuButtonWebApp, MenuButtonCommands]
|
||||||
|
|
||||||
|
chat_id: Optional[int] = None
|
||||||
|
"""Unique identifier for the target private chat. If not specified, default bot's menu button will be returned"""
|
||||||
|
|
||||||
|
def build_request(self, bot: Bot) -> Request:
|
||||||
|
data: Dict[str, Any] = self.dict()
|
||||||
|
|
||||||
|
return Request(method="getChatMenuButton", data=data)
|
||||||
|
|
@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class GetFile(TelegramMethod[File]):
|
class GetFile(TelegramMethod[File]):
|
||||||
"""
|
"""
|
||||||
Use this method to get basic info about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size. On success, a :class:`aiogram.types.file.File` object is returned. The file can then be downloaded via the link :code:`https://api.telegram.org/file/bot<token>/<file_path>`, where :code:`<file_path>` is taken from the response. It is guaranteed that the link will be valid for at least 1 hour. When the link expires, a new one can be requested by calling :class:`aiogram.methods.get_file.GetFile` again.
|
Use this method to get basic information about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size. On success, a :class:`aiogram.types.file.File` object is returned. The file can then be downloaded via the link :code:`https://api.telegram.org/file/bot<token>/<file_path>`, where :code:`<file_path>` is taken from the response. It is guaranteed that the link will be valid for at least 1 hour. When the link expires, a new one can be requested by calling :class:`aiogram.methods.get_file.GetFile` again.
|
||||||
**Note:** This function may not preserve the original file name and MIME type. You should save the file's MIME type and name (if available) when the File object is received.
|
**Note:** This function may not preserve the original file name and MIME type. You should save the file's MIME type and name (if available) when the File object is received.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#getfile
|
Source: https://core.telegram.org/bots/api#getfile
|
||||||
|
|
@ -20,7 +20,7 @@ class GetFile(TelegramMethod[File]):
|
||||||
__returning__ = File
|
__returning__ = File
|
||||||
|
|
||||||
file_id: str
|
file_id: str
|
||||||
"""File identifier to get info about"""
|
"""File identifier to get information about"""
|
||||||
|
|
||||||
def build_request(self, bot: Bot) -> Request:
|
def build_request(self, bot: Bot) -> Request:
|
||||||
data: Dict[str, Any] = self.dict()
|
data: Dict[str, Any] = self.dict()
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class GetGameHighScores(TelegramMethod[List[GameHighScore]]):
|
||||||
"""
|
"""
|
||||||
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. On success, returns an *Array* of :class:`aiogram.types.game_high_score.GameHighScore` objects.
|
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. On success, returns an *Array* of :class:`aiogram.types.game_high_score.GameHighScore` objects.
|
||||||
|
|
||||||
This method will currently return scores for the target user, plus two of their closest neighbors on each side. Will also return the top three users if the user and his neighbors are not among them. Please note that this behavior is subject to change.
|
This method will currently return scores for the target user, plus two of their closest neighbors on each side. Will also return the top three users if the user and their neighbors are not among them. Please note that this behavior is subject to change.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#getgamehighscores
|
Source: https://core.telegram.org/bots/api#getgamehighscores
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
27
aiogram/methods/get_my_default_administrator_rights.py
Normal file
27
aiogram/methods/get_my_default_administrator_rights.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||||
|
|
||||||
|
from ..types import ChatAdministratorRights
|
||||||
|
from .base import Request, TelegramMethod
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..client.bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class GetMyDefaultAdministratorRights(TelegramMethod[ChatAdministratorRights]):
|
||||||
|
"""
|
||||||
|
Use this method to get the current default administrator rights of the bot. Returns :class:`aiogram.types.chat_administrator_rights.ChatAdministratorRights` on success.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#getmydefaultadministratorrights
|
||||||
|
"""
|
||||||
|
|
||||||
|
__returning__ = ChatAdministratorRights
|
||||||
|
|
||||||
|
for_channels: Optional[bool] = None
|
||||||
|
"""Pass :code:`True` to get default administrator rights of the bot in channels. Otherwise, default administrator rights of the bot for groups and supergroups will be returned."""
|
||||||
|
|
||||||
|
def build_request(self, bot: Bot) -> Request:
|
||||||
|
data: Dict[str, Any] = self.dict()
|
||||||
|
|
||||||
|
return Request(method="getMyDefaultAdministratorRights", data=data)
|
||||||
|
|
@ -31,8 +31,8 @@ class PromoteChatMember(TelegramMethod[bool]):
|
||||||
"""Pass :code:`True`, if the administrator can edit messages of other users and can pin messages, channels only"""
|
"""Pass :code:`True`, if the administrator can edit messages of other users and can pin messages, channels only"""
|
||||||
can_delete_messages: Optional[bool] = None
|
can_delete_messages: Optional[bool] = None
|
||||||
"""Pass :code:`True`, if the administrator can delete messages of other users"""
|
"""Pass :code:`True`, if the administrator can delete messages of other users"""
|
||||||
can_manage_voice_chats: Optional[bool] = None
|
can_manage_video_chats: Optional[bool] = None
|
||||||
"""Pass :code:`True`, if the administrator can manage voice chats"""
|
"""Pass :code:`True`, if the administrator can manage video chats"""
|
||||||
can_restrict_members: Optional[bool] = None
|
can_restrict_members: Optional[bool] = None
|
||||||
"""Pass :code:`True`, if the administrator can restrict, ban or unban chat members"""
|
"""Pass :code:`True`, if the administrator can restrict, ban or unban chat members"""
|
||||||
can_promote_members: Optional[bool] = None
|
can_promote_members: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class SendAnimation(TelegramMethod[Message]):
|
||||||
chat_id: Union[int, str]
|
chat_id: Union[int, str]
|
||||||
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
||||||
animation: Union[InputFile, str]
|
animation: Union[InputFile, str]
|
||||||
"""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. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
duration: Optional[int] = None
|
duration: Optional[int] = None
|
||||||
"""Duration of sent animation in seconds"""
|
"""Duration of sent animation in seconds"""
|
||||||
width: Optional[int] = None
|
width: Optional[int] = None
|
||||||
|
|
@ -38,7 +38,7 @@ class SendAnimation(TelegramMethod[Message]):
|
||||||
height: Optional[int] = None
|
height: Optional[int] = None
|
||||||
"""Animation height"""
|
"""Animation height"""
|
||||||
thumb: Optional[Union[InputFile, str]] = None
|
thumb: Optional[Union[InputFile, str]] = None
|
||||||
"""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>. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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>. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
caption: Optional[str] = None
|
caption: Optional[str] = None
|
||||||
"""Animation caption (may also be used when resending animation by *file_id*), 0-1024 characters after entities parsing"""
|
"""Animation caption (may also be used when resending animation by *file_id*), 0-1024 characters after entities parsing"""
|
||||||
parse_mode: Optional[str] = UNSET
|
parse_mode: Optional[str] = UNSET
|
||||||
|
|
@ -47,6 +47,8 @@ class SendAnimation(TelegramMethod[Message]):
|
||||||
"""A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*"""
|
"""A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class SendAudio(TelegramMethod[Message]):
|
||||||
chat_id: Union[int, str]
|
chat_id: Union[int, str]
|
||||||
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
||||||
audio: Union[InputFile, str]
|
audio: Union[InputFile, str]
|
||||||
"""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. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
caption: Optional[str] = None
|
caption: Optional[str] = None
|
||||||
"""Audio caption, 0-1024 characters after entities parsing"""
|
"""Audio caption, 0-1024 characters after entities parsing"""
|
||||||
parse_mode: Optional[str] = UNSET
|
parse_mode: Optional[str] = UNSET
|
||||||
|
|
@ -45,9 +45,11 @@ class SendAudio(TelegramMethod[Message]):
|
||||||
title: Optional[str] = None
|
title: Optional[str] = None
|
||||||
"""Track name"""
|
"""Track name"""
|
||||||
thumb: Optional[Union[InputFile, str]] = None
|
thumb: Optional[Union[InputFile, str]] = None
|
||||||
"""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>. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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>. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ class SendContact(TelegramMethod[Message]):
|
||||||
"""Additional data about the contact in the form of a `vCard <https://en.wikipedia.org/wiki/VCard>`_, 0-2048 bytes"""
|
"""Additional data about the contact in the form of a `vCard <https://en.wikipedia.org/wiki/VCard>`_, 0-2048 bytes"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ class SendDice(TelegramMethod[Message]):
|
||||||
"""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 '🎲'"""
|
"""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 '🎲'"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,9 @@ class SendDocument(TelegramMethod[Message]):
|
||||||
chat_id: Union[int, str]
|
chat_id: Union[int, str]
|
||||||
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
||||||
document: Union[InputFile, str]
|
document: Union[InputFile, str]
|
||||||
"""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. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
thumb: Optional[Union[InputFile, str]] = None
|
thumb: Optional[Union[InputFile, str]] = None
|
||||||
"""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>. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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>. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
caption: Optional[str] = None
|
caption: Optional[str] = None
|
||||||
"""Document caption (may also be used when resending documents by *file_id*), 0-1024 characters after entities parsing"""
|
"""Document caption (may also be used when resending documents by *file_id*), 0-1024 characters after entities parsing"""
|
||||||
parse_mode: Optional[str] = UNSET
|
parse_mode: Optional[str] = UNSET
|
||||||
|
|
@ -43,6 +43,8 @@ class SendDocument(TelegramMethod[Message]):
|
||||||
"""Disables automatic server-side content type detection for files uploaded using multipart/form-data"""
|
"""Disables automatic server-side content type detection for files uploaded using multipart/form-data"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,11 @@ class SendGame(TelegramMethod[Message]):
|
||||||
chat_id: int
|
chat_id: int
|
||||||
"""Unique identifier for the target chat"""
|
"""Unique identifier for the target chat"""
|
||||||
game_short_name: str
|
game_short_name: str
|
||||||
"""Short name of the game, serves as the unique identifier for the game. Set up your games via `Botfather <https://t.me/botfather>`_."""
|
"""Short name of the game, serves as the unique identifier for the game. Set up your games via `@BotFather <https://t.me/botfather>`_."""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class SendInvoice(TelegramMethod[Message]):
|
||||||
payload: str
|
payload: str
|
||||||
"""Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes."""
|
"""Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes."""
|
||||||
provider_token: str
|
provider_token: str
|
||||||
"""Payments provider token, obtained via `Botfather <https://t.me/botfather>`_"""
|
"""Payment provider token, obtained via `@BotFather <https://t.me/botfather>`_"""
|
||||||
currency: str
|
currency: str
|
||||||
"""Three-letter ISO 4217 currency code, see `more on currencies <https://core.telegram.org/bots/payments#supported-currencies>`_"""
|
"""Three-letter ISO 4217 currency code, see `more on currencies <https://core.telegram.org/bots/payments#supported-currencies>`_"""
|
||||||
prices: List[LabeledPrice]
|
prices: List[LabeledPrice]
|
||||||
|
|
@ -39,11 +39,11 @@ class SendInvoice(TelegramMethod[Message]):
|
||||||
start_parameter: Optional[str] = None
|
start_parameter: Optional[str] = None
|
||||||
"""Unique deep-linking parameter. If left empty, **forwarded copies** of the sent message will have a *Pay* button, allowing multiple users to pay directly from the forwarded message, using the same invoice. If non-empty, forwarded copies of the sent message will have a *URL* button with a deep link to the bot (instead of a *Pay* button), with the value used as the start parameter"""
|
"""Unique deep-linking parameter. If left empty, **forwarded copies** of the sent message will have a *Pay* button, allowing multiple users to pay directly from the forwarded message, using the same invoice. If non-empty, forwarded copies of the sent message will have a *URL* button with a deep link to the bot (instead of a *Pay* button), with the value used as the start parameter"""
|
||||||
provider_data: Optional[str] = None
|
provider_data: Optional[str] = None
|
||||||
"""A JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider."""
|
"""JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider."""
|
||||||
photo_url: Optional[str] = None
|
photo_url: Optional[str] = None
|
||||||
"""URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for."""
|
"""URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for."""
|
||||||
photo_size: Optional[int] = None
|
photo_size: Optional[int] = None
|
||||||
"""Photo size"""
|
"""Photo size in bytes"""
|
||||||
photo_width: Optional[int] = None
|
photo_width: Optional[int] = None
|
||||||
"""Photo width"""
|
"""Photo width"""
|
||||||
photo_height: Optional[int] = None
|
photo_height: Optional[int] = None
|
||||||
|
|
@ -57,13 +57,15 @@ class SendInvoice(TelegramMethod[Message]):
|
||||||
need_shipping_address: Optional[bool] = None
|
need_shipping_address: Optional[bool] = None
|
||||||
"""Pass :code:`True`, if you require the user's shipping address to complete the order"""
|
"""Pass :code:`True`, if you require the user's shipping address to complete the order"""
|
||||||
send_phone_number_to_provider: Optional[bool] = None
|
send_phone_number_to_provider: Optional[bool] = None
|
||||||
"""Pass :code:`True`, if user's phone number should be sent to provider"""
|
"""Pass :code:`True`, if the user's phone number should be sent to provider"""
|
||||||
send_email_to_provider: Optional[bool] = None
|
send_email_to_provider: Optional[bool] = None
|
||||||
"""Pass :code:`True`, if user's email address should be sent to provider"""
|
"""Pass :code:`True`, if the user's email address should be sent to provider"""
|
||||||
is_flexible: Optional[bool] = None
|
is_flexible: Optional[bool] = None
|
||||||
"""Pass :code:`True`, if the final price depends on the shipping method"""
|
"""Pass :code:`True`, if the final price depends on the shipping method"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ class SendLocation(TelegramMethod[Message]):
|
||||||
"""For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified."""
|
"""For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified."""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ class SendMediaGroup(TelegramMethod[List[Message]]):
|
||||||
"""A JSON-serialized array describing messages to be sent, must include 2-10 items"""
|
"""A JSON-serialized array describing messages to be sent, must include 2-10 items"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends messages `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends messages `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent messages from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the messages are a reply, ID of the original message"""
|
"""If the messages are a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ class SendMessage(TelegramMethod[Message]):
|
||||||
"""Disables link previews for links in this message"""
|
"""Disables link previews for links in this message"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class SendPhoto(TelegramMethod[Message]):
|
||||||
chat_id: Union[int, str]
|
chat_id: Union[int, str]
|
||||||
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
||||||
photo: Union[InputFile, str]
|
photo: Union[InputFile, str]
|
||||||
"""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. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
caption: Optional[str] = None
|
caption: Optional[str] = None
|
||||||
"""Photo caption (may also be used when resending photos by *file_id*), 0-1024 characters after entities parsing"""
|
"""Photo caption (may also be used when resending photos by *file_id*), 0-1024 characters after entities parsing"""
|
||||||
parse_mode: Optional[str] = UNSET
|
parse_mode: Optional[str] = UNSET
|
||||||
|
|
@ -39,6 +39,8 @@ class SendPhoto(TelegramMethod[Message]):
|
||||||
"""A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*"""
|
"""A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ class SendPoll(TelegramMethod[Message]):
|
||||||
"""Pass :code:`True`, if the poll needs to be immediately closed. This can be useful for poll preview."""
|
"""Pass :code:`True`, if the poll needs to be immediately closed. This can be useful for poll preview."""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class SendSticker(TelegramMethod[Message]):
|
class SendSticker(TelegramMethod[Message]):
|
||||||
"""
|
"""
|
||||||
Use this method to send static .WEBP or `animated <https://telegram.org/blog/animated-stickers>`_ .TGS stickers. On success, the sent :class:`aiogram.types.message.Message` is returned.
|
Use this method to send static .WEBP, `animated <https://telegram.org/blog/animated-stickers>`_ .TGS, or `video <https://telegram.org/blog/video-stickers-better-reactions>`_ .WEBM stickers. On success, the sent :class:`aiogram.types.message.Message` is returned.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#sendsticker
|
Source: https://core.telegram.org/bots/api#sendsticker
|
||||||
"""
|
"""
|
||||||
|
|
@ -28,9 +28,11 @@ class SendSticker(TelegramMethod[Message]):
|
||||||
chat_id: Union[int, str]
|
chat_id: Union[int, str]
|
||||||
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
||||||
sticker: Union[InputFile, str]
|
sticker: Union[InputFile, str]
|
||||||
"""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 file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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 file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ class SendVenue(TelegramMethod[Message]):
|
||||||
"""Google Places type of the venue. (See `supported types <https://developers.google.com/places/web-service/supported_types>`_.)"""
|
"""Google Places type of the venue. (See `supported types <https://developers.google.com/places/web-service/supported_types>`_.)"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class SendVideo(TelegramMethod[Message]):
|
class SendVideo(TelegramMethod[Message]):
|
||||||
"""
|
"""
|
||||||
Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as :class:`aiogram.types.document.Document`). On success, the sent :class:`aiogram.types.message.Message` is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future.
|
Use this method to send video files, Telegram clients support MPEG4 videos (other formats may be sent as :class:`aiogram.types.document.Document`). On success, the sent :class:`aiogram.types.message.Message` is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#sendvideo
|
Source: https://core.telegram.org/bots/api#sendvideo
|
||||||
"""
|
"""
|
||||||
|
|
@ -30,7 +30,7 @@ class SendVideo(TelegramMethod[Message]):
|
||||||
chat_id: Union[int, str]
|
chat_id: Union[int, str]
|
||||||
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
||||||
video: Union[InputFile, str]
|
video: Union[InputFile, str]
|
||||||
"""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. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
duration: Optional[int] = None
|
duration: Optional[int] = None
|
||||||
"""Duration of sent video in seconds"""
|
"""Duration of sent video in seconds"""
|
||||||
width: Optional[int] = None
|
width: Optional[int] = None
|
||||||
|
|
@ -38,7 +38,7 @@ class SendVideo(TelegramMethod[Message]):
|
||||||
height: Optional[int] = None
|
height: Optional[int] = None
|
||||||
"""Video height"""
|
"""Video height"""
|
||||||
thumb: Optional[Union[InputFile, str]] = None
|
thumb: Optional[Union[InputFile, str]] = None
|
||||||
"""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>. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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>. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
caption: Optional[str] = None
|
caption: Optional[str] = None
|
||||||
"""Video caption (may also be used when resending videos by *file_id*), 0-1024 characters after entities parsing"""
|
"""Video caption (may also be used when resending videos by *file_id*), 0-1024 characters after entities parsing"""
|
||||||
parse_mode: Optional[str] = UNSET
|
parse_mode: Optional[str] = UNSET
|
||||||
|
|
@ -49,6 +49,8 @@ class SendVideo(TelegramMethod[Message]):
|
||||||
"""Pass :code:`True`, if the uploaded video is suitable for streaming"""
|
"""Pass :code:`True`, if the uploaded video is suitable for streaming"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class SendVideoNote(TelegramMethod[Message]):
|
class SendVideoNote(TelegramMethod[Message]):
|
||||||
"""
|
"""
|
||||||
As of `v.4.0 <https://telegram.org/blog/video-messages-and-telescope>`_, Telegram clients support rounded square mp4 videos of up to 1 minute long. Use this method to send video messages. On success, the sent :class:`aiogram.types.message.Message` is returned.
|
As of `v.4.0 <https://telegram.org/blog/video-messages-and-telescope>`_, Telegram clients support rounded square MPEG4 videos of up to 1 minute long. Use this method to send video messages. On success, the sent :class:`aiogram.types.message.Message` is returned.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#sendvideonote
|
Source: https://core.telegram.org/bots/api#sendvideonote
|
||||||
"""
|
"""
|
||||||
|
|
@ -28,15 +28,17 @@ class SendVideoNote(TelegramMethod[Message]):
|
||||||
chat_id: Union[int, str]
|
chat_id: Union[int, str]
|
||||||
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
||||||
video_note: Union[InputFile, str]
|
video_note: Union[InputFile, str]
|
||||||
"""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. :ref:`More info on Sending Files » <sending-files>`. Sending video notes by a URL is currently unsupported"""
|
"""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. :ref:`More information on Sending Files » <sending-files>`. Sending video notes by a URL is currently unsupported"""
|
||||||
duration: Optional[int] = None
|
duration: Optional[int] = None
|
||||||
"""Duration of sent video in seconds"""
|
"""Duration of sent video in seconds"""
|
||||||
length: Optional[int] = None
|
length: Optional[int] = None
|
||||||
"""Video width and height, i.e. diameter of the video message"""
|
"""Video width and height, i.e. diameter of the video message"""
|
||||||
thumb: Optional[Union[InputFile, str]] = None
|
thumb: Optional[Union[InputFile, str]] = None
|
||||||
"""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>. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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>. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class SendVoice(TelegramMethod[Message]):
|
||||||
chat_id: Union[int, str]
|
chat_id: Union[int, str]
|
||||||
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
"""Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)"""
|
||||||
voice: Union[InputFile, str]
|
voice: Union[InputFile, str]
|
||||||
"""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. :ref:`More info on Sending Files » <sending-files>`"""
|
"""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. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
caption: Optional[str] = None
|
caption: Optional[str] = None
|
||||||
"""Voice message caption, 0-1024 characters after entities parsing"""
|
"""Voice message caption, 0-1024 characters after entities parsing"""
|
||||||
parse_mode: Optional[str] = UNSET
|
parse_mode: Optional[str] = UNSET
|
||||||
|
|
@ -41,6 +41,8 @@ class SendVoice(TelegramMethod[Message]):
|
||||||
"""Duration of the voice message in seconds"""
|
"""Duration of the voice message in seconds"""
|
||||||
disable_notification: Optional[bool] = None
|
disable_notification: Optional[bool] = None
|
||||||
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
"""Sends the message `silently <https://telegram.org/blog/channels-2-0#silent-messages>`_. Users will receive a notification with no sound."""
|
||||||
|
protect_content: Optional[bool] = None
|
||||||
|
"""Protects the contents of the sent message from forwarding and saving"""
|
||||||
reply_to_message_id: Optional[int] = None
|
reply_to_message_id: Optional[int] = None
|
||||||
"""If the message is a reply, ID of the original message"""
|
"""If the message is a reply, ID of the original message"""
|
||||||
allow_sending_without_reply: Optional[bool] = None
|
allow_sending_without_reply: Optional[bool] = None
|
||||||
|
|
|
||||||
29
aiogram/methods/set_chat_menu_button.py
Normal file
29
aiogram/methods/set_chat_menu_button.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||||
|
|
||||||
|
from ..types import MenuButton
|
||||||
|
from .base import Request, TelegramMethod
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..client.bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class SetChatMenuButton(TelegramMethod[bool]):
|
||||||
|
"""
|
||||||
|
Use this method to change the bot's menu button in a private chat, or the default menu button. Returns :code:`True` on success.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#setchatmenubutton
|
||||||
|
"""
|
||||||
|
|
||||||
|
__returning__ = bool
|
||||||
|
|
||||||
|
chat_id: Optional[int] = None
|
||||||
|
"""Unique identifier for the target private chat. If not specified, default bot's menu button will be changed"""
|
||||||
|
menu_button: Optional[MenuButton] = None
|
||||||
|
"""A JSON-serialized object for the bot's new menu button. Defaults to :class:`aiogram.types.menu_button_default.MenuButtonDefault`"""
|
||||||
|
|
||||||
|
def build_request(self, bot: Bot) -> Request:
|
||||||
|
data: Dict[str, Any] = self.dict()
|
||||||
|
|
||||||
|
return Request(method="setChatMenuButton", data=data)
|
||||||
29
aiogram/methods/set_my_default_administrator_rights.py
Normal file
29
aiogram/methods/set_my_default_administrator_rights.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||||
|
|
||||||
|
from ..types import ChatAdministratorRights
|
||||||
|
from .base import Request, TelegramMethod
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..client.bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class SetMyDefaultAdministratorRights(TelegramMethod[bool]):
|
||||||
|
"""
|
||||||
|
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 are free to modify the list before adding the bot. Returns :code:`True` on success.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#setmydefaultadministratorrights
|
||||||
|
"""
|
||||||
|
|
||||||
|
__returning__ = bool
|
||||||
|
|
||||||
|
rights: Optional[ChatAdministratorRights] = None
|
||||||
|
"""A JSON-serialized object describing new default administrator rights. If not specified, the default administrator rights will be cleared."""
|
||||||
|
for_channels: Optional[bool] = None
|
||||||
|
"""Pass :code:`True` to change the default administrator rights of the bot in channels. Otherwise, the default administrator rights of the bot for groups and supergroups will be changed."""
|
||||||
|
|
||||||
|
def build_request(self, bot: Bot) -> Request:
|
||||||
|
data: Dict[str, Any] = self.dict()
|
||||||
|
|
||||||
|
return Request(method="setMyDefaultAdministratorRights", data=data)
|
||||||
|
|
@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class SetStickerSetThumb(TelegramMethod[bool]):
|
class SetStickerSetThumb(TelegramMethod[bool]):
|
||||||
"""
|
"""
|
||||||
Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Returns :code:`True` on success.
|
Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Video thumbnails can be set only for video sticker sets only. Returns :code:`True` on success.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#setstickersetthumb
|
Source: https://core.telegram.org/bots/api#setstickersetthumb
|
||||||
"""
|
"""
|
||||||
|
|
@ -23,7 +23,7 @@ class SetStickerSetThumb(TelegramMethod[bool]):
|
||||||
user_id: int
|
user_id: int
|
||||||
"""User identifier of the sticker set owner"""
|
"""User identifier of the sticker set owner"""
|
||||||
thumb: Optional[Union[InputFile, str]] = None
|
thumb: Optional[Union[InputFile, str]] = None
|
||||||
"""A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/animated_stickers#technical-requirements <https://core.telegram.org/animated_stickers#technical-requirements>`_`https://core.telegram.org/animated_stickers#technical-requirements <https://core.telegram.org/animated_stickers#technical-requirements>`_ for animated sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » <sending-files>`. Animated sticker set thumbnail can't be uploaded via HTTP URL."""
|
"""A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#animated-sticker-requirements <https://core.telegram.org/stickers#animated-sticker-requirements>`_`https://core.telegram.org/stickers#animated-sticker-requirements <https://core.telegram.org/stickers#animated-sticker-requirements>`_ for animated sticker technical requirements, or a **WEBM** video with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#video-sticker-requirements <https://core.telegram.org/stickers#video-sticker-requirements>`_`https://core.telegram.org/stickers#video-sticker-requirements <https://core.telegram.org/stickers#video-sticker-requirements>`_ for video sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files » <sending-files>`. Animated sticker set thumbnails can't be uploaded via HTTP URL."""
|
||||||
|
|
||||||
def build_request(self, bot: Bot) -> Request:
|
def build_request(self, bot: Bot) -> Request:
|
||||||
data: Dict[str, Any] = self.dict(exclude={"thumb"})
|
data: Dict[str, Any] = self.dict(exclude={"thumb"})
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class SetWebhook(TelegramMethod[bool]):
|
class SetWebhook(TelegramMethod[bool]):
|
||||||
"""
|
"""
|
||||||
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 :class:`aiogram.types.update.Update`. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns :code:`True` on success.
|
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 :class:`aiogram.types.update.Update`. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns :code:`True` on success.
|
||||||
If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the URL, e.g. :code:`https://www.example.com/<token>`. Since nobody else knows your bot's token, you can be pretty sure it's us.
|
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.
|
||||||
|
|
||||||
**Notes**
|
**Notes**
|
||||||
|
|
||||||
|
|
@ -20,8 +20,8 @@ class SetWebhook(TelegramMethod[bool]):
|
||||||
|
|
||||||
**2.** To use a self-signed certificate, you need to upload your `public key certificate <https://core.telegram.org/bots/self-signed>`_ using *certificate* parameter. Please upload as InputFile, sending a String will not work.
|
**2.** To use a self-signed certificate, you need to upload your `public key certificate <https://core.telegram.org/bots/self-signed>`_ using *certificate* parameter. Please upload as InputFile, sending a String will not work.
|
||||||
|
|
||||||
**3.** Ports currently supported *for Webhooks*: **443, 80, 88, 8443**.
|
**3.** Ports currently supported *for webhooks*: **443, 80, 88, 8443**.
|
||||||
**NEW!** If you're having any trouble setting up webhooks, please check out this `amazing guide to Webhooks <https://core.telegram.org/bots/webhooks>`_.
|
If you're having any trouble setting up webhooks, please check out this `amazing guide to webhooks <https://core.telegram.org/bots/webhooks>`_.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#setwebhook
|
Source: https://core.telegram.org/bots/api#setwebhook
|
||||||
"""
|
"""
|
||||||
|
|
@ -29,17 +29,19 @@ class SetWebhook(TelegramMethod[bool]):
|
||||||
__returning__ = bool
|
__returning__ = bool
|
||||||
|
|
||||||
url: str
|
url: str
|
||||||
"""HTTPS url to send updates to. Use an empty string to remove webhook integration"""
|
"""HTTPS URL to send updates to. Use an empty string to remove webhook integration"""
|
||||||
certificate: Optional[InputFile] = None
|
certificate: Optional[InputFile] = None
|
||||||
"""Upload your public key certificate so that the root certificate in use can be checked. See our `self-signed guide <https://core.telegram.org/bots/self-signed>`_ for details."""
|
"""Upload your public key certificate so that the root certificate in use can be checked. See our `self-signed guide <https://core.telegram.org/bots/self-signed>`_ for details."""
|
||||||
ip_address: Optional[str] = None
|
ip_address: Optional[str] = None
|
||||||
"""The fixed IP address which will be used to send webhook requests instead of the IP address resolved through DNS"""
|
"""The fixed IP address which will be used to send webhook requests instead of the IP address resolved through DNS"""
|
||||||
max_connections: Optional[int] = None
|
max_connections: Optional[int] = None
|
||||||
"""Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to *40*. Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput."""
|
"""The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to *40*. Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput."""
|
||||||
allowed_updates: Optional[List[str]] = None
|
allowed_updates: Optional[List[str]] = None
|
||||||
"""A JSON-serialized list of the update types you want your bot to receive. For example, specify ['message', 'edited_channel_post', 'callback_query'] to only receive updates of these types. See :class:`aiogram.types.update.Update` for a complete list of available update types. Specify an empty list to receive all update types except *chat_member* (default). If not specified, the previous setting will be used."""
|
"""A JSON-serialized list of the update types you want your bot to receive. For example, specify ['message', 'edited_channel_post', 'callback_query'] to only receive updates of these types. See :class:`aiogram.types.update.Update` for a complete list of available update types. Specify an empty list to receive all update types except *chat_member* (default). If not specified, the previous setting will be used."""
|
||||||
drop_pending_updates: Optional[bool] = None
|
drop_pending_updates: Optional[bool] = None
|
||||||
"""Pass :code:`True` to drop all pending updates"""
|
"""Pass :code:`True` to drop all pending updates"""
|
||||||
|
secret_token: Optional[str] = None
|
||||||
|
"""A secret token to be sent in a header 'X-Telegram-Bot-Api-Secret-Token' in every webhook request, 1-256 characters. Only characters :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed. The header is useful to ensure that the request comes from a webhook set by you."""
|
||||||
|
|
||||||
def build_request(self, bot: Bot) -> Request:
|
def build_request(self, bot: Bot) -> Request:
|
||||||
data: Dict[str, Any] = self.dict(exclude={"certificate"})
|
data: Dict[str, Any] = self.dict(exclude={"certificate"})
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class UnbanChatMember(TelegramMethod[bool]):
|
||||||
__returning__ = bool
|
__returning__ = bool
|
||||||
|
|
||||||
chat_id: Union[int, str]
|
chat_id: Union[int, str]
|
||||||
"""Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@username`)"""
|
"""Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@channelusername`)"""
|
||||||
user_id: int
|
user_id: int
|
||||||
"""Unique identifier of the target user"""
|
"""Unique identifier of the target user"""
|
||||||
only_if_banned: Optional[bool] = None
|
only_if_banned: Optional[bool] = None
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class UploadStickerFile(TelegramMethod[File]):
|
||||||
user_id: int
|
user_id: int
|
||||||
"""User identifier of sticker file owner"""
|
"""User identifier of sticker file owner"""
|
||||||
png_sticker: InputFile
|
png_sticker: InputFile
|
||||||
"""**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. :ref:`More info on Sending Files » <sending-files>`"""
|
"""**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. :ref:`More information on Sending Files » <sending-files>`"""
|
||||||
|
|
||||||
def build_request(self, bot: Bot) -> Request:
|
def build_request(self, bot: Bot) -> Request:
|
||||||
data: Dict[str, Any] = self.dict(exclude={"png_sticker"})
|
data: Dict[str, Any] = self.dict(exclude={"png_sticker"})
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from .callback_game import CallbackGame
|
||||||
from .callback_query import CallbackQuery
|
from .callback_query import CallbackQuery
|
||||||
from .chat import Chat
|
from .chat import Chat
|
||||||
from .chat_action import ChatAction
|
from .chat_action import ChatAction
|
||||||
|
from .chat_administrator_rights import ChatAdministratorRights
|
||||||
from .chat_invite_link import ChatInviteLink
|
from .chat_invite_link import ChatInviteLink
|
||||||
from .chat_join_request import ChatJoinRequest
|
from .chat_join_request import ChatJoinRequest
|
||||||
from .chat_location import ChatLocation
|
from .chat_location import ChatLocation
|
||||||
|
|
@ -82,6 +83,10 @@ from .labeled_price import LabeledPrice
|
||||||
from .location import Location
|
from .location import Location
|
||||||
from .login_url import LoginUrl
|
from .login_url import LoginUrl
|
||||||
from .mask_position import MaskPosition
|
from .mask_position import MaskPosition
|
||||||
|
from .menu_button import MenuButton
|
||||||
|
from .menu_button_commands import MenuButtonCommands
|
||||||
|
from .menu_button_default import MenuButtonDefault
|
||||||
|
from .menu_button_web_app import MenuButtonWebApp
|
||||||
from .message import ContentType, Message
|
from .message import ContentType, Message
|
||||||
from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged
|
from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged
|
||||||
from .message_entity import MessageEntity
|
from .message_entity import MessageEntity
|
||||||
|
|
@ -108,6 +113,7 @@ from .proximity_alert_triggered import ProximityAlertTriggered
|
||||||
from .reply_keyboard_markup import ReplyKeyboardMarkup
|
from .reply_keyboard_markup import ReplyKeyboardMarkup
|
||||||
from .reply_keyboard_remove import ReplyKeyboardRemove
|
from .reply_keyboard_remove import ReplyKeyboardRemove
|
||||||
from .response_parameters import ResponseParameters
|
from .response_parameters import ResponseParameters
|
||||||
|
from .sent_web_app_message import SentWebAppMessage
|
||||||
from .shipping_address import ShippingAddress
|
from .shipping_address import ShippingAddress
|
||||||
from .shipping_option import ShippingOption
|
from .shipping_option import ShippingOption
|
||||||
from .shipping_query import ShippingQuery
|
from .shipping_query import ShippingQuery
|
||||||
|
|
@ -119,12 +125,14 @@ from .user import User
|
||||||
from .user_profile_photos import UserProfilePhotos
|
from .user_profile_photos import UserProfilePhotos
|
||||||
from .venue import Venue
|
from .venue import Venue
|
||||||
from .video import Video
|
from .video import Video
|
||||||
|
from .video_chat_ended import VideoChatEnded
|
||||||
|
from .video_chat_participants_invited import VideoChatParticipantsInvited
|
||||||
|
from .video_chat_scheduled import VideoChatScheduled
|
||||||
|
from .video_chat_started import VideoChatStarted
|
||||||
from .video_note import VideoNote
|
from .video_note import VideoNote
|
||||||
from .voice import Voice
|
from .voice import Voice
|
||||||
from .voice_chat_ended import VoiceChatEnded
|
from .web_app_data import WebAppData
|
||||||
from .voice_chat_participants_invited import VoiceChatParticipantsInvited
|
from .web_app_info import WebAppInfo
|
||||||
from .voice_chat_scheduled import VoiceChatScheduled
|
|
||||||
from .voice_chat_started import VoiceChatStarted
|
|
||||||
from .webhook_info import WebhookInfo
|
from .webhook_info import WebhookInfo
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
|
@ -156,14 +164,16 @@ __all__ = (
|
||||||
"Poll",
|
"Poll",
|
||||||
"Location",
|
"Location",
|
||||||
"Venue",
|
"Venue",
|
||||||
|
"WebAppData",
|
||||||
"ProximityAlertTriggered",
|
"ProximityAlertTriggered",
|
||||||
"MessageAutoDeleteTimerChanged",
|
"MessageAutoDeleteTimerChanged",
|
||||||
"VoiceChatScheduled",
|
"VideoChatScheduled",
|
||||||
"VoiceChatStarted",
|
"VideoChatStarted",
|
||||||
"VoiceChatEnded",
|
"VideoChatEnded",
|
||||||
"VoiceChatParticipantsInvited",
|
"VideoChatParticipantsInvited",
|
||||||
"UserProfilePhotos",
|
"UserProfilePhotos",
|
||||||
"File",
|
"File",
|
||||||
|
"WebAppInfo",
|
||||||
"ReplyKeyboardMarkup",
|
"ReplyKeyboardMarkup",
|
||||||
"KeyboardButton",
|
"KeyboardButton",
|
||||||
"KeyboardButtonPollType",
|
"KeyboardButtonPollType",
|
||||||
|
|
@ -175,6 +185,7 @@ __all__ = (
|
||||||
"ForceReply",
|
"ForceReply",
|
||||||
"ChatPhoto",
|
"ChatPhoto",
|
||||||
"ChatInviteLink",
|
"ChatInviteLink",
|
||||||
|
"ChatAdministratorRights",
|
||||||
"ChatMember",
|
"ChatMember",
|
||||||
"ChatMemberOwner",
|
"ChatMemberOwner",
|
||||||
"ChatMemberAdministrator",
|
"ChatMemberAdministrator",
|
||||||
|
|
@ -195,6 +206,10 @@ __all__ = (
|
||||||
"BotCommandScopeChat",
|
"BotCommandScopeChat",
|
||||||
"BotCommandScopeChatAdministrators",
|
"BotCommandScopeChatAdministrators",
|
||||||
"BotCommandScopeChatMember",
|
"BotCommandScopeChatMember",
|
||||||
|
"MenuButton",
|
||||||
|
"MenuButtonCommands",
|
||||||
|
"MenuButtonWebApp",
|
||||||
|
"MenuButtonDefault",
|
||||||
"ResponseParameters",
|
"ResponseParameters",
|
||||||
"InputMedia",
|
"InputMedia",
|
||||||
"InputMediaPhoto",
|
"InputMediaPhoto",
|
||||||
|
|
@ -235,6 +250,7 @@ __all__ = (
|
||||||
"InputContactMessageContent",
|
"InputContactMessageContent",
|
||||||
"InputInvoiceMessageContent",
|
"InputInvoiceMessageContent",
|
||||||
"ChosenInlineResult",
|
"ChosenInlineResult",
|
||||||
|
"SentWebAppMessage",
|
||||||
"LabeledPrice",
|
"LabeledPrice",
|
||||||
"Invoice",
|
"Invoice",
|
||||||
"ShippingAddress",
|
"ShippingAddress",
|
||||||
|
|
|
||||||
|
|
@ -32,4 +32,4 @@ class Animation(TelegramObject):
|
||||||
mime_type: Optional[str] = None
|
mime_type: Optional[str] = None
|
||||||
"""*Optional*. MIME type of the file as defined by sender"""
|
"""*Optional*. MIME type of the file as defined by sender"""
|
||||||
file_size: Optional[int] = None
|
file_size: Optional[int] = None
|
||||||
"""*Optional*. File size in bytes"""
|
"""*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."""
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,6 @@ class Audio(TelegramObject):
|
||||||
mime_type: Optional[str] = None
|
mime_type: Optional[str] = None
|
||||||
"""*Optional*. MIME type of the file as defined by sender"""
|
"""*Optional*. MIME type of the file as defined by sender"""
|
||||||
file_size: Optional[int] = None
|
file_size: Optional[int] = None
|
||||||
"""*Optional*. File size in bytes"""
|
"""*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."""
|
||||||
thumb: Optional[PhotoSize] = None
|
thumb: Optional[PhotoSize] = None
|
||||||
"""*Optional*. Thumbnail of the album cover to which the music file belongs"""
|
"""*Optional*. Thumbnail of the album cover to which the music file belongs"""
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,6 @@ class BotCommand(MutableTelegramObject):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
command: str
|
command: str
|
||||||
"""Text of the command, 1-32 characters. Can contain only lowercase English letters, digits and underscores."""
|
"""Text of the command; 1-32 characters. Can contain only lowercase English letters, digits and underscores."""
|
||||||
description: str
|
description: str
|
||||||
"""Description of the command, 3-256 characters."""
|
"""Description of the command; 1-256 characters."""
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class CallbackQuery(TelegramObject):
|
||||||
inline_message_id: Optional[str] = None
|
inline_message_id: Optional[str] = None
|
||||||
"""*Optional*. Identifier of the message sent via the bot in inline mode, that originated the query."""
|
"""*Optional*. Identifier of the message sent via the bot in inline mode, that originated the query."""
|
||||||
data: Optional[str] = None
|
data: Optional[str] = None
|
||||||
"""*Optional*. Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field."""
|
"""*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: Optional[str] = None
|
game_short_name: Optional[str] = None
|
||||||
"""*Optional*. Short name of a `Game <https://core.telegram.org/bots/api#games>`_ to be returned, serves as the unique identifier for the game"""
|
"""*Optional*. Short name of a `Game <https://core.telegram.org/bots/api#games>`_ to be returned, serves as the unique identifier for the game"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,11 @@ class Chat(TelegramObject):
|
||||||
bio: Optional[str] = None
|
bio: Optional[str] = None
|
||||||
"""*Optional*. Bio of the other party in a private chat. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
"""*Optional*. Bio of the other party in a private chat. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
||||||
has_private_forwards: Optional[bool] = None
|
has_private_forwards: Optional[bool] = None
|
||||||
"""*Optional*. True, if privacy settings of the other party in the private chat allows to use :code:`tg://user?id=<user_id>` links only in chats with the user. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
"""*Optional*. :code:`True`, if privacy settings of the other party in the private chat allows to use :code:`tg://user?id=<user_id>` links only in chats with the user. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
||||||
|
join_to_send_messages: Optional[bool] = None
|
||||||
|
"""*Optional*. :code:`True`, if users need to join the supergroup before they can send messages. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
||||||
|
join_by_request: Optional[bool] = None
|
||||||
|
"""*Optional*. :code:`True`, if all users directly joining the supergroup need to be approved by supergroup administrators. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
"""*Optional*. Description, for groups, supergroups and channel chats. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
"""*Optional*. Description, for groups, supergroups and channel chats. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
||||||
invite_link: Optional[str] = None
|
invite_link: Optional[str] = None
|
||||||
|
|
@ -50,7 +54,7 @@ class Chat(TelegramObject):
|
||||||
message_auto_delete_time: Optional[int] = None
|
message_auto_delete_time: Optional[int] = None
|
||||||
"""*Optional*. The time after which all messages sent to the chat will be automatically deleted; in seconds. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
"""*Optional*. The time after which all messages sent to the chat will be automatically deleted; in seconds. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
||||||
has_protected_content: Optional[bool] = None
|
has_protected_content: Optional[bool] = None
|
||||||
"""*Optional*. True, if messages from the chat can't be forwarded to other chats. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
"""*Optional*. :code:`True`, if messages from the chat can't be forwarded to other chats. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
||||||
sticker_set_name: Optional[str] = None
|
sticker_set_name: Optional[str] = None
|
||||||
"""*Optional*. For supergroups, name of group sticker set. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
"""*Optional*. For supergroups, name of group sticker set. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
|
||||||
can_set_sticker_set: Optional[bool] = None
|
can_set_sticker_set: Optional[bool] = None
|
||||||
|
|
@ -76,6 +80,21 @@ class Chat(TelegramObject):
|
||||||
shift = int(-1 * pow(10, len(short_id) + 2))
|
shift = int(-1 * pow(10, len(short_id) + 2))
|
||||||
return shift - self.id
|
return shift - self.id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self) -> str:
|
||||||
|
"""Get full name of the Chat.
|
||||||
|
|
||||||
|
For private chat it is first_name + last_name.
|
||||||
|
For other chat types it is title.
|
||||||
|
"""
|
||||||
|
if self.title is not None:
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
if self.last_name is not None:
|
||||||
|
return f"{self.first_name} {self.last_name}"
|
||||||
|
|
||||||
|
return f"{self.first_name}"
|
||||||
|
|
||||||
def ban_sender_chat(self, sender_chat_id: int) -> BanChatSenderChat:
|
def ban_sender_chat(self, sender_chat_id: int) -> BanChatSenderChat:
|
||||||
from ..methods import BanChatSenderChat
|
from ..methods import BanChatSenderChat
|
||||||
|
|
||||||
|
|
|
||||||
39
aiogram/types/chat_administrator_rights.py
Normal file
39
aiogram/types/chat_administrator_rights.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from .base import TelegramObject
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ChatAdministratorRights(TelegramObject):
|
||||||
|
"""
|
||||||
|
Represents the rights of an administrator in a chat.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#chatadministratorrights
|
||||||
|
"""
|
||||||
|
|
||||||
|
is_anonymous: bool
|
||||||
|
""":code:`True`, if the user's presence in the chat is hidden"""
|
||||||
|
can_manage_chat: bool
|
||||||
|
""":code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege"""
|
||||||
|
can_delete_messages: bool
|
||||||
|
""":code:`True`, if the administrator can delete messages of other users"""
|
||||||
|
can_manage_video_chats: bool
|
||||||
|
""":code:`True`, if the administrator can manage video chats"""
|
||||||
|
can_restrict_members: bool
|
||||||
|
""":code:`True`, if the administrator can restrict, ban or unban chat members"""
|
||||||
|
can_promote_members: bool
|
||||||
|
""":code:`True`, if the administrator can add new administrators with a subset of their own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by the user)"""
|
||||||
|
can_change_info: bool
|
||||||
|
""":code:`True`, if the user is allowed to change the chat title, photo and other settings"""
|
||||||
|
can_invite_users: bool
|
||||||
|
""":code:`True`, if the user is allowed to invite new users to the chat"""
|
||||||
|
can_post_messages: Optional[bool] = None
|
||||||
|
"""*Optional*. :code:`True`, if the administrator can post in the channel; channels only"""
|
||||||
|
can_edit_messages: Optional[bool] = None
|
||||||
|
"""*Optional*. :code:`True`, if the administrator can edit messages of other users and can pin messages; channels only"""
|
||||||
|
can_pin_messages: Optional[bool] = None
|
||||||
|
"""*Optional*. :code:`True`, if the user is allowed to pin messages; groups and supergroups only"""
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue