From bdae5fb026ab3b6a0f97e4d6aed3b9b7ebd85e06 Mon Sep 17 00:00:00 2001 From: jrootjunior Date: Fri, 15 Nov 2019 12:17:57 +0200 Subject: [PATCH] Clean project --- .flake8 | 2 +- .gitignore | 69 +- .readthedocs.yml | 19 - CODE_OF_CONDUCT.md | 46 - Makefile | 77 +- README.md | 57 - aiogram/__init__.py | 42 +- aiogram/__main__.py | 83 - aiogram/api/client/bot.py | 110 +- aiogram/api/methods/add_sticker_to_set.py | 4 +- aiogram/api/methods/create_new_sticker_set.py | 4 +- aiogram/api/methods/send_animation.py | 4 +- aiogram/api/methods/send_audio.py | 4 +- aiogram/api/methods/send_document.py | 4 +- aiogram/api/methods/send_media_group.py | 2 +- aiogram/api/methods/send_photo.py | 4 +- aiogram/api/methods/send_sticker.py | 4 +- aiogram/api/methods/send_video.py | 4 +- aiogram/api/methods/send_video_note.py | 4 +- aiogram/api/methods/send_voice.py | 4 +- aiogram/api/methods/set_chat_photo.py | 4 +- aiogram/api/methods/set_webhook.py | 4 +- aiogram/api/methods/upload_sticker_file.py | 4 +- aiogram/api/session/aiohttp.py | 4 +- aiogram/api/session/base.py | 2 +- aiogram/bot/__init__.py | 5 - aiogram/bot/api.py | 264 -- aiogram/bot/base.py | 314 --- aiogram/bot/bot.py | 2166 ---------------- aiogram/contrib/__init__.py | 0 aiogram/contrib/fsm_storage/__init__.py | 0 aiogram/contrib/fsm_storage/files.py | 59 - aiogram/contrib/fsm_storage/memory.py | 131 - aiogram/contrib/fsm_storage/mongo.py | 200 -- aiogram/contrib/fsm_storage/redis.py | 405 --- aiogram/contrib/fsm_storage/rethinkdb.py | 242 -- aiogram/contrib/middlewares/__init__.py | 0 aiogram/contrib/middlewares/environment.py | 21 - aiogram/contrib/middlewares/fsm.py | 80 - aiogram/contrib/middlewares/i18n.py | 150 -- aiogram/contrib/middlewares/logging.py | 427 ---- aiogram/dispatcher/__init__.py | 17 - aiogram/dispatcher/dispatcher.py | 1133 --------- aiogram/dispatcher/filters/__init__.py | 34 - aiogram/dispatcher/filters/builtin.py | 646 ----- aiogram/dispatcher/filters/factory.py | 80 - aiogram/dispatcher/filters/filters.py | 289 --- aiogram/dispatcher/filters/state.py | 197 -- aiogram/dispatcher/handler.py | 139 -- aiogram/dispatcher/middlewares.py | 130 - aiogram/dispatcher/storage.py | 562 ----- aiogram/dispatcher/webhook.py | 2181 ----------------- aiogram/types/__init__.py | 206 -- aiogram/types/animation.py | 20 - aiogram/types/audio.py | 20 - aiogram/types/auth_widget_data.py | 49 - aiogram/types/base.py | 288 --- aiogram/types/callback_game.py | 11 - aiogram/types/callback_query.py | 70 - aiogram/types/chat.py | 637 ----- aiogram/types/chat_member.py | 65 - aiogram/types/chat_permissions.py | 39 - aiogram/types/chat_photo.py | 93 - aiogram/types/chosen_inline_result.py | 23 - aiogram/types/contact.py | 26 - aiogram/types/document.py | 18 - aiogram/types/encrypted_credentials.py | 16 - aiogram/types/encrypted_passport_element.py | 22 - aiogram/types/fields.py | 201 -- aiogram/types/file.py | 22 - aiogram/types/force_reply.py | 37 - aiogram/types/game.py | 24 - aiogram/types/game_high_score.py | 17 - aiogram/types/inline_keyboard.py | 127 - aiogram/types/inline_query.py | 71 - aiogram/types/inline_query_result.py | 939 ------- aiogram/types/input_file.py | 219 -- aiogram/types/input_media.py | 364 --- aiogram/types/input_message_content.py | 125 - aiogram/types/invoice.py | 16 - aiogram/types/labeled_price.py | 16 - aiogram/types/location.py | 13 - aiogram/types/login_url.py | 33 - aiogram/types/mask_position.py | 15 - aiogram/types/message.py | 1801 -------------- aiogram/types/message_entity.py | 109 - aiogram/types/mixins.py | 69 - aiogram/types/order_info.py | 16 - aiogram/types/passport_data.py | 17 - aiogram/types/passport_element_error.py | 135 - aiogram/types/passport_file.py | 15 - aiogram/types/photo_size.py | 16 - aiogram/types/poll.py | 16 - aiogram/types/pre_checkout_query.py | 35 - aiogram/types/reply_keyboard.py | 126 - aiogram/types/response_parameters.py | 13 - aiogram/types/shipping_address.py | 17 - aiogram/types/shipping_option.py | 35 - aiogram/types/shipping_query.py | 25 - aiogram/types/sticker.py | 23 - aiogram/types/sticker_set.py | 19 - aiogram/types/successful_payment.py | 19 - aiogram/types/update.py | 62 - aiogram/types/user.py | 84 - aiogram/types/user_profile_photos.py | 16 - aiogram/types/venue.py | 17 - aiogram/types/video.py | 20 - aiogram/types/video_note.py | 18 - aiogram/types/voice.py | 16 - aiogram/types/webhook_info.py | 20 - aiogram/utils/__init__.py | 0 aiogram/utils/auth_widget.py | 69 - aiogram/utils/callback_data.py | 136 - aiogram/utils/deprecated.py | 43 +- aiogram/utils/emoji.py | 12 - aiogram/utils/exceptions.py | 199 +- aiogram/utils/executor.py | 384 --- aiogram/utils/helper.py | 39 +- aiogram/utils/json.py | 47 - aiogram/utils/mixins.py | 16 +- aiogram/utils/parts.py | 59 - aiogram/utils/payload.py | 83 - dev_requirements.txt | 18 - docs/Makefile | 20 - .../{source/static => assets/images}/logo.png | Bin docs/index.md | 13 + docs/install.md | 14 + docs/source/conf.py | 177 -- docs/source/contribution.rst | 4 - docs/source/dispatcher/filters.rst | 183 -- docs/source/dispatcher/fsm.rst | 45 - docs/source/dispatcher/index.rst | 33 - docs/source/dispatcher/middleware.rst | 15 - docs/source/dispatcher/webhook.rst | 16 - .../examples/advanced_executor_example.rst | 28 - docs/source/examples/broadcast_example.rst | 10 - docs/source/examples/check_user_language.rst | 13 - docs/source/examples/echo_bot.rst | 9 - .../examples/finite_state_machine_example.rst | 10 - docs/source/examples/i18n_example.rst | 28 - docs/source/examples/index.rst | 21 - docs/source/examples/inline_bot.rst | 10 - docs/source/examples/media_group.rst | 10 - .../examples/middleware_and_antiflood.rst | 10 - docs/source/examples/payments.rst | 10 - docs/source/examples/proxy_and_emojize.rst | 10 - .../regexp_commands_filter_example.rst | 10 - docs/source/examples/throtling_example.rst | 14 - docs/source/examples/webhook_example.rst | 13 - docs/source/examples/webhook_example_2.rst | 10 - docs/source/index.rst | 93 - docs/source/install.rst | 97 - docs/source/links.rst | 4 - docs/source/migration_1_to_2.rst | 260 -- docs/source/quick_start.rst | 45 - docs/source/telegram/bot.rst | 25 - docs/source/telegram/index.rst | 7 - docs/source/telegram/types/animation.rst | 11 - docs/source/telegram/types/audio.rst | 11 - .../telegram/types/auth_widget_data.rst | 11 - docs/source/telegram/types/base.rst | 19 - docs/source/telegram/types/callback_game.rst | 11 - docs/source/telegram/types/callback_query.rst | 11 - docs/source/telegram/types/chat.rst | 27 - docs/source/telegram/types/chat_member.rst | 19 - docs/source/telegram/types/chat_photo.rst | 11 - .../telegram/types/chosen_inline_result.rst | 11 - docs/source/telegram/types/contact.rst | 11 - docs/source/telegram/types/document.rst | 11 - .../telegram/types/encrypted_credentials.rst | 11 - .../types/encrypted_passport_element.rst | 11 - docs/source/telegram/types/fields.rst | 53 - docs/source/telegram/types/file.rst | 9 - docs/source/telegram/types/force_reply.rst | 11 - docs/source/telegram/types/game.rst | 11 - .../source/telegram/types/game_high_score.rst | 11 - docs/source/telegram/types/index.rst | 70 - .../source/telegram/types/inline_keyboard.rst | 19 - docs/source/telegram/types/inline_query.rst | 11 - .../telegram/types/inline_query_result.rst | 171 -- docs/source/telegram/types/input_file.rst | 11 - docs/source/telegram/types/input_media.rst | 59 - .../telegram/types/input_message_content.rst | 43 - docs/source/telegram/types/invoice.rst | 11 - docs/source/telegram/types/labeled_price.rst | 11 - docs/source/telegram/types/location.rst | 11 - docs/source/telegram/types/mask_position.rst | 11 - docs/source/telegram/types/message.rst | 35 - docs/source/telegram/types/message_entity.rst | 19 - docs/source/telegram/types/mixins.rst | 12 - docs/source/telegram/types/order_info.rst | 11 - docs/source/telegram/types/passport_data.rst | 11 - .../telegram/types/passport_element_error.rst | 59 - docs/source/telegram/types/passport_file.rst | 11 - docs/source/telegram/types/photo_size.rst | 11 - .../telegram/types/pre_checkout_query.rst | 11 - docs/source/telegram/types/reply_keyboard.rst | 27 - .../telegram/types/response_parameters.rst | 11 - .../telegram/types/shipping_address.rst | 11 - .../source/telegram/types/shipping_option.rst | 11 - docs/source/telegram/types/shipping_query.rst | 11 - docs/source/telegram/types/sticker.rst | 11 - docs/source/telegram/types/sticker_set.rst | 11 - .../telegram/types/successful_payment.rst | 11 - docs/source/telegram/types/update.rst | 19 - docs/source/telegram/types/user.rst | 11 - .../telegram/types/user_profile_photos.rst | 11 - docs/source/telegram/types/venue.rst | 11 - docs/source/telegram/types/video.rst | 11 - docs/source/telegram/types/video_note.rst | 11 - docs/source/telegram/types/voice.rst | 11 - docs/source/telegram/types/webhook_info.rst | 11 - docs/source/utils/auth_widget.rst | 6 - docs/source/utils/deprecated.rst | 6 - docs/source/utils/emoji.rst | 6 - docs/source/utils/exceptions.rst | 6 - docs/source/utils/executor.rst | 7 - docs/source/utils/helper.rst | 6 - docs/source/utils/index.rst | 15 - docs/source/utils/json.rst | 6 - docs/source/utils/markdown.rst | 6 - docs/source/utils/parts.rst | 6 - docs/source/utils/payload.rst | 6 - environment.yml | 36 - generator/__init__.py | 0 generator/__main__.py | 7 - generator/cli.py | 22 - generator/consts.py | 32 - generator/generator.py | 34 - generator/normalizers.py | 92 - generator/parser.py | 134 - generator/structures.py | 91 - generator/templates/type.py.jinja2 | 12 - generator/templates/types.py.jinja2 | 20 - generator/templates/types_group.py.jinja2 | 10 - mkdocs.yml | 28 + poetry.lock | 585 +++++ requirements.txt | 3 - setup.py | 85 - tests/test_bot.py | 247 +- tests/test_bot/test_api.py | 8 +- tests/test_dispatcher.py | 2 +- .../test_filters/test_builtin.py | 18 +- .../test_filters/test_state.py | 3 +- tests/test_filters.py | 221 +- tests/test_message.py | 3 +- tests/test_utils/test_auth_widget.py | 27 +- tests/test_utils/test_helper.py | 7 +- tests/types/dataset.py | 92 +- tests/types/test_animation.py | 1 + tests/types/test_chat.py | 1 + tests/types/test_chat_member.py | 29 +- tests/types/test_document.py | 1 + tests/types/test_game.py | 1 + tests/types/test_message.py | 1 + tests/types/test_photo.py | 1 + tests/types/test_update.py | 1 + tests/types/test_user.py | 1 + tox.ini | 7 - 259 files changed, 1303 insertions(+), 21135 deletions(-) delete mode 100644 .readthedocs.yml delete mode 100644 CODE_OF_CONDUCT.md delete mode 100644 README.md delete mode 100644 aiogram/__main__.py delete mode 100644 aiogram/bot/__init__.py delete mode 100644 aiogram/bot/api.py delete mode 100644 aiogram/bot/base.py delete mode 100644 aiogram/bot/bot.py delete mode 100644 aiogram/contrib/__init__.py delete mode 100644 aiogram/contrib/fsm_storage/__init__.py delete mode 100644 aiogram/contrib/fsm_storage/files.py delete mode 100644 aiogram/contrib/fsm_storage/memory.py delete mode 100644 aiogram/contrib/fsm_storage/mongo.py delete mode 100644 aiogram/contrib/fsm_storage/redis.py delete mode 100644 aiogram/contrib/fsm_storage/rethinkdb.py delete mode 100644 aiogram/contrib/middlewares/__init__.py delete mode 100644 aiogram/contrib/middlewares/environment.py delete mode 100644 aiogram/contrib/middlewares/fsm.py delete mode 100644 aiogram/contrib/middlewares/i18n.py delete mode 100644 aiogram/contrib/middlewares/logging.py delete mode 100644 aiogram/dispatcher/__init__.py delete mode 100644 aiogram/dispatcher/dispatcher.py delete mode 100644 aiogram/dispatcher/filters/__init__.py delete mode 100644 aiogram/dispatcher/filters/builtin.py delete mode 100644 aiogram/dispatcher/filters/factory.py delete mode 100644 aiogram/dispatcher/filters/filters.py delete mode 100644 aiogram/dispatcher/filters/state.py delete mode 100644 aiogram/dispatcher/handler.py delete mode 100644 aiogram/dispatcher/middlewares.py delete mode 100644 aiogram/dispatcher/storage.py delete mode 100644 aiogram/dispatcher/webhook.py delete mode 100644 aiogram/types/__init__.py delete mode 100644 aiogram/types/animation.py delete mode 100644 aiogram/types/audio.py delete mode 100644 aiogram/types/auth_widget_data.py delete mode 100644 aiogram/types/base.py delete mode 100644 aiogram/types/callback_game.py delete mode 100644 aiogram/types/callback_query.py delete mode 100644 aiogram/types/chat.py delete mode 100644 aiogram/types/chat_member.py delete mode 100644 aiogram/types/chat_permissions.py delete mode 100644 aiogram/types/chat_photo.py delete mode 100644 aiogram/types/chosen_inline_result.py delete mode 100644 aiogram/types/contact.py delete mode 100644 aiogram/types/document.py delete mode 100644 aiogram/types/encrypted_credentials.py delete mode 100644 aiogram/types/encrypted_passport_element.py delete mode 100644 aiogram/types/fields.py delete mode 100644 aiogram/types/file.py delete mode 100644 aiogram/types/force_reply.py delete mode 100644 aiogram/types/game.py delete mode 100644 aiogram/types/game_high_score.py delete mode 100644 aiogram/types/inline_keyboard.py delete mode 100644 aiogram/types/inline_query.py delete mode 100644 aiogram/types/inline_query_result.py delete mode 100644 aiogram/types/input_file.py delete mode 100644 aiogram/types/input_media.py delete mode 100644 aiogram/types/input_message_content.py delete mode 100644 aiogram/types/invoice.py delete mode 100644 aiogram/types/labeled_price.py delete mode 100644 aiogram/types/location.py delete mode 100644 aiogram/types/login_url.py delete mode 100644 aiogram/types/mask_position.py delete mode 100644 aiogram/types/message.py delete mode 100644 aiogram/types/message_entity.py delete mode 100644 aiogram/types/mixins.py delete mode 100644 aiogram/types/order_info.py delete mode 100644 aiogram/types/passport_data.py delete mode 100644 aiogram/types/passport_element_error.py delete mode 100644 aiogram/types/passport_file.py delete mode 100644 aiogram/types/photo_size.py delete mode 100644 aiogram/types/poll.py delete mode 100644 aiogram/types/pre_checkout_query.py delete mode 100644 aiogram/types/reply_keyboard.py delete mode 100644 aiogram/types/response_parameters.py delete mode 100644 aiogram/types/shipping_address.py delete mode 100644 aiogram/types/shipping_option.py delete mode 100644 aiogram/types/shipping_query.py delete mode 100644 aiogram/types/sticker.py delete mode 100644 aiogram/types/sticker_set.py delete mode 100644 aiogram/types/successful_payment.py delete mode 100644 aiogram/types/update.py delete mode 100644 aiogram/types/user.py delete mode 100644 aiogram/types/user_profile_photos.py delete mode 100644 aiogram/types/venue.py delete mode 100644 aiogram/types/video.py delete mode 100644 aiogram/types/video_note.py delete mode 100644 aiogram/types/voice.py delete mode 100644 aiogram/types/webhook_info.py delete mode 100644 aiogram/utils/__init__.py delete mode 100644 aiogram/utils/auth_widget.py delete mode 100644 aiogram/utils/callback_data.py delete mode 100644 aiogram/utils/emoji.py delete mode 100644 aiogram/utils/executor.py delete mode 100644 aiogram/utils/json.py delete mode 100644 aiogram/utils/parts.py delete mode 100644 aiogram/utils/payload.py delete mode 100644 dev_requirements.txt delete mode 100644 docs/Makefile rename docs/{source/static => assets/images}/logo.png (100%) create mode 100644 docs/index.md create mode 100644 docs/install.md delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/contribution.rst delete mode 100644 docs/source/dispatcher/filters.rst delete mode 100644 docs/source/dispatcher/fsm.rst delete mode 100644 docs/source/dispatcher/index.rst delete mode 100644 docs/source/dispatcher/middleware.rst delete mode 100644 docs/source/dispatcher/webhook.rst delete mode 100644 docs/source/examples/advanced_executor_example.rst delete mode 100644 docs/source/examples/broadcast_example.rst delete mode 100644 docs/source/examples/check_user_language.rst delete mode 100644 docs/source/examples/echo_bot.rst delete mode 100644 docs/source/examples/finite_state_machine_example.rst delete mode 100644 docs/source/examples/i18n_example.rst delete mode 100644 docs/source/examples/index.rst delete mode 100644 docs/source/examples/inline_bot.rst delete mode 100644 docs/source/examples/media_group.rst delete mode 100644 docs/source/examples/middleware_and_antiflood.rst delete mode 100644 docs/source/examples/payments.rst delete mode 100644 docs/source/examples/proxy_and_emojize.rst delete mode 100644 docs/source/examples/regexp_commands_filter_example.rst delete mode 100644 docs/source/examples/throtling_example.rst delete mode 100644 docs/source/examples/webhook_example.rst delete mode 100644 docs/source/examples/webhook_example_2.rst delete mode 100644 docs/source/index.rst delete mode 100644 docs/source/install.rst delete mode 100644 docs/source/links.rst delete mode 100644 docs/source/migration_1_to_2.rst delete mode 100644 docs/source/quick_start.rst delete mode 100644 docs/source/telegram/bot.rst delete mode 100644 docs/source/telegram/index.rst delete mode 100644 docs/source/telegram/types/animation.rst delete mode 100644 docs/source/telegram/types/audio.rst delete mode 100644 docs/source/telegram/types/auth_widget_data.rst delete mode 100644 docs/source/telegram/types/base.rst delete mode 100644 docs/source/telegram/types/callback_game.rst delete mode 100644 docs/source/telegram/types/callback_query.rst delete mode 100644 docs/source/telegram/types/chat.rst delete mode 100644 docs/source/telegram/types/chat_member.rst delete mode 100644 docs/source/telegram/types/chat_photo.rst delete mode 100644 docs/source/telegram/types/chosen_inline_result.rst delete mode 100644 docs/source/telegram/types/contact.rst delete mode 100644 docs/source/telegram/types/document.rst delete mode 100644 docs/source/telegram/types/encrypted_credentials.rst delete mode 100644 docs/source/telegram/types/encrypted_passport_element.rst delete mode 100644 docs/source/telegram/types/fields.rst delete mode 100644 docs/source/telegram/types/file.rst delete mode 100644 docs/source/telegram/types/force_reply.rst delete mode 100644 docs/source/telegram/types/game.rst delete mode 100644 docs/source/telegram/types/game_high_score.rst delete mode 100644 docs/source/telegram/types/index.rst delete mode 100644 docs/source/telegram/types/inline_keyboard.rst delete mode 100644 docs/source/telegram/types/inline_query.rst delete mode 100644 docs/source/telegram/types/inline_query_result.rst delete mode 100644 docs/source/telegram/types/input_file.rst delete mode 100644 docs/source/telegram/types/input_media.rst delete mode 100644 docs/source/telegram/types/input_message_content.rst delete mode 100644 docs/source/telegram/types/invoice.rst delete mode 100644 docs/source/telegram/types/labeled_price.rst delete mode 100644 docs/source/telegram/types/location.rst delete mode 100644 docs/source/telegram/types/mask_position.rst delete mode 100644 docs/source/telegram/types/message.rst delete mode 100644 docs/source/telegram/types/message_entity.rst delete mode 100644 docs/source/telegram/types/mixins.rst delete mode 100644 docs/source/telegram/types/order_info.rst delete mode 100644 docs/source/telegram/types/passport_data.rst delete mode 100644 docs/source/telegram/types/passport_element_error.rst delete mode 100644 docs/source/telegram/types/passport_file.rst delete mode 100644 docs/source/telegram/types/photo_size.rst delete mode 100644 docs/source/telegram/types/pre_checkout_query.rst delete mode 100644 docs/source/telegram/types/reply_keyboard.rst delete mode 100644 docs/source/telegram/types/response_parameters.rst delete mode 100644 docs/source/telegram/types/shipping_address.rst delete mode 100644 docs/source/telegram/types/shipping_option.rst delete mode 100644 docs/source/telegram/types/shipping_query.rst delete mode 100644 docs/source/telegram/types/sticker.rst delete mode 100644 docs/source/telegram/types/sticker_set.rst delete mode 100644 docs/source/telegram/types/successful_payment.rst delete mode 100644 docs/source/telegram/types/update.rst delete mode 100644 docs/source/telegram/types/user.rst delete mode 100644 docs/source/telegram/types/user_profile_photos.rst delete mode 100644 docs/source/telegram/types/venue.rst delete mode 100644 docs/source/telegram/types/video.rst delete mode 100644 docs/source/telegram/types/video_note.rst delete mode 100644 docs/source/telegram/types/voice.rst delete mode 100644 docs/source/telegram/types/webhook_info.rst delete mode 100644 docs/source/utils/auth_widget.rst delete mode 100644 docs/source/utils/deprecated.rst delete mode 100644 docs/source/utils/emoji.rst delete mode 100644 docs/source/utils/exceptions.rst delete mode 100644 docs/source/utils/executor.rst delete mode 100644 docs/source/utils/helper.rst delete mode 100644 docs/source/utils/index.rst delete mode 100644 docs/source/utils/json.rst delete mode 100644 docs/source/utils/markdown.rst delete mode 100644 docs/source/utils/parts.rst delete mode 100644 docs/source/utils/payload.rst delete mode 100644 environment.yml delete mode 100644 generator/__init__.py delete mode 100644 generator/__main__.py delete mode 100644 generator/cli.py delete mode 100644 generator/consts.py delete mode 100644 generator/generator.py delete mode 100644 generator/normalizers.py delete mode 100644 generator/parser.py delete mode 100644 generator/structures.py delete mode 100644 generator/templates/type.py.jinja2 delete mode 100644 generator/templates/types.py.jinja2 delete mode 100644 generator/templates/types_group.py.jinja2 create mode 100644 mkdocs.yml create mode 100644 poetry.lock delete mode 100644 requirements.txt delete mode 100755 setup.py delete mode 100644 tox.ini diff --git a/.flake8 b/.flake8 index 5a02cb11..9e19cd03 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,5 @@ [flake8] -max-line-length = 80 +max-line-length = 99 select = C,E,F,W,B,B950 ignore = E501,W503,E203 exclude = diff --git a/.gitignore b/.gitignore index 709e832b..89f863b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,65 +1,12 @@ -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.pytest_cache/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover - -# Sphinx documentation -docs/_build/ - -# virtualenv -.venv -venv/ -ENV/ - -# JetBrains .idea/ -# Current project -experiment.py +__pycache__/ +*.py[cod] -# Doc's -docs/html +env/ +build/ +dist/ +*.egg-info/ +*.egg -# i18n/l10n -*.mo - - -.mypy_cache \ No newline at end of file +.mypy_cache diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index c022e7f7..00000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,19 +0,0 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/source/conf.py - -# Optionally build your docs in additional formats such as PDF and ePub -formats: all - -# Optionally set the version of Python and requirements required to build your docs -python: - version: 3.7 - install: - - requirements: dev_requirements.txt diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 8d67dc53..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jroot.junior@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Makefile b/Makefile index f21ec8ae..2d6c3ffc 100644 --- a/Makefile +++ b/Makefile @@ -1,49 +1,42 @@ -VENV_NAME := venv -PYTHON := $(VENV_NAME)/bin/python -AIOGRAM_VERSION := $(shell $(PYTHON) -c "import aiogram;print(aiogram.__version__)") +.DEFAULT_GOAL := help -RM := rm -rf +python := python3.7 -mkvenv: - virtualenv $(VENV_NAME) - $(PYTHON) -m pip install -r requirements.txt - -clean: - find . -name '*.pyc' -exec $(RM) {} + - find . -name '*.pyo' -exec $(RM) {} + - find . -name '*~' -exec $(RM) {} + - find . -name '__pycache__' -exec $(RM) {} + - $(RM) build/ dist/ docs/build/ .tox/ .cache/ .pytest_cache/ *.egg-info - -tag: - @echo "Add tag: '$(AIOGRAM_VERSION)'" - git tag v$(AIOGRAM_VERSION) - -build: - $(PYTHON) setup.py sdist bdist_wheel - -upload: - twine upload dist/* - -release: - make clean - make test - make build - make tag - @echo "Released aiogram $(AIOGRAM_VERSION)" - -full-release: - make release - make upload +.PHONY: help +help: + @echo "=======================================================================================" + @echo " aiogram build tools " + @echo "=======================================================================================" + @echo "Commands list:" + @echo " install: Install development dependencies" + @echo " isort: Run isort tool" + @echo " black: Run black tool" + @echo " flake8: Run flake8 tool" + @echo " mypy: Run mypy tool" + @echo " lint: Run isort, black, flake8 and mypy tools" + @echo "" + @echo "" +.PHONY: install install: - $(PYTHON) setup.py install + $(python) -m pip install --user -U poetry + poetry install -test: - tox +.PHONY: isort +isort: + poetry run isort -rc aiogram tests -summary: - cloc aiogram/ tests/ examples/ setup.py +.PHONY: black +black: + poetry run black aiogram tests -docs: docs/source/* - cd docs && $(MAKE) html +.PHONY: flake8 +flake8: + poetry run flake8 aiogram tests + +.PHONY: mypy +mypy: + poetry run mypy aiogram tests + +.PHONY: lint +lint: isort black flake8 mypy diff --git a/README.md b/README.md deleted file mode 100644 index 02a9374f..00000000 --- a/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# AIOGram - -[![Financial Contributors on Open Collective](https://opencollective.com/aiogram/all/badge.svg?style=flat-square)](https://opencollective.com/aiogram) -[![\[Telegram\] aiogram live](https://img.shields.io/badge/telegram-aiogram-blue.svg?style=flat-square)](https://t.me/aiogram_live) -[![PyPi Package Version](https://img.shields.io/pypi/v/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) -[![PyPi status](https://img.shields.io/pypi/status/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) -[![Downloads](https://img.shields.io/pypi/dm/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) -[![Supported python versions](https://img.shields.io/pypi/pyversions/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) -[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-4.4-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api) -[![Documentation Status](https://img.shields.io/readthedocs/aiogram?style=flat-square)](http://aiogram.readthedocs.io/en/latest/?badge=latest) -[![Github issues](https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square)](https://github.com/aiogram/aiogram/issues) -[![MIT License](https://img.shields.io/pypi/l/aiogram.svg?style=flat-square)](https://opensource.org/licenses/MIT) - -**aiogram** is a pretty simple and fully asynchronous framework for [Telegram Bot API](https://core.telegram.org/bots/api) written in Python 3.7 with [asyncio](https://docs.python.org/3/library/asyncio.html) and [aiohttp](https://github.com/aio-libs/aiohttp). It helps you to make your bots faster and simpler. - -You can [read the docs here](http://aiogram.readthedocs.io/en/latest/). - - -## Official aiogram resources: - - News: [@aiogram_live](https://t.me/aiogram_live) - - Community: [@aiogram](https://t.me/aiogram) - - Russian community: [@aiogram_ru](https://t.me/aiogram_ru) - - Pip: [aiogram](https://pypi.python.org/pypi/aiogram) - - Docs: [ReadTheDocs](http://aiogram.readthedocs.io) - - Source: [Github repo](https://github.com/aiogram/aiogram) - - Issues/Bug tracker: [Github issues tracker](https://github.com/aiogram/aiogram/issues) - - Test bot: [@aiogram_bot](https://t.me/aiogram_bot) - -## Contributors - -### Code Contributors - -This project exists thanks to all the people who contribute. [[Code of conduct](CODE_OF_CONDUCT.md)]. - - -### Financial Contributors - -Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/aiogram/contribute)] - -#### Individuals - - - -#### Organizations - -Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/aiogram/contribute)] - - - - - - - - - - - diff --git a/aiogram/__init__.py b/aiogram/__init__.py index edea1806..eb1f92ad 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -1,42 +1,14 @@ -import asyncio -import os - -from . import bot -from . import contrib -from . import dispatcher -from . import types -from . import utils -from .bot import Bot -from .dispatcher import Dispatcher -from .dispatcher import filters -from .dispatcher import middlewares -from .utils import exceptions, executor, helper, markdown as md +from .api import methods, session, types +from .api.client.bot import Bot try: import uvloop + + uvloop.install() except ImportError: uvloop = None -else: - if 'DISABLE_UVLOOP' not in os.environ: - asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) -__all__ = [ - 'Bot', - 'Dispatcher', - '__api_version__', - '__version__', - 'bot', - 'contrib', - 'dispatcher', - 'exceptions', - 'executor', - 'filters', - 'helper', - 'md', - 'middlewares', - 'types', - 'utils' -] +__all__ = ["__api_version__", "__version__", "types", "methods", "Bot", "session"] -__version__ = '2.4' -__api_version__ = '4.4' +__version__ = "3.0dev.1" +__api_version__ = "4.4" diff --git a/aiogram/__main__.py b/aiogram/__main__.py deleted file mode 100644 index 787f8f9a..00000000 --- a/aiogram/__main__.py +++ /dev/null @@ -1,83 +0,0 @@ -import platform -import sys - -import aiohttp - -import aiogram -from aiogram.utils import json - - -class SysInfo: - @property - def os(self): - return platform.platform() - - @property - def python_implementation(self): - return platform.python_implementation() - - @property - def python(self): - return sys.version.replace("\n", "") - - @property - def aiogram(self): - return aiogram.__version__ - - @property - def api(self): - return aiogram.__api_version__ - - @property - def uvloop(self): - try: - import uvloop - except ImportError: - return - return uvloop.__version__ - - @property - def ujson(self): - try: - import ujson - except ImportError: - return - return ujson.__version__ - - @property - def rapidjson(self): - try: - import rapidjson - except ImportError: - return - return rapidjson.__version__ - - @property - def aiohttp(self): - return aiohttp.__version__ - - def collect(self): - yield f"{self.python_implementation}: {self.python}" - yield f"OS: {self.os}" - yield f"aiogram: {self.aiogram}" - yield f"aiohttp: {self.aiohttp}" - - uvloop = self.uvloop - if uvloop: - yield f"uvloop: {uvloop}" - - yield f"JSON mode: {json.mode}" - - rapidjson = self.rapidjson - if rapidjson: - yield f"rapidjson: {rapidjson}" - ujson = self.ujson - if ujson: - yield f"ujson: {ujson}" - - def __str__(self): - return "\n".join(self.collect()) - - -if __name__ == "__main__": - print(SysInfo()) diff --git a/aiogram/api/client/bot.py b/aiogram/api/client/bot.py index 3c2077ba..5cf50b5b 100644 --- a/aiogram/api/client/bot.py +++ b/aiogram/api/client/bot.py @@ -146,7 +146,7 @@ class Bot(BaseBot): :return: An Array of Update objects is returned. """ call = GetUpdates( - offset=offset, limit=limit, timeout=timeout, allowed_updates=allowed_updates, + offset=offset, limit=limit, timeout=timeout, allowed_updates=allowed_updates ) return await self.emit(call) @@ -1007,7 +1007,7 @@ class Bot(BaseBot): ) return await self.emit(call) - async def send_chat_action(self, chat_id: Union[int, str], action: str,) -> bool: + async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: """ Use this method when you need to tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, @@ -1030,11 +1030,11 @@ class Bot(BaseBot): data, record_video_note or upload_video_note for video notes. :return: Returns True on success. """ - call = SendChatAction(chat_id=chat_id, action=action,) + call = SendChatAction(chat_id=chat_id, action=action) return await self.emit(call) async def get_user_profile_photos( - self, user_id: int, offset: Optional[int] = None, limit: Optional[int] = None, + self, user_id: int, offset: Optional[int] = None, limit: Optional[int] = None ) -> UserProfilePhotos: """ Use this method to get a list of profile pictures for a user. Returns a UserProfilePhotos @@ -1049,10 +1049,10 @@ class Bot(BaseBot): accepted. Defaults to 100. :return: Returns a UserProfilePhotos object. """ - call = GetUserProfilePhotos(user_id=user_id, offset=offset, limit=limit,) + call = GetUserProfilePhotos(user_id=user_id, offset=offset, limit=limit) return await self.emit(call) - async def get_file(self, file_id: str,) -> File: + async def get_file(self, file_id: str) -> 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 File object is @@ -1068,7 +1068,7 @@ class Bot(BaseBot): :param file_id: File identifier to get info about :return: On success, a File object is returned. """ - call = GetFile(file_id=file_id,) + call = GetFile(file_id=file_id) return await self.emit(call) async def kick_chat_member( @@ -1094,10 +1094,10 @@ class Bot(BaseBot): :return: In the case of supergroups and channels, the user will not be able to return to the group on their own using invite links, etc. Returns True on success. """ - call = KickChatMember(chat_id=chat_id, user_id=user_id, until_date=until_date,) + call = KickChatMember(chat_id=chat_id, user_id=user_id, until_date=until_date) return await self.emit(call) - async def unban_chat_member(self, chat_id: Union[int, str], user_id: int,) -> bool: + async def unban_chat_member(self, chat_id: Union[int, str], user_id: int) -> bool: """ Use this method to unban a previously kicked user in a supergroup or channel. The user will not return to the group or channel automatically, but will be able to join via link, @@ -1111,7 +1111,7 @@ class Bot(BaseBot): :return: The user will not return to the group or channel automatically, but will be able to join via link, etc. Returns True on success. """ - call = UnbanChatMember(chat_id=chat_id, user_id=user_id,) + call = UnbanChatMember(chat_id=chat_id, user_id=user_id) return await self.emit(call) async def restrict_chat_member( @@ -1138,7 +1138,7 @@ class Bot(BaseBot): :return: Returns True on success. """ call = RestrictChatMember( - chat_id=chat_id, user_id=user_id, permissions=permissions, until_date=until_date, + chat_id=chat_id, user_id=user_id, permissions=permissions, until_date=until_date ) return await self.emit(call) @@ -1199,7 +1199,7 @@ class Bot(BaseBot): return await self.emit(call) async def set_chat_permissions( - self, chat_id: Union[int, str], permissions: ChatPermissions, + self, chat_id: Union[int, str], permissions: ChatPermissions ) -> bool: """ Use this method to set default chat permissions for all members. The bot must be an @@ -1213,10 +1213,10 @@ class Bot(BaseBot): :param permissions: New default chat permissions :return: Returns True on success. """ - call = SetChatPermissions(chat_id=chat_id, permissions=permissions,) + call = SetChatPermissions(chat_id=chat_id, permissions=permissions) return await self.emit(call) - async def export_chat_invite_link(self, chat_id: Union[int, str],) -> str: + async def export_chat_invite_link(self, chat_id: Union[int, str]) -> str: """ Use this method to generate a new invite link for a chat; any previously generated link is revoked. The bot must be an administrator in the chat for this to work and must have the @@ -1233,10 +1233,10 @@ class Bot(BaseBot): (in the format @channelusername) :return: Returns the new invite link as String on success. """ - call = ExportChatInviteLink(chat_id=chat_id,) + call = ExportChatInviteLink(chat_id=chat_id) return await self.emit(call) - async def set_chat_photo(self, chat_id: Union[int, str], photo: InputFile,) -> bool: + async def set_chat_photo(self, chat_id: Union[int, str], photo: InputFile) -> bool: """ Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have @@ -1249,10 +1249,10 @@ class Bot(BaseBot): :param photo: New chat photo, uploaded using multipart/form-data :return: Returns True on success. """ - call = SetChatPhoto(chat_id=chat_id, photo=photo,) + call = SetChatPhoto(chat_id=chat_id, photo=photo) return await self.emit(call) - async def delete_chat_photo(self, chat_id: Union[int, str],) -> bool: + async def delete_chat_photo(self, chat_id: Union[int, str]) -> bool: """ Use this method to delete a chat photo. Photos can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the appropriate admin @@ -1264,10 +1264,10 @@ class Bot(BaseBot): (in the format @channelusername) :return: Returns True on success. """ - call = DeleteChatPhoto(chat_id=chat_id,) + call = DeleteChatPhoto(chat_id=chat_id) return await self.emit(call) - async def set_chat_title(self, chat_id: Union[int, str], title: str,) -> bool: + async def set_chat_title(self, chat_id: Union[int, str], title: str) -> bool: """ Use this method to change the title of a chat. Titles can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the @@ -1280,11 +1280,11 @@ class Bot(BaseBot): :param title: New chat title, 1-255 characters :return: Returns True on success. """ - call = SetChatTitle(chat_id=chat_id, title=title,) + call = SetChatTitle(chat_id=chat_id, title=title) return await self.emit(call) async def set_chat_description( - self, chat_id: Union[int, str], description: Optional[str] = None, + self, chat_id: Union[int, str], description: Optional[str] = None ) -> bool: """ Use this method to change the description of a group, a supergroup or a channel. The bot @@ -1298,7 +1298,7 @@ class Bot(BaseBot): :param description: New chat description, 0-255 characters :return: Returns True on success. """ - call = SetChatDescription(chat_id=chat_id, description=description,) + call = SetChatDescription(chat_id=chat_id, description=description) return await self.emit(call) async def pin_chat_message( @@ -1324,11 +1324,11 @@ class Bot(BaseBot): :return: Returns True on success. """ call = PinChatMessage( - chat_id=chat_id, message_id=message_id, disable_notification=disable_notification, + chat_id=chat_id, message_id=message_id, disable_notification=disable_notification ) return await self.emit(call) - async def unpin_chat_message(self, chat_id: Union[int, str],) -> bool: + async def unpin_chat_message(self, chat_id: Union[int, str]) -> bool: """ Use this method to unpin a message in a group, a supergroup, or a channel. The bot must be an administrator in the chat for this to work and must have the ‘can_pin_messages’ admin @@ -1341,10 +1341,10 @@ class Bot(BaseBot): (in the format @channelusername) :return: Returns True on success. """ - call = UnpinChatMessage(chat_id=chat_id,) + call = UnpinChatMessage(chat_id=chat_id) return await self.emit(call) - async def leave_chat(self, chat_id: Union[int, str],) -> bool: + async def leave_chat(self, chat_id: Union[int, str]) -> bool: """ Use this method for your bot to leave a group, supergroup or channel. Returns True on success. @@ -1355,10 +1355,10 @@ class Bot(BaseBot): or channel (in the format @channelusername) :return: Returns True on success. """ - call = LeaveChat(chat_id=chat_id,) + call = LeaveChat(chat_id=chat_id) return await self.emit(call) - async def get_chat(self, chat_id: Union[int, str],) -> Chat: + async def get_chat(self, chat_id: Union[int, str]) -> Chat: """ Use this method to get up to date information about the chat (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.). Returns a @@ -1370,10 +1370,10 @@ class Bot(BaseBot): or channel (in the format @channelusername) :return: Returns a Chat object on success. """ - call = GetChat(chat_id=chat_id,) + call = GetChat(chat_id=chat_id) return await self.emit(call) - async def get_chat_administrators(self, chat_id: Union[int, str],) -> List[ChatMember]: + async def get_chat_administrators(self, chat_id: Union[int, str]) -> List[ChatMember]: """ Use this method to get a list of administrators in a chat. On success, returns an Array of ChatMember objects that contains information about all chat administrators except other @@ -1389,10 +1389,10 @@ class Bot(BaseBot): supergroup and no administrators were appointed, only the creator will be returned. """ - call = GetChatAdministrators(chat_id=chat_id,) + call = GetChatAdministrators(chat_id=chat_id) return await self.emit(call) - async def get_chat_members_count(self, chat_id: Union[int, str],) -> int: + async def get_chat_members_count(self, chat_id: Union[int, str]) -> int: """ Use this method to get the number of members in a chat. Returns Int on success. @@ -1402,10 +1402,10 @@ class Bot(BaseBot): or channel (in the format @channelusername) :return: Returns Int on success. """ - call = GetChatMembersCount(chat_id=chat_id,) + call = GetChatMembersCount(chat_id=chat_id) return await self.emit(call) - async def get_chat_member(self, chat_id: Union[int, str], user_id: int,) -> ChatMember: + async def get_chat_member(self, chat_id: Union[int, str], user_id: int) -> ChatMember: """ Use this method to get information about a member of a chat. Returns a ChatMember object on success. @@ -1417,10 +1417,10 @@ class Bot(BaseBot): :param user_id: Unique identifier of the target user :return: Returns a ChatMember object on success. """ - call = GetChatMember(chat_id=chat_id, user_id=user_id,) + call = GetChatMember(chat_id=chat_id, user_id=user_id) return await self.emit(call) - async def set_chat_sticker_set(self, chat_id: Union[int, str], sticker_set_name: str,) -> bool: + async def set_chat_sticker_set(self, chat_id: Union[int, str], sticker_set_name: str) -> bool: """ Use this method to set a new group sticker set for a supergroup. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Use @@ -1435,10 +1435,10 @@ class Bot(BaseBot): :return: Use the field can_set_sticker_set optionally returned in getChat requests to check if the bot can use this method. Returns True on success. """ - call = SetChatStickerSet(chat_id=chat_id, sticker_set_name=sticker_set_name,) + call = SetChatStickerSet(chat_id=chat_id, sticker_set_name=sticker_set_name) return await self.emit(call) - async def delete_chat_sticker_set(self, chat_id: Union[int, str],) -> bool: + async def delete_chat_sticker_set(self, chat_id: Union[int, str]) -> bool: """ Use this method to delete a group sticker set from a supergroup. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Use @@ -1452,7 +1452,7 @@ class Bot(BaseBot): :return: Use the field can_set_sticker_set optionally returned in getChat requests to check if the bot can use this method. Returns True on success. """ - call = DeleteChatStickerSet(chat_id=chat_id,) + call = DeleteChatStickerSet(chat_id=chat_id) return await self.emit(call) async def answer_callback_query( @@ -1671,10 +1671,10 @@ class Bot(BaseBot): :param reply_markup: A JSON-serialized object for a new message inline keyboard. :return: On success, the stopped Poll with the final results is returned. """ - call = StopPoll(chat_id=chat_id, message_id=message_id, reply_markup=reply_markup,) + call = StopPoll(chat_id=chat_id, message_id=message_id, reply_markup=reply_markup) return await self.emit(call) - async def delete_message(self, chat_id: Union[int, str], message_id: int,) -> bool: + async def delete_message(self, chat_id: Union[int, str], message_id: int) -> bool: """ Use this method to delete a message, including service messages, with the following limitations: @@ -1694,7 +1694,7 @@ class Bot(BaseBot): :param message_id: Identifier of the message to delete :return: Returns True on success. """ - call = DeleteMessage(chat_id=chat_id, message_id=message_id,) + call = DeleteMessage(chat_id=chat_id, message_id=message_id) return await self.emit(call) # ============================================================================================= @@ -1741,7 +1741,7 @@ class Bot(BaseBot): ) return await self.emit(call) - async def get_sticker_set(self, name: str,) -> StickerSet: + async def get_sticker_set(self, name: str) -> StickerSet: """ Use this method to get a sticker set. On success, a StickerSet object is returned. @@ -1750,10 +1750,10 @@ class Bot(BaseBot): :param name: Name of the sticker set :return: On success, a StickerSet object is returned. """ - call = GetStickerSet(name=name,) + call = GetStickerSet(name=name) return await self.emit(call) - async def upload_sticker_file(self, user_id: int, png_sticker: InputFile,) -> File: + async def upload_sticker_file(self, user_id: int, png_sticker: InputFile) -> File: """ Use this method to upload a .png file with a sticker for later use in createNewStickerSet and addStickerToSet methods (can be used multiple times). Returns the uploaded File on @@ -1767,7 +1767,7 @@ class Bot(BaseBot): exactly 512px. :return: Returns the uploaded File on success. """ - call = UploadStickerFile(user_id=user_id, png_sticker=png_sticker,) + call = UploadStickerFile(user_id=user_id, png_sticker=png_sticker) return await self.emit(call) async def create_new_sticker_set( @@ -1850,7 +1850,7 @@ class Bot(BaseBot): ) return await self.emit(call) - async def set_sticker_position_in_set(self, sticker: str, position: int,) -> bool: + async def set_sticker_position_in_set(self, sticker: str, position: int) -> bool: """ Use this method to move a sticker in a set created by the bot to a specific position . Returns True on success. @@ -1861,10 +1861,10 @@ class Bot(BaseBot): :param position: New sticker position in the set, zero-based :return: Returns True on success. """ - call = SetStickerPositionInSet(sticker=sticker, position=position,) + call = SetStickerPositionInSet(sticker=sticker, position=position) return await self.emit(call) - async def delete_sticker_from_set(self, sticker: str,) -> bool: + async def delete_sticker_from_set(self, sticker: str) -> bool: """ Use this method to delete a sticker from a set created by the bot. Returns True on success. @@ -1874,7 +1874,7 @@ class Bot(BaseBot): :param sticker: File identifier of the sticker :return: Returns True on success. """ - call = DeleteStickerFromSet(sticker=sticker,) + call = DeleteStickerFromSet(sticker=sticker) return await self.emit(call) # ============================================================================================= @@ -2066,7 +2066,7 @@ class Bot(BaseBot): return await self.emit(call) async def answer_pre_checkout_query( - self, pre_checkout_query_id: str, ok: bool, error_message: Optional[str] = None, + self, pre_checkout_query_id: str, ok: bool, error_message: Optional[str] = None ) -> bool: """ Once the user has confirmed their payment and shipping details, the Bot API sends the @@ -2088,7 +2088,7 @@ class Bot(BaseBot): :return: On success, True is returned. """ call = AnswerPreCheckoutQuery( - pre_checkout_query_id=pre_checkout_query_id, ok=ok, error_message=error_message, + pre_checkout_query_id=pre_checkout_query_id, ok=ok, error_message=error_message ) return await self.emit(call) @@ -2098,7 +2098,7 @@ class Bot(BaseBot): # ============================================================================================= async def set_passport_data_errors( - self, user_id: int, errors: List[PassportElementError], + self, user_id: int, errors: List[PassportElementError] ) -> bool: """ Informs a user that some of the Telegram Passport elements they provided contains errors. @@ -2118,7 +2118,7 @@ class Bot(BaseBot): fixed (the contents of the field for which you returned the error must change). Returns True on success. """ - call = SetPassportDataErrors(user_id=user_id, errors=errors,) + call = SetPassportDataErrors(user_id=user_id, errors=errors) return await self.emit(call) # ============================================================================================= diff --git a/aiogram/api/methods/add_sticker_to_set.py b/aiogram/api/methods/add_sticker_to_set.py index 6da6060d..6684f70a 100644 --- a/aiogram/api/methods/add_sticker_to_set.py +++ b/aiogram/api/methods/add_sticker_to_set.py @@ -28,9 +28,7 @@ class AddStickerToSet(TelegramMethod[bool]): """A JSON-serialized object for position where the mask should be placed on faces""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"png_sticker",} - ) + data: Dict[str, Any] = self.dict(exclude={"png_sticker"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker) diff --git a/aiogram/api/methods/create_new_sticker_set.py b/aiogram/api/methods/create_new_sticker_set.py index 9545c302..e45a85db 100644 --- a/aiogram/api/methods/create_new_sticker_set.py +++ b/aiogram/api/methods/create_new_sticker_set.py @@ -36,9 +36,7 @@ class CreateNewStickerSet(TelegramMethod[bool]): """A JSON-serialized object for position where the mask should be placed on faces""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"png_sticker",} - ) + data: Dict[str, Any] = self.dict(exclude={"png_sticker"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker) diff --git a/aiogram/api/methods/send_animation.py b/aiogram/api/methods/send_animation.py index a373d818..a71b88f8 100644 --- a/aiogram/api/methods/send_animation.py +++ b/aiogram/api/methods/send_animation.py @@ -58,9 +58,7 @@ class SendAnimation(TelegramMethod[Message]): keyboard, instructions to remove reply keyboard or to force a reply from the user.""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"animation", "thumb",} - ) + data: Dict[str, Any] = self.dict(exclude={"animation", "thumb"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="animation", value=self.animation) diff --git a/aiogram/api/methods/send_audio.py b/aiogram/api/methods/send_audio.py index ad671e0c..e8bb85a1 100644 --- a/aiogram/api/methods/send_audio.py +++ b/aiogram/api/methods/send_audio.py @@ -60,9 +60,7 @@ class SendAudio(TelegramMethod[Message]): keyboard, instructions to remove reply keyboard or to force a reply from the user.""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"audio", "thumb",} - ) + data: Dict[str, Any] = self.dict(exclude={"audio", "thumb"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="audio", value=self.audio) diff --git a/aiogram/api/methods/send_document.py b/aiogram/api/methods/send_document.py index 9fd983fb..f037b324 100644 --- a/aiogram/api/methods/send_document.py +++ b/aiogram/api/methods/send_document.py @@ -52,9 +52,7 @@ class SendDocument(TelegramMethod[Message]): keyboard, instructions to remove reply keyboard or to force a reply from the user.""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"document", "thumb",} - ) + data: Dict[str, Any] = self.dict(exclude={"document", "thumb"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="document", value=self.document) diff --git a/aiogram/api/methods/send_media_group.py b/aiogram/api/methods/send_media_group.py index d07b8cbb..05c3d341 100644 --- a/aiogram/api/methods/send_media_group.py +++ b/aiogram/api/methods/send_media_group.py @@ -1,6 +1,6 @@ from typing import Any, Dict, List, Optional, Union -from ..types import InputMediaPhoto, InputMediaVideo, Message, InputFile +from ..types import InputFile, InputMediaPhoto, InputMediaVideo, Message from .base import Request, TelegramMethod diff --git a/aiogram/api/methods/send_photo.py b/aiogram/api/methods/send_photo.py index c1e3e854..924eecb6 100644 --- a/aiogram/api/methods/send_photo.py +++ b/aiogram/api/methods/send_photo.py @@ -43,9 +43,7 @@ class SendPhoto(TelegramMethod[Message]): keyboard, instructions to remove reply keyboard or to force a reply from the user.""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"photo",} - ) + data: Dict[str, Any] = self.dict(exclude={"photo"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="photo", value=self.photo) diff --git a/aiogram/api/methods/send_sticker.py b/aiogram/api/methods/send_sticker.py index bfab1b53..3b6977a0 100644 --- a/aiogram/api/methods/send_sticker.py +++ b/aiogram/api/methods/send_sticker.py @@ -39,9 +39,7 @@ class SendSticker(TelegramMethod[Message]): keyboard, instructions to remove reply keyboard or to force a reply from the user.""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"sticker",} - ) + data: Dict[str, Any] = self.dict(exclude={"sticker"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="sticker", value=self.sticker) diff --git a/aiogram/api/methods/send_video.py b/aiogram/api/methods/send_video.py index e7cfdf28..e234e7fa 100644 --- a/aiogram/api/methods/send_video.py +++ b/aiogram/api/methods/send_video.py @@ -60,9 +60,7 @@ class SendVideo(TelegramMethod[Message]): keyboard, instructions to remove reply keyboard or to force a reply from the user.""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"video", "thumb",} - ) + data: Dict[str, Any] = self.dict(exclude={"video", "thumb"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="video", value=self.video) diff --git a/aiogram/api/methods/send_video_note.py b/aiogram/api/methods/send_video_note.py index 6cbdb779..75baf397 100644 --- a/aiogram/api/methods/send_video_note.py +++ b/aiogram/api/methods/send_video_note.py @@ -50,9 +50,7 @@ class SendVideoNote(TelegramMethod[Message]): keyboard, instructions to remove reply keyboard or to force a reply from the user.""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"video_note", "thumb",} - ) + data: Dict[str, Any] = self.dict(exclude={"video_note", "thumb"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="video_note", value=self.video_note) diff --git a/aiogram/api/methods/send_voice.py b/aiogram/api/methods/send_voice.py index aacbc196..50a845df 100644 --- a/aiogram/api/methods/send_voice.py +++ b/aiogram/api/methods/send_voice.py @@ -49,9 +49,7 @@ class SendVoice(TelegramMethod[Message]): keyboard, instructions to remove reply keyboard or to force a reply from the user.""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"voice",} - ) + data: Dict[str, Any] = self.dict(exclude={"voice"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="voice", value=self.voice) diff --git a/aiogram/api/methods/set_chat_photo.py b/aiogram/api/methods/set_chat_photo.py index a1d9e1aa..38f32370 100644 --- a/aiogram/api/methods/set_chat_photo.py +++ b/aiogram/api/methods/set_chat_photo.py @@ -22,9 +22,7 @@ class SetChatPhoto(TelegramMethod[bool]): """New chat photo, uploaded using multipart/form-data""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"photo",} - ) + data: Dict[str, Any] = self.dict(exclude={"photo"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="photo", value=self.photo) diff --git a/aiogram/api/methods/set_webhook.py b/aiogram/api/methods/set_webhook.py index c4e1d532..5b5e8ede 100644 --- a/aiogram/api/methods/set_webhook.py +++ b/aiogram/api/methods/set_webhook.py @@ -43,9 +43,7 @@ class SetWebhook(TelegramMethod[bool]): regardless of type (default). If not specified, the previous setting will be used.""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"certificate",} - ) + data: Dict[str, Any] = self.dict(exclude={"certificate"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="certificate", value=self.certificate) diff --git a/aiogram/api/methods/upload_sticker_file.py b/aiogram/api/methods/upload_sticker_file.py index f5cdbafe..3a1a592e 100644 --- a/aiogram/api/methods/upload_sticker_file.py +++ b/aiogram/api/methods/upload_sticker_file.py @@ -21,9 +21,7 @@ class UploadStickerFile(TelegramMethod[File]): 512px, and either width or height must be exactly 512px.""" def build_request(self) -> Request: - data: Dict[str, Any] = self.dict( - exclude={"png_sticker",} - ) + data: Dict[str, Any] = self.dict(exclude={"png_sticker"}) files: Dict[str, InputFile] = {} self.prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker) diff --git a/aiogram/api/session/aiohttp.py b/aiogram/api/session/aiohttp.py index 1f67db97..8b539ded 100644 --- a/aiogram/api/session/aiohttp.py +++ b/aiogram/api/session/aiohttp.py @@ -1,9 +1,9 @@ -from typing import Optional, TypeVar, Callable, cast +from typing import Callable, Optional, TypeVar, cast from aiohttp import ClientSession, FormData -from .base import PRODUCTION, BaseSession, TelegramAPIServer from ..methods import Request, TelegramMethod +from .base import PRODUCTION, BaseSession, TelegramAPIServer T = TypeVar("T") diff --git a/aiogram/api/session/base.py b/aiogram/api/session/base.py index af9f787f..f0e6eefe 100644 --- a/aiogram/api/session/base.py +++ b/aiogram/api/session/base.py @@ -2,7 +2,7 @@ import abc import asyncio import datetime import json -from typing import TypeVar, Union, Any, List, Dict, Optional, Callable +from typing import Any, Callable, Dict, List, Optional, TypeVar, Union from pydantic.dataclasses import dataclass diff --git a/aiogram/bot/__init__.py b/aiogram/bot/__init__.py deleted file mode 100644 index 78d1719d..00000000 --- a/aiogram/bot/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from . import api -from .base import BaseBot -from .bot import Bot - -__all__ = ["BaseBot", "Bot", "api"] diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py deleted file mode 100644 index cf69be5d..00000000 --- a/aiogram/bot/api.py +++ /dev/null @@ -1,264 +0,0 @@ -import logging -import os -from http import HTTPStatus - -import aiohttp - -from .. import types -from ..utils import exceptions -from ..utils import json -from ..utils.helper import Helper, HelperMode, Item - -# Main aiogram logger -log = logging.getLogger("aiogram") - -# API Url's -API_URL = "https://api.telegram.org/bot{token}/{method}" -FILE_URL = "https://api.telegram.org/file/bot{token}/{path}" - - -def check_token(token: str) -> bool: - """ - Validate BOT token - - :param token: - :return: - """ - if any(x.isspace() for x in token): - raise exceptions.ValidationError("Token is invalid!") - - left, sep, right = token.partition(":") - if (not sep) or (not left.isdigit()) or (len(left) < 3): - raise exceptions.ValidationError("Token is invalid!") - - return True - - -def check_result(method_name: str, content_type: str, status_code: int, body: str): - """ - Checks whether `result` is a valid API response. - A result is considered invalid if: - - The server returned an HTTP response code other than 200 - - The content of the result is invalid JSON. - - The method call was unsuccessful (The JSON 'ok' field equals False) - - :param method_name: The name of the method called - :param status_code: status code - :param content_type: content type of result - :param body: result body - :return: The result parsed to a JSON dictionary - :raises ApiException: if one of the above listed cases is applicable - """ - log.debug('Response for %s: [%d] "%r"', method_name, status_code, body) - - if content_type != "application/json": - raise exceptions.NetworkError( - f'Invalid response with content type {content_type}: "{body}"' - ) - - try: - result_json = json.loads(body) - except ValueError: - result_json = {} - - description = result_json.get("description") or body - parameters = types.ResponseParameters(**result_json.get("parameters", {}) or {}) - - if HTTPStatus.OK <= status_code <= HTTPStatus.IM_USED: - return result_json.get("result") - elif parameters.retry_after: - raise exceptions.RetryAfter(parameters.retry_after) - elif parameters.migrate_to_chat_id: - raise exceptions.MigrateToChat(parameters.migrate_to_chat_id) - elif status_code == HTTPStatus.BAD_REQUEST: - exceptions.BadRequest.detect(description) - elif status_code == HTTPStatus.NOT_FOUND: - exceptions.NotFound.detect(description) - elif status_code == HTTPStatus.CONFLICT: - exceptions.ConflictError.detect(description) - elif status_code in [HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN]: - exceptions.Unauthorized.detect(description) - elif status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE: - raise exceptions.NetworkError( - "File too large for uploading. " - "Check telegram api limits https://core.telegram.org/bots/api#senddocument" - ) - elif status_code >= HTTPStatus.INTERNAL_SERVER_ERROR: - if "restart" in description: - raise exceptions.RestartingTelegram() - raise exceptions.TelegramAPIError(description) - raise exceptions.TelegramAPIError(f"{description} [{status_code}]") - - -async def make_request(session, token, method, data=None, files=None, **kwargs): - # log.debug(f"Make request: '{method}' with data: {data} and files {files}") - log.debug('Make request: "%s" with data: "%r" and files "%r"', method, data, files) - - url = Methods.api_url(token=token, method=method) - - req = compose_data(data, files) - try: - async with session.post(url, data=req, **kwargs) as response: - return check_result( - method, response.content_type, response.status, await response.text() - ) - except aiohttp.ClientError as e: - raise exceptions.NetworkError( - f"aiohttp client throws an error: {e.__class__.__name__}: {e}" - ) - - -def guess_filename(obj): - """ - Get file name from object - - :param obj: - :return: - """ - name = getattr(obj, "name", None) - if name and isinstance(name, str) and name[0] != "<" and name[-1] != ">": - return os.path.basename(name) - - -def compose_data(params=None, files=None): - """ - Prepare request data - - :param params: - :param files: - :return: - """ - data = aiohttp.formdata.FormData(quote_fields=False) - - if params: - for key, value in params.items(): - data.add_field(key, str(value)) - - if files: - for key, f in files.items(): - if isinstance(f, tuple): - if len(f) == 2: - filename, fileobj = f - else: - raise ValueError("Tuple must have exactly 2 elements: filename, fileobj") - elif isinstance(f, types.InputFile): - filename, fileobj = f.filename, f.file - else: - filename, fileobj = guess_filename(f) or key, f - - data.add_field(key, fileobj, filename=filename) - - return data - - -class Methods(Helper): - """ - Helper for Telegram API Methods listed on https://core.telegram.org/bots/api - - List is updated to Bot API 4.4 - """ - - mode = HelperMode.lowerCamelCase - - # Getting Updates - GET_UPDATES = Item() # getUpdates - SET_WEBHOOK = Item() # setWebhook - DELETE_WEBHOOK = Item() # deleteWebhook - GET_WEBHOOK_INFO = Item() # getWebhookInfo - - # Available methods - GET_ME = Item() # getMe - SEND_MESSAGE = Item() # sendMessage - FORWARD_MESSAGE = Item() # forwardMessage - SEND_PHOTO = Item() # sendPhoto - SEND_AUDIO = Item() # sendAudio - SEND_DOCUMENT = Item() # sendDocument - SEND_VIDEO = Item() # sendVideo - SEND_ANIMATION = Item() # sendAnimation - SEND_VOICE = Item() # sendVoice - SEND_VIDEO_NOTE = Item() # sendVideoNote - SEND_MEDIA_GROUP = Item() # sendMediaGroup - SEND_LOCATION = Item() # sendLocation - EDIT_MESSAGE_LIVE_LOCATION = Item() # editMessageLiveLocation - STOP_MESSAGE_LIVE_LOCATION = Item() # stopMessageLiveLocation - SEND_VENUE = Item() # sendVenue - SEND_CONTACT = Item() # sendContact - SEND_POLL = Item() # sendPoll - SEND_CHAT_ACTION = Item() # sendChatAction - GET_USER_PROFILE_PHOTOS = Item() # getUserProfilePhotos - GET_FILE = Item() # getFile - KICK_CHAT_MEMBER = Item() # kickChatMember - UNBAN_CHAT_MEMBER = Item() # unbanChatMember - RESTRICT_CHAT_MEMBER = Item() # restrictChatMember - PROMOTE_CHAT_MEMBER = Item() # promoteChatMember - SET_CHAT_PERMISSIONS = Item() # setChatPermissions - EXPORT_CHAT_INVITE_LINK = Item() # exportChatInviteLink - SET_CHAT_PHOTO = Item() # setChatPhoto - DELETE_CHAT_PHOTO = Item() # deleteChatPhoto - SET_CHAT_TITLE = Item() # setChatTitle - SET_CHAT_DESCRIPTION = Item() # setChatDescription - PIN_CHAT_MESSAGE = Item() # pinChatMessage - UNPIN_CHAT_MESSAGE = Item() # unpinChatMessage - LEAVE_CHAT = Item() # leaveChat - GET_CHAT = Item() # getChat - GET_CHAT_ADMINISTRATORS = Item() # getChatAdministrators - GET_CHAT_MEMBERS_COUNT = Item() # getChatMembersCount - GET_CHAT_MEMBER = Item() # getChatMember - SET_CHAT_STICKER_SET = Item() # setChatStickerSet - DELETE_CHAT_STICKER_SET = Item() # deleteChatStickerSet - ANSWER_CALLBACK_QUERY = Item() # answerCallbackQuery - - # Updating messages - EDIT_MESSAGE_TEXT = Item() # editMessageText - EDIT_MESSAGE_CAPTION = Item() # editMessageCaption - EDIT_MESSAGE_MEDIA = Item() # editMessageMedia - EDIT_MESSAGE_REPLY_MARKUP = Item() # editMessageReplyMarkup - STOP_POLL = Item() # stopPoll - DELETE_MESSAGE = Item() # deleteMessage - - # Stickers - SEND_STICKER = Item() # sendSticker - GET_STICKER_SET = Item() # getStickerSet - UPLOAD_STICKER_FILE = Item() # uploadStickerFile - CREATE_NEW_STICKER_SET = Item() # createNewStickerSet - ADD_STICKER_TO_SET = Item() # addStickerToSet - SET_STICKER_POSITION_IN_SET = Item() # setStickerPositionInSet - DELETE_STICKER_FROM_SET = Item() # deleteStickerFromSet - - # Inline mode - ANSWER_INLINE_QUERY = Item() # answerInlineQuery - - # Payments - SEND_INVOICE = Item() # sendInvoice - ANSWER_SHIPPING_QUERY = Item() # answerShippingQuery - ANSWER_PRE_CHECKOUT_QUERY = Item() # answerPreCheckoutQuery - - # Telegram Passport - SET_PASSPORT_DATA_ERRORS = Item() # setPassportDataErrors - - # Games - SEND_GAME = Item() # sendGame - SET_GAME_SCORE = Item() # setGameScore - GET_GAME_HIGH_SCORES = Item() # getGameHighScores - - @staticmethod - def api_url(token, method): - """ - Generate API URL with included token and method name - - :param token: - :param method: - :return: - """ - return API_URL.format(token=token, method=method) - - @staticmethod - def file_url(token, path): - """ - Generate File URL with included token and file path - - :param token: - :param path: - :return: - """ - return FILE_URL.format(token=token, path=path) diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py deleted file mode 100644 index 04ec9f43..00000000 --- a/aiogram/bot/base.py +++ /dev/null @@ -1,314 +0,0 @@ -import asyncio -import contextlib -import io -import ssl -import typing -from contextvars import ContextVar -from typing import Dict, List, Optional, Union - -import aiohttp -import certifi -from aiohttp.helpers import sentinel - -from . import api -from ..types import ParseMode, base -from ..utils import json -from ..utils.auth_widget import check_integrity - - -class BaseBot: - """ - Base class for bot. It's raw bot. - """ - - _ctx_timeout = ContextVar("TelegramRequestTimeout") - _ctx_token = ContextVar("BotDifferentToken") - - def __init__( - self, - token: base.String, - loop: Optional[asyncio.AbstractEventLoop] = None, - connections_limit: Optional[base.Integer] = None, - proxy: Optional[base.String] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - validate_token: Optional[base.Boolean] = True, - parse_mode: typing.Optional[base.String] = None, - timeout: typing.Optional[ - typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout] - ] = None, - ): - """ - Instructions how to get Bot token is found here: https://core.telegram.org/bots#3-how-do-i-create-a-bot - - :param token: token from @BotFather - :type token: :obj:`str` - :param loop: event loop - :type loop: Optional Union :obj:`asyncio.BaseEventLoop`, :obj:`asyncio.AbstractEventLoop` - :param connections_limit: connections limit for aiohttp.ClientSession - :type connections_limit: :obj:`int` - :param proxy: HTTP proxy URL - :type proxy: :obj:`str` - :param proxy_auth: Authentication information - :type proxy_auth: Optional :obj:`aiohttp.BasicAuth` - :param validate_token: Validate token. - :type validate_token: :obj:`bool` - :param parse_mode: You can set default parse mode - :type parse_mode: :obj:`str` - :param timeout: Request timeout - :type timeout: :obj:`typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]]` - :raise: when token is invalid throw an :obj:`aiogram.utils.exceptions.ValidationError` - """ - # Authentication - if validate_token: - api.check_token(token) - self._token = None - self.__token = token - - self.proxy = proxy - self.proxy_auth = proxy_auth - - # Asyncio loop instance - if loop is None: - loop = asyncio.get_event_loop() - self.loop = loop - - # aiohttp main session - ssl_context = ssl.create_default_context(cafile=certifi.where()) - - if isinstance(proxy, str) and ( - proxy.startswith("socks5://") or proxy.startswith("socks4://") - ): - from aiohttp_socks import SocksConnector - from aiohttp_socks.helpers import parse_socks_url - - socks_ver, host, port, username, password = parse_socks_url(proxy) - if proxy_auth: - if not username: - username = proxy_auth.login - if not password: - password = proxy_auth.password - - connector = SocksConnector( - socks_ver=socks_ver, - host=host, - port=port, - username=username, - password=password, - limit=connections_limit, - ssl_context=ssl_context, - rdns=True, - loop=self.loop, - ) - - self.proxy = None - self.proxy_auth = None - else: - connector = aiohttp.TCPConnector( - limit=connections_limit, ssl=ssl_context, loop=self.loop - ) - self._timeout = None - self.timeout = timeout - - self.session = aiohttp.ClientSession( - connector=connector, loop=self.loop, json_serialize=json.dumps - ) - - self.parse_mode = parse_mode - - def __del__(self): - if not hasattr(self, 'loop'): - return - if self.loop.is_running(): - self.loop.create_task(self.close()) - return - loop = asyncio.new_event_loop() - loop.run_until_complete(self.close()) - - @staticmethod - def _prepare_timeout( - value: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]] - ) -> typing.Optional[aiohttp.ClientTimeout]: - if value is None or isinstance(value, aiohttp.ClientTimeout): - return value - return aiohttp.ClientTimeout(total=value) - - @property - def timeout(self): - timeout = self._ctx_timeout.get(self._timeout) - if timeout is None: - return sentinel - return timeout - - @timeout.setter - def timeout(self, value): - self._timeout = self._prepare_timeout(value) - - @timeout.deleter - def timeout(self): - self.timeout = None - - @contextlib.contextmanager - def request_timeout( - self, timeout: typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout] - ): - """ - Context manager implements opportunity to change request timeout in current context - - :param timeout: Request timeout - :type timeout: :obj:`typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]]` - :return: - """ - timeout = self._prepare_timeout(timeout) - token = self._ctx_timeout.set(timeout) - try: - yield - finally: - self._ctx_timeout.reset(token) - - @property - def __token(self): - return self._ctx_token.get(self._token) - - @__token.setter - def __token(self, value): - self._token = value - - @contextlib.contextmanager - def with_token(self, bot_token: base.String, validate_token: Optional[base.Boolean] = True): - if validate_token: - api.check_token(bot_token) - token = self._ctx_token.set(bot_token) - try: - yield - finally: - self._ctx_token.reset(token) - - async def close(self): - """ - Close all client sessions - """ - await self.session.close() - - async def request( - self, - method: base.String, - data: Optional[Dict] = None, - files: Optional[Dict] = None, - **kwargs, - ) -> Union[List, Dict, base.Boolean]: - """ - Make an request to Telegram Bot API - - https://core.telegram.org/bots/api#making-requests - - :param method: API method - :type method: :obj:`str` - :param data: request parameters - :type data: :obj:`dict` - :param files: files - :type files: :obj:`dict` - :return: result - :rtype: Union[List, Dict] - :raise: :obj:`aiogram.exceptions.TelegramApiError` - """ - return await api.make_request( - self.session, - self.__token, - method, - data, - files, - proxy=self.proxy, - proxy_auth=self.proxy_auth, - timeout=self.timeout, - **kwargs, - ) - - async def download_file( - self, - file_path: base.String, - destination: Optional[base.InputFile] = None, - timeout: Optional[base.Integer] = sentinel, - chunk_size: Optional[base.Integer] = 65536, - seek: Optional[base.Boolean] = True, - ) -> Union[io.BytesIO, io.FileIO]: - """ - Download file by file_path to destination - - if You want to automatically create destination (:class:`io.BytesIO`) use default - value of destination and handle result of this method. - - :param file_path: file path on telegram server (You can get it from :obj:`aiogram.types.File`) - :type file_path: :obj:`str` - :param destination: filename or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO` - :param timeout: Integer - :param chunk_size: Integer - :param seek: Boolean - go to start of file when downloading is finished. - :return: destination - """ - if destination is None: - destination = io.BytesIO() - - url = self.get_file_url(file_path) - - dest = destination if isinstance(destination, io.IOBase) else open(destination, "wb") - async with self.session.get( - url, timeout=timeout, proxy=self.proxy, proxy_auth=self.proxy_auth - ) as response: - while True: - chunk = await response.content.read(chunk_size) - if not chunk: - break - dest.write(chunk) - dest.flush() - if seek: - dest.seek(0) - return dest - - def get_file_url(self, file_path): - return api.Methods.file_url(token=self.__token, path=file_path) - - async def send_file(self, file_type, method, file, payload) -> Union[Dict, base.Boolean]: - """ - Send file - - https://core.telegram.org/bots/api#inputfile - - :param file_type: field name - :param method: API method - :param file: String or io.IOBase - :param payload: request payload - :return: response - """ - if file is None: - files = {} - elif isinstance(file, str): - # You can use file ID or URL in the most of requests - payload[file_type] = file - files = None - else: - files = {file_type: file} - - return await self.request(method, payload, files) - - @property - def parse_mode(self): - return getattr(self, "_parse_mode", None) - - @parse_mode.setter - def parse_mode(self, value): - if value is None: - setattr(self, "_parse_mode", None) - else: - if not isinstance(value, str): - raise TypeError(f"Parse mode must be str, not {type(value)}") - value = value.lower() - if value not in ParseMode.all(): - raise ValueError(f"Parse mode must be one of {ParseMode.all()}") - setattr(self, "_parse_mode", value) - - @parse_mode.deleter - def parse_mode(self): - self.parse_mode = None - - def check_auth_widget(self, data): - return check_integrity(self.__token, data) diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py deleted file mode 100644 index 5e8f05d9..00000000 --- a/aiogram/bot/bot.py +++ /dev/null @@ -1,2166 +0,0 @@ -from __future__ import annotations - -import datetime -import typing -import warnings - -from .base import BaseBot, api -from .. import types -from ..types import base -from ..utils.mixins import DataMixin, ContextInstanceMixin -from ..utils.payload import generate_payload, prepare_arg, prepare_attachment, prepare_file - - -class Bot(BaseBot, DataMixin, ContextInstanceMixin): - """ - Base bot class - """ - - @property - async def me(self) -> types.User: - """ - Alias for self.get_me() but lazy and with caching. - - :return: :class:`aiogram.types.User` - """ - if not hasattr(self, '_me'): - setattr(self, '_me', await self.get_me()) - return getattr(self, '_me') - - @me.deleter - def me(self): - """ - Reset `me` - - .. code-block:: python3 - - await bot.me - - :return: :obj:`aiogram.types.User` - """ - if hasattr(self, '_me'): - delattr(self, '_me') - - async def download_file_by_id(self, file_id: base.String, destination=None, - timeout: base.Integer = 30, chunk_size: base.Integer = 65536, - seek: base.Boolean = True): - """ - Download file by file_id to destination - - if You want to automatically create destination (:class:`io.BytesIO`) use default - value of destination and handle result of this method. - - :param file_id: str - :param destination: filename or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO` - :param timeout: int - :param chunk_size: int - :param seek: bool - go to start of file when downloading is finished - :return: destination - """ - file = await self.get_file(file_id) - return await self.download_file(file_path=file.file_path, destination=destination, - timeout=timeout, chunk_size=chunk_size, seek=seek) - - # === Getting updates === - # https://core.telegram.org/bots/api#getting-updates - - async def get_updates(self, offset: typing.Union[base.Integer, None] = None, - limit: typing.Union[base.Integer, None] = None, - timeout: typing.Union[base.Integer, None] = None, - allowed_updates: - typing.Union[typing.List[base.String], None] = None) -> typing.List[types.Update]: - """ - Use this method to receive incoming updates using long polling (wiki). - - Notes - 1. This method will not work if an outgoing webhook is set up. - 2. In order to avoid getting duplicate updates, recalculate offset after each server response. - - Source: https://core.telegram.org/bots/api#getupdates - - :param offset: Identifier of the first update to be returned - :type offset: :obj:`typing.Union[base.Integer, None]` - :param limit: Limits the number of updates to be retrieved - :type limit: :obj:`typing.Union[base.Integer, None]` - :param timeout: Timeout in seconds for long polling - :type timeout: :obj:`typing.Union[base.Integer, None]` - :param allowed_updates: List the types of updates you want your bot to receive - :type allowed_updates: :obj:`typing.Union[typing.List[base.String], None]` - :return: An Array of Update objects is returned - :rtype: :obj:`typing.List[types.Update]` - """ - allowed_updates = prepare_arg(allowed_updates) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.GET_UPDATES, payload) - return [types.Update(**update) for update in result] - - async def set_webhook(self, url: base.String, - certificate: typing.Union[base.InputFile, None] = None, - max_connections: typing.Union[base.Integer, None] = None, - allowed_updates: typing.Union[typing.List[base.String], None] = None) -> base.Boolean: - """ - Use this method to specify a url and receive incoming updates via an outgoing webhook. - Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, - containing a JSON-serialized Update. In case of an unsuccessful request, - we will give up after a reasonable amount of attempts. - - Source: https://core.telegram.org/bots/api#setwebhook - - :param url: HTTPS url to send updates to. Use an empty string to remove webhook integration - :type url: :obj:`base.String` - :param certificate: Upload your public key certificate so that the root certificate in use can be checked - :type certificate: :obj:`typing.Union[base.InputFile, None]` - :param max_connections: Maximum allowed number of simultaneous HTTPS connections to the webhook - for update delivery, 1-100. - :type max_connections: :obj:`typing.Union[base.Integer, None]` - :param allowed_updates: List the types of updates you want your bot to receive - :type allowed_updates: :obj:`typing.Union[typing.List[base.String], None]` - :return: Returns true - :rtype: :obj:`base.Boolean` - """ - allowed_updates = prepare_arg(allowed_updates) - payload = generate_payload(**locals(), exclude=['certificate']) - - files = {} - prepare_file(payload, files, 'certificate', certificate) - - result = await self.request(api.Methods.SET_WEBHOOK, payload, files) - return result - - async def delete_webhook(self) -> base.Boolean: - """ - Use this method to remove webhook integration if you decide to switch back to getUpdates. - Returns True on success. Requires no parameters. - - Source: https://core.telegram.org/bots/api#deletewebhook - - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.DELETE_WEBHOOK, payload) - return result - - async def get_webhook_info(self) -> types.WebhookInfo: - """ - Use this method to get current webhook status. Requires no parameters. - - If the bot is using getUpdates, will return an object with the url field empty. - - Source: https://core.telegram.org/bots/api#getwebhookinfo - - :return: On success, returns a WebhookInfo object - :rtype: :obj:`types.WebhookInfo` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.GET_WEBHOOK_INFO, payload) - return types.WebhookInfo(**result) - - # === Base methods === - # https://core.telegram.org/bots/api#available-methods - - async def get_me(self) -> types.User: - """ - A simple method for testing your bot's auth token. Requires no parameters. - - Source: https://core.telegram.org/bots/api#getme - - :return: Returns basic information about the bot in form of a User object - :rtype: :obj:`types.User` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.GET_ME, payload) - return types.User(**result) - - async def send_message(self, chat_id: typing.Union[base.Integer, base.String], text: base.String, - parse_mode: typing.Union[base.String, None] = None, - disable_web_page_preview: typing.Union[base.Boolean, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send text messages. - - Source: https://core.telegram.org/bots/api#sendmessage - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param text: Text of the message to be sent - :type text: :obj:`base.String` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_web_page_preview: Disables link previews for links in this message - :type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - if self.parse_mode: - payload.setdefault('parse_mode', self.parse_mode) - - result = await self.request(api.Methods.SEND_MESSAGE, payload) - return types.Message(**result) - - async def forward_message(self, chat_id: typing.Union[base.Integer, base.String], - from_chat_id: typing.Union[base.Integer, base.String], message_id: base.Integer, - disable_notification: typing.Union[base.Boolean, None] = None) -> types.Message: - """ - Use this method to forward messages of any kind. - - Source: https://core.telegram.org/bots/api#forwardmessage - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param from_chat_id: Unique identifier for the chat where the original message was sent - :type from_chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param message_id: Message identifier in the chat specified in from_chat_id - :type message_id: :obj:`base.Integer` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.FORWARD_MESSAGE, payload) - return types.Message(**result) - - async def send_photo(self, chat_id: typing.Union[base.Integer, base.String], - photo: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send photos. - - Source: https://core.telegram.org/bots/api#sendphoto - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param photo: Photo to send - :type photo: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Photo caption (may also be used when resending photos by file_id), 0-1024 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals(), exclude=['photo']) - if self.parse_mode: - payload.setdefault('parse_mode', self.parse_mode) - - files = {} - prepare_file(payload, files, 'photo', photo) - - result = await self.request(api.Methods.SEND_PHOTO, payload, files) - return types.Message(**result) - - async def send_audio(self, chat_id: typing.Union[base.Integer, base.String], - audio: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - duration: typing.Union[base.Integer, None] = None, - performer: typing.Union[base.String, None] = None, - title: typing.Union[base.String, None] = None, - thumb: typing.Union[base.InputFile, base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send audio files, if you want Telegram clients to display them in the music player. - Your audio must be in the .mp3 format. - - For sending voice messages, use the sendVoice method instead. - - Source: https://core.telegram.org/bots/api#sendaudio - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param audio: Audio file to send - :type audio: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Audio caption, 0-1024 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param duration: Duration of the audio in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param performer: Performer - :type performer: :obj:`typing.Union[base.String, None]` - :param title: Track name - :type title: :obj:`typing.Union[base.String, None]` - :param thumb: Thumbnail of the file sent - :type thumb: :obj:`typing.Union[base.InputFile, base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals(), exclude=['audio', 'thumb']) - if self.parse_mode: - payload.setdefault('parse_mode', self.parse_mode) - - files = {} - prepare_file(payload, files, 'audio', audio) - prepare_attachment(payload, files, 'thumb', thumb) - - result = await self.request(api.Methods.SEND_AUDIO, payload, files) - return types.Message(**result) - - async def send_document(self, chat_id: typing.Union[base.Integer, base.String], - document: typing.Union[base.InputFile, base.String], - thumb: typing.Union[base.InputFile, base.String, None] = None, - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send general files. - - Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future. - - Source: https://core.telegram.org/bots/api#senddocument - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param document: File to send - :type document: :obj:`typing.Union[base.InputFile, base.String]` - :param thumb: Thumbnail of the file sent - :type thumb: :obj:`typing.Union[base.InputFile, base.String, None]` - :param caption: Document caption (may also be used when resending documents by file_id), 0-1024 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, types.ForceReply], None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals(), exclude=['document']) - if self.parse_mode: - payload.setdefault('parse_mode', self.parse_mode) - - files = {} - prepare_file(payload, files, 'document', document) - - result = await self.request(api.Methods.SEND_DOCUMENT, payload, files) - return types.Message(**result) - - async def send_video(self, chat_id: typing.Union[base.Integer, base.String], - video: typing.Union[base.InputFile, base.String], - duration: typing.Union[base.Integer, None] = None, - width: typing.Union[base.Integer, None] = None, - height: typing.Union[base.Integer, None] = None, - thumb: typing.Union[base.InputFile, base.String, None] = None, - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - supports_streaming: typing.Union[base.Boolean, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send video files, Telegram clients support mp4 videos - (other formats may be sent as Document). - - Source: https://core.telegram.org/bots/api#sendvideo - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param video: Video to send - :type video: :obj:`typing.Union[base.InputFile, base.String]` - :param duration: Duration of sent video in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param width: Video width - :type width: :obj:`typing.Union[base.Integer, None]` - :param height: Video height - :type height: :obj:`typing.Union[base.Integer, None]` - :param thumb: Thumbnail of the file sent - :type thumb: :obj:`typing.Union[base.InputFile, base.String, None]` - :param caption: Video caption (may also be used when resending videos by file_id), 0-1024 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param supports_streaming: Pass True, if the uploaded video is suitable for streaming - :type supports_streaming: :obj:`typing.Union[base.Boolean, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals(), exclude=['video', 'thumb']) - if self.parse_mode: - payload.setdefault('parse_mode', self.parse_mode) - - files = {} - prepare_file(payload, files, 'video', video) - prepare_attachment(payload, files, 'thumb', thumb) - - result = await self.request(api.Methods.SEND_VIDEO, payload, files) - return types.Message(**result) - - async def send_animation(self, - chat_id: typing.Union[base.Integer, base.String], - animation: typing.Union[base.InputFile, base.String], - duration: typing.Union[base.Integer, None] = None, - width: typing.Union[base.Integer, None] = None, - height: typing.Union[base.Integer, None] = None, - thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None, - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply], None] = None - ) -> types.Message: - """ - Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). - - On success, the sent Message is returned. - Bots can currently send animation files of up to 50 MB in size, this limit may be changed in the future. - - Source https://core.telegram.org/bots/api#sendanimation - - :param chat_id: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param animation: Animation to send. Pass a file_id as String to send an animation that exists - on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation - from the Internet, or upload a new animation using multipart/form-data - :type animation: :obj:`typing.Union[base.InputFile, base.String]` - :param duration: Duration of sent animation in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param width: Animation width - :type width: :obj:`typing.Union[base.Integer, None]` - :param height: Animation height - :type height: :obj:`typing.Union[base.Integer, None]` - :param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 90. - :type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]` - :param caption: Animation caption (may also be used when resending animation by file_id), 0-1024 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in the media caption - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, types.ForceReply], None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals(), exclude=["animation", "thumb"]) - - files = {} - prepare_file(payload, files, 'animation', animation) - prepare_attachment(payload, files, 'thumb', thumb) - - result = await self.request(api.Methods.SEND_ANIMATION, payload, files) - return types.Message(**result) - - async def send_voice(self, chat_id: typing.Union[base.Integer, base.String], - voice: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - duration: typing.Union[base.Integer, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send audio files, if you want Telegram clients to display the file - as a playable voice message. - - For this to work, your audio must be in an .ogg file encoded with OPUS - (other formats may be sent as Audio or Document). - - Source: https://core.telegram.org/bots/api#sendvoice - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param voice: Audio file to send - :type voice: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Voice message caption, 0-1024 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param duration: Duration of the voice message in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals(), exclude=['voice']) - if self.parse_mode: - payload.setdefault('parse_mode', self.parse_mode) - - files = {} - prepare_file(payload, files, 'voice', voice) - - result = await self.request(api.Methods.SEND_VOICE, payload, files) - return types.Message(**result) - - async def send_video_note(self, chat_id: typing.Union[base.Integer, base.String], - video_note: typing.Union[base.InputFile, base.String], - duration: typing.Union[base.Integer, None] = None, - length: typing.Union[base.Integer, None] = None, - thumb: typing.Union[base.InputFile, base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long. - Use this method to send video messages. - - Source: https://core.telegram.org/bots/api#sendvideonote - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param video_note: Video note to send - :type video_note: :obj:`typing.Union[base.InputFile, base.String]` - :param duration: Duration of sent video in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param length: Video width and height - :type length: :obj:`typing.Union[base.Integer, None]` - :param thumb: Thumbnail of the file sent - :type thumb: :obj:`typing.Union[base.InputFile, base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals(), exclude=['video_note']) - - files = {} - prepare_file(payload, files, 'video_note', video_note) - - result = await self.request(api.Methods.SEND_VIDEO_NOTE, payload, files) - return types.Message(**result) - - async def send_media_group(self, chat_id: typing.Union[base.Integer, base.String], - media: typing.Union[types.MediaGroup, typing.List], - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, - None] = None) -> typing.List[types.Message]: - """ - Use this method to send a group of photos or videos as an album. - - Source: https://core.telegram.org/bots/api#sendmediagroup - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param media: A JSON-serialized array describing photos and videos to be sent - :type media: :obj:`typing.Union[types.MediaGroup, typing.List]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :return: On success, an array of the sent Messages is returned - :rtype: typing.List[types.Message] - """ - # Convert list to MediaGroup - if isinstance(media, list): - media = types.MediaGroup(media) - - files = dict(media.get_files()) - - media = prepare_arg(media) - payload = generate_payload(**locals(), exclude=['files']) - - result = await self.request(api.Methods.SEND_MEDIA_GROUP, payload, files) - return [types.Message(**message) for message in result] - - async def send_location(self, chat_id: typing.Union[base.Integer, base.String], - latitude: base.Float, longitude: base.Float, - live_period: typing.Union[base.Integer, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send point on the map. - - Source: https://core.telegram.org/bots/api#sendlocation - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param latitude: Latitude of the location - :type latitude: :obj:`base.Float` - :param longitude: Longitude of the location - :type longitude: :obj:`base.Float` - :param live_period: Period in seconds for which the location will be updated - :type live_period: :obj:`typing.Union[base.Integer, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SEND_LOCATION, payload) - return types.Message(**result) - - async def edit_message_live_location(self, latitude: base.Float, longitude: base.Float, - chat_id: typing.Union[base.Integer, base.String, None] = None, - message_id: typing.Union[base.Integer, None] = None, - inline_message_id: typing.Union[base.String, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - None] = None) -> types.Message or base.Boolean: - """ - Use this method to edit live location messages sent by the bot or via the bot (for inline bots). - A location can be edited until its live_period expires or editing is explicitly disabled by a call - to stopMessageLiveLocation. - - Source: https://core.telegram.org/bots/api#editmessagelivelocation - - :param chat_id: Required if inline_message_id is not specified - :type chat_id: :obj:`typing.Union[base.Integer, base.String, None]` - :param message_id: Required if inline_message_id is not specified. Identifier of the sent message - :type message_id: :obj:`typing.Union[base.Integer, None]` - :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message - :type inline_message_id: :obj:`typing.Union[base.String, None]` - :param latitude: Latitude of new location - :type latitude: :obj:`base.Float` - :param longitude: Longitude of new location - :type longitude: :obj:`base.Float` - :param reply_markup: A JSON-serialized object for a new inline keyboard - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if the edited message was sent by the bot, the edited Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.EDIT_MESSAGE_LIVE_LOCATION, payload) - if isinstance(result, bool): - return result - return types.Message(**result) - - async def stop_message_live_location(self, - chat_id: typing.Union[base.Integer, base.String, None] = None, - message_id: typing.Union[base.Integer, None] = None, - inline_message_id: typing.Union[base.String, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - None] = None) -> types.Message or base.Boolean: - """ - Use this method to stop updating a live location message sent by the bot or via the bot - (for inline bots) before live_period expires. - - Source: https://core.telegram.org/bots/api#stopmessagelivelocation - - :param chat_id: Required if inline_message_id is not specified - :type chat_id: :obj:`typing.Union[base.Integer, base.String, None]` - :param message_id: Required if inline_message_id is not specified. Identifier of the sent message - :type message_id: :obj:`typing.Union[base.Integer, None]` - :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message - :type inline_message_id: :obj:`typing.Union[base.String, None]` - :param reply_markup: A JSON-serialized object for a new inline keyboard - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if the message was sent by the bot, the sent Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.STOP_MESSAGE_LIVE_LOCATION, payload) - if isinstance(result, bool): - return result - return types.Message(**result) - - async def send_venue(self, chat_id: typing.Union[base.Integer, base.String], - latitude: base.Float, longitude: base.Float, - title: base.String, address: base.String, - foursquare_id: typing.Union[base.String, None] = None, - foursquare_type: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send information about a venue. - - Source: https://core.telegram.org/bots/api#sendvenue - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param latitude: Latitude of the venue - :type latitude: :obj:`base.Float` - :param longitude: Longitude of the venue - :type longitude: :obj:`base.Float` - :param title: Name of the venue - :type title: :obj:`base.String` - :param address: Address of the venue - :type address: :obj:`base.String` - :param foursquare_id: Foursquare identifier of the venue - :type foursquare_id: :obj:`typing.Union[base.String, None]` - :param foursquare_type: Foursquare type of the venue, if known - :type foursquare_type: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SEND_VENUE, payload) - return types.Message(**result) - - async def send_contact(self, chat_id: typing.Union[base.Integer, base.String], - phone_number: base.String, first_name: base.String, - last_name: typing.Union[base.String, None] = None, - vcard: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send phone contacts. - - Source: https://core.telegram.org/bots/api#sendcontact - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param phone_number: Contact's phone number - :type phone_number: :obj:`base.String` - :param first_name: Contact's first name - :type first_name: :obj:`base.String` - :param last_name: Contact's last name - :type last_name: :obj:`typing.Union[base.String, None]` - :param vcard: vcard - :type vcard: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SEND_CONTACT, payload) - return types.Message(**result) - - async def send_poll(self, chat_id: typing.Union[base.Integer, base.String], - question: base.String, - options: typing.List[base.String], - disable_notification: typing.Optional[base.Boolean] = None, - reply_to_message_id: typing.Optional[base.Integer] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send a native poll. A native poll can't be sent to a private chat. - On success, the sent Message is returned. - - :param chat_id: Unique identifier for the target chat - or username of the target channel (in the format @channelusername). - A native poll can't be sent to a private chat. - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param question: Poll question, 1-255 characters - :type question: :obj:`base.String` - :param options: List of answer options, 2-10 strings 1-100 characters each - :param options: :obj:`typing.List[base.String]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Optional[Boolean]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Optional[Integer]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - options = prepare_arg(options) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SEND_POLL, payload) - return types.Message(**result) - - async def send_chat_action(self, chat_id: typing.Union[base.Integer, base.String], - action: base.String) -> base.Boolean: - """ - Use this method when you need to tell the user that something is happening on the bot's side. - The status is set for 5 seconds or less - (when a message arrives from your bot, Telegram clients clear its typing status). - - We only recommend using this method when a response from the bot will take - a noticeable amount of time to arrive. - - Source: https://core.telegram.org/bots/api#sendchataction - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param action: Type of action to broadcast - :type action: :obj:`base.String` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SEND_CHAT_ACTION, payload) - return result - - async def get_user_profile_photos(self, user_id: base.Integer, offset: typing.Union[base.Integer, None] = None, - limit: typing.Union[base.Integer, None] = None) -> types.UserProfilePhotos: - """ - Use this method to get a list of profile pictures for a user. Returns a UserProfilePhotos object. - - Source: https://core.telegram.org/bots/api#getuserprofilephotos - - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :param offset: Sequential number of the first photo to be returned. By default, all photos are returned - :type offset: :obj:`typing.Union[base.Integer, None]` - :param limit: Limits the number of photos to be retrieved. Values between 1—100 are accepted. Defaults to 100 - :type limit: :obj:`typing.Union[base.Integer, None]` - :return: Returns a UserProfilePhotos object - :rtype: :obj:`types.UserProfilePhotos` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.GET_USER_PROFILE_PHOTOS, payload) - return types.UserProfilePhotos(**result) - - async def get_file(self, file_id: base.String) -> types.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. - - 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 - - :param file_id: File identifier to get info about - :type file_id: :obj:`base.String` - :return: On success, a File object is returned - :rtype: :obj:`types.File` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.GET_FILE, payload) - return types.File(**result) - - async def kick_chat_member(self, chat_id: typing.Union[base.Integer, base.String], user_id: base.Integer, - until_date: typing.Union[ - base.Integer, datetime.datetime, datetime.timedelta, None] = None) -> base.Boolean: - """ - Use this method to kick a user from a group, a supergroup or a channel. - In the case of supergroups and channels, the user will not be able to return to the group - on their own using invite links, etc., unless unbanned first. - - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ setting - is off in the target group. - Otherwise members may only be removed by the group's creator or by the member that added them. - - Source: https://core.telegram.org/bots/api#kickchatmember - - :param chat_id: Unique identifier for the target group or username of the target supergroup or channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :param until_date: Date when the user will be unbanned, unix time - :type until_date: :obj:`typing.Union[base.Integer, None]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - until_date = prepare_arg(until_date) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.KICK_CHAT_MEMBER, payload) - return result - - async def unban_chat_member(self, chat_id: typing.Union[base.Integer, base.String], - user_id: base.Integer) -> base.Boolean: - """ - Use this method to unban a previously kicked user in a supergroup or channel. ` - The user will not return to the group or channel automatically, but will be able to join via link, etc. - - The bot must be an administrator for this to work. - - Source: https://core.telegram.org/bots/api#unbanchatmember - - :param chat_id: Unique identifier for the target group or username of the target supergroup or channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.UNBAN_CHAT_MEMBER, payload) - return result - - async def restrict_chat_member(self, chat_id: typing.Union[base.Integer, base.String], - user_id: base.Integer, - permissions: typing.Optional[types.ChatPermissions] = None, - # permissions argument need to be required after removing other `can_*` arguments - until_date: typing.Union[ - base.Integer, datetime.datetime, datetime.timedelta, None] = None, - can_send_messages: typing.Union[base.Boolean, None] = None, - can_send_media_messages: typing.Union[base.Boolean, None] = None, - can_send_other_messages: typing.Union[base.Boolean, None] = None, - can_add_web_page_previews: typing.Union[base.Boolean, None] = None) -> base.Boolean: - """ - Use this method to restrict a user in a supergroup. - The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. - Pass True for all boolean parameters to lift restrictions from a user. - - Source: https://core.telegram.org/bots/api#restrictchatmember - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :param permissions: New user permissions - :type permissions: :obj:`ChatPermissions` - :param until_date: Date when restrictions will be lifted for the user, unix time - :type until_date: :obj:`typing.Union[base.Integer, None]` - :param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues - :type can_send_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_send_media_messages: Pass True, if the user can send audios, documents, photos, videos, - video notes and voice notes, implies can_send_messages - :type can_send_media_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_send_other_messages: Pass True, if the user can send animations, games, stickers and - use inline bots, implies can_send_media_messages - :type can_send_other_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_add_web_page_previews: Pass True, if the user may add web page previews to their messages, - implies can_send_media_messages - :type can_add_web_page_previews: :obj:`typing.Union[base.Boolean, None]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - until_date = prepare_arg(until_date) - permissions = prepare_arg(permissions) - payload = generate_payload(**locals()) - - for permission in ['can_send_messages', - 'can_send_media_messages', - 'can_send_other_messages', - 'can_add_web_page_previews']: - if permission in payload: - warnings.warn(f"The method `restrict_chat_member` now takes the new user permissions " - f"in a single argument of the type ChatPermissions instead of " - f"passing regular argument {payload[permission]}", - DeprecationWarning, stacklevel=2) - - result = await self.request(api.Methods.RESTRICT_CHAT_MEMBER, payload) - return result - - async def promote_chat_member(self, chat_id: typing.Union[base.Integer, base.String], - user_id: base.Integer, - can_change_info: typing.Union[base.Boolean, None] = None, - can_post_messages: typing.Union[base.Boolean, None] = None, - can_edit_messages: typing.Union[base.Boolean, None] = None, - can_delete_messages: typing.Union[base.Boolean, None] = None, - can_invite_users: typing.Union[base.Boolean, None] = None, - can_restrict_members: typing.Union[base.Boolean, None] = None, - can_pin_messages: typing.Union[base.Boolean, None] = None, - can_promote_members: typing.Union[base.Boolean, None] = None) -> base.Boolean: - """ - Use this method to promote or demote a user in a supergroup or a channel. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Pass False for all boolean parameters to demote a user. - - Source: https://core.telegram.org/bots/api#promotechatmember - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :param can_change_info: Pass True, if the administrator can change chat title, photo and other settings - :type can_change_info: :obj:`typing.Union[base.Boolean, None]` - :param can_post_messages: Pass True, if the administrator can create channel posts, channels only - :type can_post_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_edit_messages: Pass True, if the administrator can edit messages of other users, channels only - :type can_edit_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_delete_messages: Pass True, if the administrator can delete messages of other users - :type can_delete_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_invite_users: Pass True, if the administrator can invite new users to the chat - :type can_invite_users: :obj:`typing.Union[base.Boolean, None]` - :param can_restrict_members: Pass True, if the administrator can restrict, ban or unban chat members - :type can_restrict_members: :obj:`typing.Union[base.Boolean, None]` - :param can_pin_messages: Pass True, if the administrator can pin messages, supergroups only - :type can_pin_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_promote_members: Pass True, if the administrator can add new administrators - with a subset of his own privileges or demote administrators that he has promoted, - directly or indirectly (promoted by administrators that were appointed by him) - :type can_promote_members: :obj:`typing.Union[base.Boolean, None]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.PROMOTE_CHAT_MEMBER, payload) - return result - - async def set_chat_permissions(self, chat_id: typing.Union[base.Integer, base.String], - permissions: types.ChatPermissions) -> base.Boolean: - """ - Use this method to set default chat permissions for all members. - The bot must be an administrator in the group or a supergroup for this to work and must have the - can_restrict_members admin rights. - - Returns True on success. - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - :param permissions: New default chat permissions - :return: True on success. - """ - permissions = prepare_arg(permissions) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SET_CHAT_PERMISSIONS, payload) - return result - - async def export_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String]) -> base.String: - """ - Use this method to generate a new invite link for a chat; any previously generated link is revoked. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Source: https://core.telegram.org/bots/api#exportchatinvitelink - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :return: Returns exported invite link as String on success - :rtype: :obj:`base.String` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.EXPORT_CHAT_INVITE_LINK, payload) - return result - - async def set_chat_photo(self, chat_id: typing.Union[base.Integer, base.String], - photo: base.InputFile) -> base.Boolean: - """ - Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ - setting is off in the target group. - - Source: https://core.telegram.org/bots/api#setchatphoto - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param photo: New chat photo, uploaded using multipart/form-data - :type photo: :obj:`base.InputFile` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals(), exclude=['photo']) - - files = {} - prepare_file(payload, files, 'photo', photo) - - result = await self.request(api.Methods.SET_CHAT_PHOTO, payload, files) - return result - - async def delete_chat_photo(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean: - """ - Use this method to delete a chat photo. Photos can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ - setting is off in the target group. - - Source: https://core.telegram.org/bots/api#deletechatphoto - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.DELETE_CHAT_PHOTO, payload) - return result - - async def set_chat_title(self, chat_id: typing.Union[base.Integer, base.String], - title: base.String) -> base.Boolean: - """ - Use this method to change the title of a chat. Titles can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ - setting is off in the target group. - - Source: https://core.telegram.org/bots/api#setchattitle - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param title: New chat title, 1-255 characters - :type title: :obj:`base.String` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SET_CHAT_TITLE, payload) - return result - - async def set_chat_description(self, chat_id: typing.Union[base.Integer, base.String], - description: typing.Union[base.String, None] = None) -> base.Boolean: - """ - Use this method to change the description of a supergroup or a channel. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Source: https://core.telegram.org/bots/api#setchatdescription - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param description: New chat description, 0-255 characters - :type description: :obj:`typing.Union[base.String, None]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SET_CHAT_DESCRIPTION, payload) - return result - - async def pin_chat_message(self, chat_id: typing.Union[base.Integer, base.String], message_id: base.Integer, - disable_notification: typing.Union[base.Boolean, None] = None) -> base.Boolean: - """ - Use this method to pin a message in a supergroup. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Source: https://core.telegram.org/bots/api#pinchatmessage - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param message_id: Identifier of a message to pin - :type message_id: :obj:`base.Integer` - :param disable_notification: Pass True, if it is not necessary to send a notification to - all group members about the new pinned message - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.PIN_CHAT_MESSAGE, payload) - return result - - async def unpin_chat_message(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean: - """ - Use this method to unpin a message in a supergroup chat. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Source: https://core.telegram.org/bots/api#unpinchatmessage - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.UNPIN_CHAT_MESSAGE, payload) - return result - - async def leave_chat(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean: - """ - Use this method for your bot to leave a group, supergroup or channel. - - Source: https://core.telegram.org/bots/api#leavechat - - :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.LEAVE_CHAT, payload) - return result - - async def get_chat(self, chat_id: typing.Union[base.Integer, base.String]) -> types.Chat: - """ - Use this method to get up to date information about the chat - (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.). - - Source: https://core.telegram.org/bots/api#getchat - - :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :return: Returns a Chat object on success - :rtype: :obj:`types.Chat` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.GET_CHAT, payload) - return types.Chat(**result) - - async def get_chat_administrators(self, chat_id: typing.Union[base.Integer, base.String] - ) -> typing.List[types.ChatMember]: - """ - Use this method to get a list of administrators in a chat. - - Source: https://core.telegram.org/bots/api#getchatadministrators - - :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :return: On success, returns an Array of ChatMember objects that contains information about all - chat administrators except other bots. - If the chat is a group or a supergroup and no administrators were appointed, - only the creator will be returned. - :rtype: :obj:`typing.List[types.ChatMember]` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.GET_CHAT_ADMINISTRATORS, payload) - return [types.ChatMember(**chatmember) for chatmember in result] - - async def get_chat_members_count(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Integer: - """ - Use this method to get the number of members in a chat. - - Source: https://core.telegram.org/bots/api#getchatmemberscount - - :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :return: Returns Int on success - :rtype: :obj:`base.Integer` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.GET_CHAT_MEMBERS_COUNT, payload) - return result - - async def get_chat_member(self, chat_id: typing.Union[base.Integer, base.String], - user_id: base.Integer) -> types.ChatMember: - """ - Use this method to get information about a member of a chat. - - Source: https://core.telegram.org/bots/api#getchatmember - - :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :return: Returns a ChatMember object on success - :rtype: :obj:`types.ChatMember` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.GET_CHAT_MEMBER, payload) - return types.ChatMember(**result) - - async def set_chat_sticker_set(self, chat_id: typing.Union[base.Integer, base.String], - sticker_set_name: base.String) -> base.Boolean: - """ - Use this method to set a new group sticker set for a supergroup. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Use the field can_set_sticker_set optionally returned in getChat requests to check - if the bot can use this method. - - Source: https://core.telegram.org/bots/api#setchatstickerset - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param sticker_set_name: Name of the sticker set to be set as the group sticker set - :type sticker_set_name: :obj:`base.String` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SET_CHAT_STICKER_SET, payload) - return result - - async def delete_chat_sticker_set(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean: - """ - Use this method to delete a group sticker set from a supergroup. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Use the field can_set_sticker_set optionally returned in getChat requests - to check if the bot can use this method. - - Source: https://core.telegram.org/bots/api#deletechatstickerset - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.DELETE_CHAT_STICKER_SET, payload) - return result - - async def answer_callback_query(self, callback_query_id: base.String, - text: typing.Union[base.String, None] = None, - show_alert: typing.Union[base.Boolean, None] = None, - url: typing.Union[base.String, None] = None, - cache_time: typing.Union[base.Integer, None] = None) -> base.Boolean: - """ - Use this method to send answers to callback queries sent from inline keyboards. - The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. - - 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 and accept the terms. - Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. - - Source: https://core.telegram.org/bots/api#answercallbackquery - - :param callback_query_id: Unique identifier for the query to be answered - :type callback_query_id: :obj:`base.String` - :param text: Text of the notification. If not specified, nothing will be shown to the user, 0-1024 characters - :type text: :obj:`typing.Union[base.String, None]` - :param show_alert: If true, an alert will be shown by the client instead of a notification - at the top of the chat screen. Defaults to false. - :type show_alert: :obj:`typing.Union[base.Boolean, None]` - :param url: URL that will be opened by the user's client - :type url: :obj:`typing.Union[base.String, None]` - :param cache_time: The maximum amount of time in seconds that the - result of the callback query may be cached client-side. - :type cache_time: :obj:`typing.Union[base.Integer, None]` - :return: On success, True is returned - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.ANSWER_CALLBACK_QUERY, payload) - return result - - async def edit_message_text(self, text: base.String, - chat_id: typing.Union[base.Integer, base.String, None] = None, - message_id: typing.Union[base.Integer, None] = None, - inline_message_id: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_web_page_preview: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - None] = None) -> types.Message or base.Boolean: - """ - Use this method to edit text and game messages sent by the bot or via the bot (for inline bots). - - Source: https://core.telegram.org/bots/api#editmessagetext - - :param chat_id: Required if inline_message_id is not specified - Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String, None]` - :param message_id: Required if inline_message_id is not specified. Identifier of the sent message - :type message_id: :obj:`typing.Union[base.Integer, None]` - :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message - :type inline_message_id: :obj:`typing.Union[base.String, None]` - :param text: New text of the message - :type text: :obj:`base.String` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_web_page_preview: Disables link previews for links in this message - :type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: A JSON-serialized object for an inline keyboard - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if edited message is sent by the bot, - the edited Message is returned, otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - if self.parse_mode: - payload.setdefault('parse_mode', self.parse_mode) - - result = await self.request(api.Methods.EDIT_MESSAGE_TEXT, payload) - if isinstance(result, bool): - return result - return types.Message(**result) - - async def edit_message_caption(self, chat_id: typing.Union[base.Integer, base.String, None] = None, - message_id: typing.Union[base.Integer, None] = None, - inline_message_id: typing.Union[base.String, None] = None, - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - None] = None) -> types.Message or base.Boolean: - """ - Use this method to edit captions of messages sent by the bot or via the bot (for inline bots). - - Source: https://core.telegram.org/bots/api#editmessagecaption - - :param chat_id: Required if inline_message_id is not specified - Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String, None]` - :param message_id: Required if inline_message_id is not specified. Identifier of the sent message - :type message_id: :obj:`typing.Union[base.Integer, None]` - :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message - :type inline_message_id: :obj:`typing.Union[base.String, None]` - :param caption: New caption of the message - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param reply_markup: A JSON-serialized object for an inline keyboard - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if edited message is sent by the bot, the edited Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - if self.parse_mode: - payload.setdefault('parse_mode', self.parse_mode) - - result = await self.request(api.Methods.EDIT_MESSAGE_CAPTION, payload) - if isinstance(result, bool): - return result - return types.Message(**result) - - async def edit_message_media(self, - media: types.InputMedia, - chat_id: typing.Union[typing.Union[base.Integer, base.String], None] = None, - message_id: typing.Union[base.Integer, None] = None, - inline_message_id: typing.Union[base.String, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None, - ) -> typing.Union[types.Message, base.Boolean]: - """ - Use this method to edit audio, document, photo, or video messages. - If a message is a part of a message album, then it can be edited only to a photo or a video. - Otherwise, message type can be changed arbitrarily. - When inline message is edited, new file can't be uploaded. - Use previously uploaded file via its file_id or specify a URL. - - On success, if the edited message was sent by the bot, - the edited Message is returned, otherwise True is returned. - - Source https://core.telegram.org/bots/api#editmessagemedia - - :param chat_id: Required if inline_message_id is not specified - :type chat_id: :obj:`typing.Union[typing.Union[base.Integer, base.String], None]` - :param message_id: Required if inline_message_id is not specified. Identifier of the sent message - :type message_id: :obj:`typing.Union[base.Integer, None]` - :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message - :type inline_message_id: :obj:`typing.Union[base.String, None]` - :param media: A JSON-serialized object for a new media content of the message - :type media: :obj:`types.InputMedia` - :param reply_markup: A JSON-serialized object for a new inline keyboard - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if the edited message was sent by the bot, the edited Message is returned, - otherwise True is returned - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - - if isinstance(media, types.InputMedia): - files = dict(media.get_files()) - else: - files = None - - result = await self.request(api.Methods.EDIT_MESSAGE_MEDIA, payload, files) - if isinstance(result, bool): - return result - return types.Message(**result) - - async def edit_message_reply_markup(self, - chat_id: typing.Union[base.Integer, base.String, None] = None, - message_id: typing.Union[base.Integer, None] = None, - inline_message_id: typing.Union[base.String, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - None] = None) -> types.Message or base.Boolean: - """ - Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots). - - Source: https://core.telegram.org/bots/api#editmessagereplymarkup - - :param chat_id: Required if inline_message_id is not specified - Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String, None]` - :param message_id: Required if inline_message_id is not specified. Identifier of the sent message - :type message_id: :obj:`typing.Union[base.Integer, None]` - :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message - :type inline_message_id: :obj:`typing.Union[base.String, None]` - :param reply_markup: A JSON-serialized object for an inline keyboard - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if edited message is sent by the bot, the edited Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.EDIT_MESSAGE_REPLY_MARKUP, payload) - if isinstance(result, bool): - return result - return types.Message(**result) - - async def stop_poll(self, chat_id: typing.Union[base.String, base.Integer], - message_id: base.Integer, - reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None) -> types.Poll: - """ - Use this method to stop a poll which was sent by the bot. - On success, the stopped Poll with the final results is returned. - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.String, base.Integer]` - :param message_id: Identifier of the original message with the poll - :type message_id: :obj:`base.Integer` - :param reply_markup: A JSON-serialized object for a new message inline keyboard. - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, the stopped Poll with the final results is returned. - :rtype: :obj:`types.Poll` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.STOP_POLL, payload) - return types.Poll(**result) - - async def delete_message(self, chat_id: typing.Union[base.Integer, base.String], - message_id: base.Integer) -> base.Boolean: - """ - Use this method to delete a message, including service messages, with the following limitations: - - A message can only be deleted if it was sent less than 48 hours ago. - - Bots can delete outgoing messages in private chats, groups, and supergroups. - - Bots can delete incoming messages in private chats. - - Bots granted can_post_messages permissions can delete outgoing messages in channels. - - If the bot is an administrator of a group, it can delete any message there. - - If the bot has can_delete_messages permission in a supergroup or a channel, it can delete any message there. - - Source: https://core.telegram.org/bots/api#deletemessage - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param message_id: Identifier of the message to delete - :type message_id: :obj:`base.Integer` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.DELETE_MESSAGE, payload) - return result - - # === Stickers === - # https://core.telegram.org/bots/api#stickers - - async def send_sticker(self, chat_id: typing.Union[base.Integer, base.String], - sticker: typing.Union[base.InputFile, base.String], - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, - types.ForceReply, None] = None) -> types.Message: - """ - Use this method to send .webp stickers. - - Source: https://core.telegram.org/bots/api#sendsticker - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param sticker: Sticker to send - :type sticker: :obj:`typing.Union[base.InputFile, base.String]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals(), exclude=['sticker']) - - files = {} - prepare_file(payload, files, 'sticker', sticker) - - result = await self.request(api.Methods.SEND_STICKER, payload, files) - return types.Message(**result) - - async def get_sticker_set(self, name: base.String) -> types.StickerSet: - """ - Use this method to get a sticker set. - - Source: https://core.telegram.org/bots/api#getstickerset - - :param name: Name of the sticker set - :type name: :obj:`base.String` - :return: On success, a StickerSet object is returned - :rtype: :obj:`types.StickerSet` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.GET_STICKER_SET, payload) - return types.StickerSet(**result) - - async def upload_sticker_file(self, user_id: base.Integer, png_sticker: base.InputFile) -> types.File: - """ - Use this method to upload a .png file with a sticker for later use in createNewStickerSet - and addStickerToSet methods (can be used multiple times). - - Source: https://core.telegram.org/bots/api#uploadstickerfile - - :param user_id: User identifier of sticker file owner - :type user_id: :obj:`base.Integer` - :param png_sticker: 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. - :type png_sticker: :obj:`base.InputFile` - :return: Returns the uploaded File on success - :rtype: :obj:`types.File` - """ - payload = generate_payload(**locals(), exclude=['png_sticker']) - - files = {} - prepare_file(payload, files, 'png_sticker', png_sticker) - - result = await self.request(api.Methods.UPLOAD_STICKER_FILE, payload, files) - return types.File(**result) - - async def create_new_sticker_set(self, user_id: base.Integer, name: base.String, title: base.String, - png_sticker: typing.Union[base.InputFile, base.String], emojis: base.String, - contains_masks: typing.Union[base.Boolean, None] = None, - mask_position: typing.Union[types.MaskPosition, None] = None) -> base.Boolean: - """ - Use this method to create new sticker set owned by a user. The bot will be able to edit the created sticker set. - - Source: https://core.telegram.org/bots/api#createnewstickerset - - :param user_id: User identifier of created sticker set owner - :type user_id: :obj:`base.Integer` - :param name: Short name of sticker set, to be used in t.me/addstickers/ URLs (e.g., animals) - :type name: :obj:`base.String` - :param title: Sticker set title, 1-64 characters - :type title: :obj:`base.String` - :param png_sticker: 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. - :type png_sticker: :obj:`typing.Union[base.InputFile, base.String]` - :param emojis: One or more emoji corresponding to the sticker - :type emojis: :obj:`base.String` - :param contains_masks: Pass True, if a set of mask stickers should be created - :type contains_masks: :obj:`typing.Union[base.Boolean, None]` - :param mask_position: A JSON-serialized object for position where the mask should be placed on faces - :type mask_position: :obj:`typing.Union[types.MaskPosition, None]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - mask_position = prepare_arg(mask_position) - payload = generate_payload(**locals(), exclude=['png_sticker']) - - files = {} - prepare_file(payload, files, 'png_sticker', png_sticker) - - result = await self.request(api.Methods.CREATE_NEW_STICKER_SET, payload, files) - return result - - async def add_sticker_to_set(self, user_id: base.Integer, name: base.String, - png_sticker: typing.Union[base.InputFile, base.String], emojis: base.String, - mask_position: typing.Union[types.MaskPosition, None] = None) -> base.Boolean: - """ - Use this method to add a new sticker to a set created by the bot. - - Source: https://core.telegram.org/bots/api#addstickertoset - - :param user_id: User identifier of sticker set owner - :type user_id: :obj:`base.Integer` - :param name: Sticker set name - :type name: :obj:`base.String` - :param png_sticker: 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. - :type png_sticker: :obj:`typing.Union[base.InputFile, base.String]` - :param emojis: One or more emoji corresponding to the sticker - :type emojis: :obj:`base.String` - :param mask_position: A JSON-serialized object for position where the mask should be placed on faces - :type mask_position: :obj:`typing.Union[types.MaskPosition, None]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - mask_position = prepare_arg(mask_position) - payload = generate_payload(**locals(), exclude=['png_sticker']) - - files = {} - prepare_file(payload, files, 'png_sticker', png_sticker) - - result = await self.request(api.Methods.ADD_STICKER_TO_SET, payload, files) - return result - - async def set_sticker_position_in_set(self, sticker: base.String, position: base.Integer) -> base.Boolean: - """ - Use this method to move a sticker in a set created by the bot to a specific position. - - Source: https://core.telegram.org/bots/api#setstickerpositioninset - - :param sticker: File identifier of the sticker - :type sticker: :obj:`base.String` - :param position: New sticker position in the set, zero-based - :type position: :obj:`base.Integer` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - result = await self.request(api.Methods.SET_STICKER_POSITION_IN_SET, payload) - - return result - - async def delete_sticker_from_set(self, sticker: base.String) -> base.Boolean: - """ - Use this method to delete a sticker from a set created by the bot. - - The following methods and objects allow your bot to work in inline mode. - - Source: https://core.telegram.org/bots/api#deletestickerfromset - - :param sticker: File identifier of the sticker - :type sticker: :obj:`base.String` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.DELETE_STICKER_FROM_SET, payload) - return result - - async def answer_inline_query(self, inline_query_id: base.String, - results: typing.List[types.InlineQueryResult], - cache_time: typing.Union[base.Integer, None] = None, - is_personal: typing.Union[base.Boolean, None] = None, - next_offset: typing.Union[base.String, None] = None, - switch_pm_text: typing.Union[base.String, None] = None, - switch_pm_parameter: typing.Union[base.String, None] = None) -> base.Boolean: - """ - Use this method to send answers to an inline query. - No more than 50 results per query are allowed. - - Source: https://core.telegram.org/bots/api#answerinlinequery - - :param inline_query_id: Unique identifier for the answered query - :type inline_query_id: :obj:`base.String` - :param results: A JSON-serialized array of results for the inline query - :type results: :obj:`typing.List[types.InlineQueryResult]` - :param cache_time: The maximum amount of time in seconds that the result of the - inline query may be cached on the server. Defaults to 300. - :type cache_time: :obj:`typing.Union[base.Integer, None]` - :param is_personal: Pass True, if results may be cached on the server side only - for the user that sent the query. By default, results may be returned to any user who sends the same query - :type is_personal: :obj:`typing.Union[base.Boolean, None]` - :param next_offset: Pass the offset that a client should send in the - next query with the same text to receive more results. - Pass an empty string if there are no more results or if you don‘t support pagination. - Offset length can’t exceed 64 bytes. - :type next_offset: :obj:`typing.Union[base.String, None]` - :param switch_pm_text: If passed, clients will display a button with specified text that - switches the user to a private chat with the bot and sends the bot a start message - with the parameter switch_pm_parameter - :type switch_pm_text: :obj:`typing.Union[base.String, None]` - :param switch_pm_parameter: Deep-linking parameter for the /start message sent to the bot when - user presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed. - :type switch_pm_parameter: :obj:`typing.Union[base.String, None]` - :return: On success, True is returned - :rtype: :obj:`base.Boolean` - """ - results = prepare_arg(results) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.ANSWER_INLINE_QUERY, payload) - return result - - # === Payments === - # https://core.telegram.org/bots/api#payments - - async def send_invoice(self, chat_id: base.Integer, title: base.String, - description: base.String, payload: base.String, - provider_token: base.String, start_parameter: base.String, - currency: base.String, prices: typing.List[types.LabeledPrice], - provider_data: typing.Union[typing.Dict, None] = None, - photo_url: typing.Union[base.String, None] = None, - photo_size: typing.Union[base.Integer, None] = None, - photo_width: typing.Union[base.Integer, None] = None, - photo_height: typing.Union[base.Integer, None] = None, - need_name: typing.Union[base.Boolean, None] = None, - need_phone_number: typing.Union[base.Boolean, None] = None, - need_email: typing.Union[base.Boolean, None] = None, - need_shipping_address: typing.Union[base.Boolean, None] = None, - is_flexible: typing.Union[base.Boolean, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None) -> types.Message: - """ - Use this method to send invoices. - - Source: https://core.telegram.org/bots/api#sendinvoice - - :param chat_id: Unique identifier for the target private chat - :type chat_id: :obj:`base.Integer` - :param title: Product name, 1-32 characters - :type title: :obj:`base.String` - :param description: Product description, 1-255 characters - :type description: :obj:`base.String` - :param payload: Bot-defined invoice payload, 1-128 bytes - This will not be displayed to the user, use for your internal processes. - :type payload: :obj:`base.String` - :param provider_token: Payments provider token, obtained via Botfather - :type provider_token: :obj:`base.String` - :param start_parameter: Unique deep-linking parameter that can be used to generate this - invoice when used as a start parameter - :type start_parameter: :obj:`base.String` - :param currency: Three-letter ISO 4217 currency code, see more on currencies - :type currency: :obj:`base.String` - :param prices: Price breakdown, a list of components - (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) - :type prices: :obj:`typing.List[types.LabeledPrice]` - :param provider_data: JSON-encoded data about the invoice, which will be shared with the payment provider - :type provider_data: :obj:`typing.Union[typing.Dict, None]` - :param photo_url: URL of the product photo for the invoice - :type photo_url: :obj:`typing.Union[base.String, None]` - :param photo_size: Photo size - :type photo_size: :obj:`typing.Union[base.Integer, None]` - :param photo_width: Photo width - :type photo_width: :obj:`typing.Union[base.Integer, None]` - :param photo_height: Photo height - :type photo_height: :obj:`typing.Union[base.Integer, None]` - :param need_name: Pass True, if you require the user's full name to complete the order - :type need_name: :obj:`typing.Union[base.Boolean, None]` - :param need_phone_number: Pass True, if you require the user's phone number to complete the order - :type need_phone_number: :obj:`typing.Union[base.Boolean, None]` - :param need_email: Pass True, if you require the user's email to complete the order - :type need_email: :obj:`typing.Union[base.Boolean, None]` - :param need_shipping_address: Pass True, if you require the user's shipping address to complete the order - :type need_shipping_address: :obj:`typing.Union[base.Boolean, None]` - :param is_flexible: Pass True, if the final price depends on the shipping method - :type is_flexible: :obj:`typing.Union[base.Boolean, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: A JSON-serialized object for an inline keyboard - If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button. - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - prices = prepare_arg([price.to_python() if hasattr(price, 'to_python') else price for price in prices]) - reply_markup = prepare_arg(reply_markup) - payload_ = generate_payload(**locals()) - - result = await self.request(api.Methods.SEND_INVOICE, payload_) - return types.Message(**result) - - async def answer_shipping_query(self, shipping_query_id: base.String, ok: base.Boolean, - shipping_options: typing.Union[typing.List[types.ShippingOption], None] = None, - error_message: typing.Union[base.String, None] = None) -> base.Boolean: - """ - If you sent an invoice requesting a shipping address and the parameter is_flexible was specified, - the Bot API will send an Update with a shipping_query field to the bot. - - Source: https://core.telegram.org/bots/api#answershippingquery - - :param shipping_query_id: Unique identifier for the query to be answered - :type shipping_query_id: :obj:`base.String` - :param ok: Specify True if delivery to the specified address is possible and False if there are any problems - (for example, if delivery to the specified address is not possible) - :type ok: :obj:`base.Boolean` - :param shipping_options: Required if ok is True. A JSON-serialized array of available shipping options - :type shipping_options: :obj:`typing.Union[typing.List[types.ShippingOption], None]` - :param error_message: Required if ok is False - Error message in human readable form that explains why it is impossible to complete the order - (e.g. "Sorry, delivery to your desired address is unavailable'). - Telegram will display this message to the user. - :type error_message: :obj:`typing.Union[base.String, None]` - :return: On success, True is returned - :rtype: :obj:`base.Boolean` - """ - if shipping_options: - shipping_options = prepare_arg([shipping_option.to_python() - if hasattr(shipping_option, 'to_python') - else shipping_option - for shipping_option in shipping_options]) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.ANSWER_SHIPPING_QUERY, payload) - return result - - async def answer_pre_checkout_query(self, pre_checkout_query_id: base.String, ok: base.Boolean, - error_message: typing.Union[base.String, None] = None) -> base.Boolean: - """ - Once the user has confirmed their payment and shipping details, - the Bot API sends the final confirmation in the form of an Update with the field pre_checkout_query. - Use this method to respond to such pre-checkout queries. - - Source: https://core.telegram.org/bots/api#answerprecheckoutquery - - :param pre_checkout_query_id: Unique identifier for the query to be answered - :type pre_checkout_query_id: :obj:`base.String` - :param ok: Specify True if everything is alright (goods are available, etc.) and the - bot is ready to proceed with the order. Use False if there are any problems. - :type ok: :obj:`base.Boolean` - :param error_message: Required if ok is False - Error message in human readable form that explains the reason for failure to proceed with the checkout - (e.g. "Sorry, somebody just bought the last of our amazing black T-shirts while you were busy filling - out your payment details. Please choose a different color or garment!"). - Telegram will display this message to the user. - :type error_message: :obj:`typing.Union[base.String, None]` - :return: On success, True is returned - :rtype: :obj:`base.Boolean` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.ANSWER_PRE_CHECKOUT_QUERY, payload) - return result - - # === Games === - # https://core.telegram.org/bots/api#games - - async def set_passport_data_errors(self, - user_id: base.Integer, - errors: typing.List[types.PassportElementError]) -> base.Boolean: - """ - Informs a user that some of the Telegram Passport elements they provided contains errors. - The user will not be able to re-submit their Passport to you until the errors are fixed - (the contents of the field for which you returned the error must change). - Returns True on success. - - Use this if the data submitted by the user doesn't satisfy the standards your service - requires for any reason. For example, if a birthday date seems invalid, a submitted document - is blurry, a scan shows evidence of tampering, etc. Supply some details in the error message - to make sure the user knows how to correct the issues. - - Source https://core.telegram.org/bots/api#setpassportdataerrors - - :param user_id: User identifier - :type user_id: :obj:`base.Integer` - :param errors: A JSON-serialized array describing the errors - :type errors: :obj:`typing.List[types.PassportElementError]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - errors = prepare_arg(errors) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SET_PASSPORT_DATA_ERRORS, payload) - return result - - # === Games === - # https://core.telegram.org/bots/api#games - - async def send_game(self, chat_id: base.Integer, game_short_name: base.String, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_to_message_id: typing.Union[base.Integer, None] = None, - reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None) -> types.Message: - """ - Use this method to send a game. - - Source: https://core.telegram.org/bots/api#sendgame - - :param chat_id: Unique identifier for the target chat - :type chat_id: :obj:`base.Integer` - :param game_short_name: Short name of the game, serves as the unique identifier for the game. \ - Set up your games via Botfather. - :type game_short_name: :obj:`base.String` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :param reply_markup: A JSON-serialized object for an inline keyboard - If empty, one ‘Play game_title’ button will be shown. If not empty, the first button must launch the game. - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - reply_markup = prepare_arg(reply_markup) - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SEND_GAME, payload) - return types.Message(**result) - - async def set_game_score(self, user_id: base.Integer, score: base.Integer, - force: typing.Union[base.Boolean, None] = None, - disable_edit_message: typing.Union[base.Boolean, None] = None, - chat_id: typing.Union[base.Integer, None] = None, - message_id: typing.Union[base.Integer, None] = None, - inline_message_id: typing.Union[base.String, - None] = None) -> types.Message or base.Boolean: - """ - Use this method to set the score of the specified user in a game. - - Source: https://core.telegram.org/bots/api#setgamescore - - :param user_id: User identifier - :type user_id: :obj:`base.Integer` - :param score: New score, must be non-negative - :type score: :obj:`base.Integer` - :param force: Pass True, if the high score is allowed to decrease - This can be useful when fixing mistakes or banning cheaters - :type force: :obj:`typing.Union[base.Boolean, None]` - :param disable_edit_message: Pass True, if the game message should not be automatically - edited to include the current scoreboard - :type disable_edit_message: :obj:`typing.Union[base.Boolean, None]` - :param chat_id: Required if inline_message_id is not specified. Unique identifier for the target chat - :type chat_id: :obj:`typing.Union[base.Integer, None]` - :param message_id: Required if inline_message_id is not specified. Identifier of the sent message - :type message_id: :obj:`typing.Union[base.Integer, None]` - :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message - :type inline_message_id: :obj:`typing.Union[base.String, None]` - :return: On success, if the message was sent by the bot, returns the edited Message, otherwise returns True - Returns an error, if the new score is not greater than the user's - current score in the chat and force is False. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - payload = generate_payload(**locals()) - - result = await self.request(api.Methods.SET_GAME_SCORE, payload) - if isinstance(result, bool): - return result - return types.Message(**result) - - async def get_game_high_scores(self, user_id: base.Integer, - chat_id: typing.Union[base.Integer, None] = None, - message_id: typing.Union[base.Integer, None] = None, - inline_message_id: typing.Union[base.String, - None] = None) -> typing.List[types.GameHighScore]: - """ - Use this method to get data for high score tables. - - This method will currently return scores for the target user, plus two of his 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. - - Source: https://core.telegram.org/bots/api#getgamehighscores - - :param user_id: Target user id - :type user_id: :obj:`base.Integer` - :param chat_id: Required if inline_message_id is not specified. Unique identifier for the target chat - :type chat_id: :obj:`typing.Union[base.Integer, None]` - :param message_id: Required if inline_message_id is not specified. Identifier of the sent message - :type message_id: :obj:`typing.Union[base.Integer, None]` - :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message - :type inline_message_id: :obj:`typing.Union[base.String, None]` - :return: Will return the score of the specified user and several of his neighbors in a game - On success, returns an Array of GameHighScore objects. - This method will currently return scores for the target user, - plus two of his closest neighbors on each side. Will also return the top three users if the - user and his neighbors are not among them. - :rtype: :obj:`typing.List[types.GameHighScore]` - """ - payload = generate_payload(**locals()) - result = await self.request(api.Methods.GET_GAME_HIGH_SCORES, payload) - - return [types.GameHighScore(**gamehighscore) for gamehighscore in result] diff --git a/aiogram/contrib/__init__.py b/aiogram/contrib/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/aiogram/contrib/fsm_storage/__init__.py b/aiogram/contrib/fsm_storage/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/aiogram/contrib/fsm_storage/files.py b/aiogram/contrib/fsm_storage/files.py deleted file mode 100644 index ed9c7d57..00000000 --- a/aiogram/contrib/fsm_storage/files.py +++ /dev/null @@ -1,59 +0,0 @@ -import json -import pathlib -import pickle -import typing - -from .memory import MemoryStorage - - -class _FileStorage(MemoryStorage): - def __init__(self, path: typing.Union[pathlib.Path, str]): - """ - :param path: file path - """ - super(_FileStorage, self).__init__() - path = self.path = pathlib.Path(path) - - try: - self.data = self.read(path) - except FileNotFoundError: - pass - - async def close(self): - if self.data: - self.write(self.path) - await super(_FileStorage, self).close() - - def read(self, path: pathlib.Path): - raise NotImplementedError - - def write(self, path: pathlib.Path): - raise NotImplementedError - - -class JSONStorage(_FileStorage): - """ - JSON File storage based on MemoryStorage - """ - - def read(self, path: pathlib.Path): - with path.open("r") as f: - return json.load(f) - - def write(self, path: pathlib.Path): - with path.open("w") as f: - return json.dump(self.data, f, indent=4) - - -class PickleStorage(_FileStorage): - """ - Pickle File storage based on MemoryStorage - """ - - def read(self, path: pathlib.Path): - with path.open("rb") as f: - return pickle.load(f) - - def write(self, path: pathlib.Path): - with path.open("wb") as f: - return pickle.dump(self.data, f, protocol=pickle.HIGHEST_PROTOCOL) diff --git a/aiogram/contrib/fsm_storage/memory.py b/aiogram/contrib/fsm_storage/memory.py deleted file mode 100644 index 98bad524..00000000 --- a/aiogram/contrib/fsm_storage/memory.py +++ /dev/null @@ -1,131 +0,0 @@ -import copy -import typing - -from ...dispatcher.storage import BaseStorage - - -class MemoryStorage(BaseStorage): - """ - In-memory based states storage. - - This type of storage is not recommended for usage in bots, because you will lost all states after restarting. - """ - - async def wait_closed(self): - pass - - async def close(self): - self.data.clear() - - def __init__(self): - self.data = {} - - def resolve_address(self, chat, user): - chat_id, user_id = map(str, self.check_address(chat=chat, user=user)) - - if chat_id not in self.data: - self.data[chat_id] = {} - if user_id not in self.data[chat_id]: - self.data[chat_id][user_id] = {"state": None, "data": {}, "bucket": {}} - - return chat_id, user_id - - async def get_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None, - ) -> typing.Optional[str]: - chat, user = self.resolve_address(chat=chat, user=user) - return self.data[chat][user]["state"] - - async def get_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None, - ) -> typing.Dict: - chat, user = self.resolve_address(chat=chat, user=user) - return copy.deepcopy(self.data[chat][user]["data"]) - - async def update_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - data: typing.Dict = None, - **kwargs, - ): - if data is None: - data = {} - chat, user = self.resolve_address(chat=chat, user=user) - self.data[chat][user]["data"].update(data, **kwargs) - - async def set_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - state: typing.AnyStr = None, - ): - chat, user = self.resolve_address(chat=chat, user=user) - self.data[chat][user]["state"] = state - - async def set_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - data: typing.Dict = None, - ): - chat, user = self.resolve_address(chat=chat, user=user) - self.data[chat][user]["data"] = copy.deepcopy(data) - - async def reset_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - with_data: typing.Optional[bool] = True, - ): - await self.set_state(chat=chat, user=user, state=None) - if with_data: - await self.set_data(chat=chat, user=user, data={}) - - def has_bucket(self): - return True - - async def get_bucket( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[dict] = None, - ) -> typing.Dict: - chat, user = self.resolve_address(chat=chat, user=user) - return copy.deepcopy(self.data[chat][user]["bucket"]) - - async def set_bucket( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - bucket: typing.Dict = None, - ): - chat, user = self.resolve_address(chat=chat, user=user) - self.data[chat][user]["bucket"] = copy.deepcopy(bucket) - - async def update_bucket( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - bucket: typing.Dict = None, - **kwargs, - ): - if bucket is None: - bucket = {} - chat, user = self.resolve_address(chat=chat, user=user) - self.data[chat][user]["bucket"].update(bucket, **kwargs) diff --git a/aiogram/contrib/fsm_storage/mongo.py b/aiogram/contrib/fsm_storage/mongo.py deleted file mode 100644 index 9ec18090..00000000 --- a/aiogram/contrib/fsm_storage/mongo.py +++ /dev/null @@ -1,200 +0,0 @@ -""" -This module has mongo storage for finite-state machine - based on `aiomongo AioMongoClient: - if isinstance(self._mongo, AioMongoClient): - return self._mongo - - uri = 'mongodb://' - - # set username + password - if self._username and self._password: - uri += f'{self._username}:{self._password}@' - - # set host and port (optional) - uri += f'{self._host}:{self._port}' if self._host else f'localhost:{self._port}' - - # define and return client - self._mongo = await aiomongo.create_client(uri) - return self._mongo - - async def get_db(self) -> Database: - """ - Get Mongo db - - This property is awaitable. - """ - if isinstance(self._db, Database): - return self._db - - mongo = await self.get_client() - self._db = mongo.get_database(self._db_name) - - if self._index: - await self.apply_index(self._db) - return self._db - - @staticmethod - async def apply_index(db): - for collection in COLLECTIONS: - await db[collection].create_index(keys=[('chat', 1), ('user', 1)], - name="chat_user_idx", unique=True, background=True) - - async def close(self): - if self._mongo: - self._mongo.close() - - async def wait_closed(self): - if self._mongo: - return await self._mongo.wait_closed() - return True - - async def set_state(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None, - state: Optional[AnyStr] = None): - chat, user = self.check_address(chat=chat, user=user) - db = await self.get_db() - - if state is None: - await db[STATE].delete_one(filter={'chat': chat, 'user': user}) - else: - await db[STATE].update_one(filter={'chat': chat, 'user': user}, - update={'$set': {'state': state}}, upsert=True) - - async def get_state(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None, - default: Optional[str] = None) -> Optional[str]: - chat, user = self.check_address(chat=chat, user=user) - db = await self.get_db() - result = await db[STATE].find_one(filter={'chat': chat, 'user': user}) - - return result.get('state') if result else default - - async def set_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None, - data: Dict = None): - chat, user = self.check_address(chat=chat, user=user) - db = await self.get_db() - - await db[DATA].update_one(filter={'chat': chat, 'user': user}, - update={'$set': {'data': data}}, upsert=True) - - async def get_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None, - default: Optional[dict] = None) -> Dict: - chat, user = self.check_address(chat=chat, user=user) - db = await self.get_db() - result = await db[DATA].find_one(filter={'chat': chat, 'user': user}) - - return result.get('data') if result else default or {} - - async def update_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None, - data: Dict = None, **kwargs): - if data is None: - data = {} - temp_data = await self.get_data(chat=chat, user=user, default={}) - temp_data.update(data, **kwargs) - await self.set_data(chat=chat, user=user, data=temp_data) - - def has_bucket(self): - return True - - async def get_bucket(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None, - default: Optional[dict] = None) -> Dict: - chat, user = self.check_address(chat=chat, user=user) - db = await self.get_db() - result = await db[BUCKET].find_one(filter={'chat': chat, 'user': user}) - return result.get('bucket') if result else default or {} - - async def set_bucket(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None, - bucket: Dict = None): - chat, user = self.check_address(chat=chat, user=user) - db = await self.get_db() - - await db[BUCKET].update_one(filter={'chat': chat, 'user': user}, - update={'$set': {'bucket': bucket}}, upsert=True) - - async def update_bucket(self, *, chat: Union[str, int, None] = None, - user: Union[str, int, None] = None, - bucket: Dict = None, **kwargs): - if bucket is None: - bucket = {} - temp_bucket = await self.get_bucket(chat=chat, user=user) - temp_bucket.update(bucket, **kwargs) - await self.set_bucket(chat=chat, user=user, bucket=temp_bucket) - - async def reset_all(self, full=True): - """ - Reset states in DB - - :param full: clean DB or clean only states - :return: - """ - db = await self.get_db() - - await db[STATE].drop() - - if full: - await db[DATA].drop() - await db[BUCKET].drop() - - async def get_states_list(self) -> List[Tuple[int, int]]: - """ - Get list of all stored chat's and user's - - :return: list of tuples where first element is chat id and second is user id - """ - db = await self.get_db() - result = [] - - items = await db[STATE].find().to_list() - for item in items: - result.append( - (int(item['chat']), int(item['user'])) - ) - - return result diff --git a/aiogram/contrib/fsm_storage/redis.py b/aiogram/contrib/fsm_storage/redis.py deleted file mode 100644 index 106a7b97..00000000 --- a/aiogram/contrib/fsm_storage/redis.py +++ /dev/null @@ -1,405 +0,0 @@ -""" -This module has redis storage for finite-state machine based on `aioredis `_ driver -""" - -import asyncio -import logging -import typing - -import aioredis - -from ...dispatcher.storage import BaseStorage -from ...utils import json - -STATE_KEY = 'state' -STATE_DATA_KEY = 'data' -STATE_BUCKET_KEY = 'bucket' - - -class RedisStorage(BaseStorage): - """ - Simple Redis-base storage for FSM. - - Usage: - - .. code-block:: python3 - - storage = RedisStorage('localhost', 6379, db=5) - dp = Dispatcher(bot, storage=storage) - - And need to close Redis connection when shutdown - - .. code-block:: python3 - - await dp.storage.close() - await dp.storage.wait_closed() - - """ - def __init__(self, host='localhost', port=6379, db=None, password=None, ssl=None, loop=None, **kwargs): - self._host = host - self._port = port - self._db = db - self._password = password - self._ssl = ssl - self._loop = loop or asyncio.get_event_loop() - self._kwargs = kwargs - - self._redis: aioredis.RedisConnection = None - self._connection_lock = asyncio.Lock(loop=self._loop) - - async def close(self): - if self._redis and not self._redis.closed: - self._redis.close() - del self._redis - self._redis = None - - async def wait_closed(self): - if self._redis: - return await self._redis.wait_closed() - return True - - async def redis(self) -> aioredis.RedisConnection: - """ - Get Redis connection - """ - # Use thread-safe asyncio Lock because this method without that is not safe - async with self._connection_lock: - if self._redis is None: - self._redis = await aioredis.create_connection((self._host, self._port), - db=self._db, password=self._password, ssl=self._ssl, - loop=self._loop, - **self._kwargs) - return self._redis - - async def get_record(self, *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None) -> typing.Dict: - """ - Get record from storage - - :param chat: - :param user: - :return: - """ - chat, user = self.check_address(chat=chat, user=user) - addr = f"fsm:{chat}:{user}" - - conn = await self.redis() - data = await conn.execute('GET', addr) - if data is None: - return {'state': None, 'data': {}} - return json.loads(data) - - async def set_record(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - state=None, data=None, bucket=None): - """ - Write record to storage - - :param bucket: - :param chat: - :param user: - :param state: - :param data: - :return: - """ - if data is None: - data = {} - if bucket is None: - bucket = {} - - chat, user = self.check_address(chat=chat, user=user) - addr = f"fsm:{chat}:{user}" - - record = {'state': state, 'data': data, 'bucket': bucket} - - conn = await self.redis() - await conn.execute('SET', addr, json.dumps(record)) - - async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None) -> typing.Optional[str]: - record = await self.get_record(chat=chat, user=user) - return record['state'] - - async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None) -> typing.Dict: - record = await self.get_record(chat=chat, user=user) - return record['data'] - - async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - state: typing.Optional[typing.AnyStr] = None): - record = await self.get_record(chat=chat, user=user) - await self.set_record(chat=chat, user=user, state=state, data=record['data']) - - async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - data: typing.Dict = None): - record = await self.get_record(chat=chat, user=user) - await self.set_record(chat=chat, user=user, state=record['state'], data=data) - - async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - data: typing.Dict = None, **kwargs): - if data is None: - data = {} - record = await self.get_record(chat=chat, user=user) - record_data = record.get('data', {}) - record_data.update(data, **kwargs) - await self.set_record(chat=chat, user=user, state=record['state'], data=record_data) - - async def get_states_list(self) -> typing.List[typing.Tuple[int]]: - """ - Get list of all stored chat's and user's - - :return: list of tuples where first element is chat id and second is user id - """ - conn = await self.redis() - result = [] - - keys = await conn.execute('KEYS', 'fsm:*') - for item in keys: - *_, chat, user = item.decode('utf-8').split(':') - result.append((chat, user)) - - return result - - async def reset_all(self, full=True): - """ - Reset states in DB - - :param full: clean DB or clean only states - :return: - """ - conn = await self.redis() - - if full: - await conn.execute('FLUSHDB') - else: - keys = await conn.execute('KEYS', 'fsm:*') - await conn.execute('DEL', *keys) - - def has_bucket(self): - return True - - async def get_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None) -> typing.Dict: - record = await self.get_record(chat=chat, user=user) - return record.get('bucket', {}) - - async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - bucket: typing.Dict = None): - record = await self.get_record(chat=chat, user=user) - await self.set_record(chat=chat, user=user, state=record['state'], data=record['data'], bucket=bucket) - - async def update_bucket(self, *, chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - bucket: typing.Dict = None, **kwargs): - record = await self.get_record(chat=chat, user=user) - record_bucket = record.get('bucket', {}) - if bucket is None: - bucket = {} - record_bucket.update(bucket, **kwargs) - await self.set_record(chat=chat, user=user, state=record['state'], data=record_bucket, bucket=bucket) - - -class RedisStorage2(BaseStorage): - """ - Busted Redis-base storage for FSM. - Works with Redis connection pool and customizable keys prefix. - - Usage: - - .. code-block:: python3 - - storage = RedisStorage('localhost', 6379, db=5, pool_size=10, prefix='my_fsm_key') - dp = Dispatcher(bot, storage=storage) - - And need to close Redis connection when shutdown - - .. code-block:: python3 - - await dp.storage.close() - await dp.storage.wait_closed() - - """ - def __init__(self, host: str = 'localhost', port=6379, db=None, password=None, - ssl=None, pool_size=10, loop=None, prefix='fsm', - state_ttl: int = 0, - data_ttl: int = 0, - bucket_ttl: int = 0, - **kwargs): - self._host = host - self._port = port - self._db = db - self._password = password - self._ssl = ssl - self._pool_size = pool_size - self._loop = loop or asyncio.get_event_loop() - self._kwargs = kwargs - self._prefix = (prefix,) - - self._state_ttl = state_ttl - self._data_ttl = data_ttl - self._bucket_ttl = bucket_ttl - - self._redis: aioredis.RedisConnection = None - self._connection_lock = asyncio.Lock(loop=self._loop) - - async def redis(self) -> aioredis.Redis: - """ - Get Redis connection - """ - # Use thread-safe asyncio Lock because this method without that is not safe - async with self._connection_lock: - if self._redis is None: - self._redis = await aioredis.create_redis_pool((self._host, self._port), - db=self._db, password=self._password, ssl=self._ssl, - minsize=1, maxsize=self._pool_size, - loop=self._loop, **self._kwargs) - return self._redis - - def generate_key(self, *parts): - return ':'.join(self._prefix + tuple(map(str, parts))) - - async def close(self): - async with self._connection_lock: - if self._redis and not self._redis.closed: - self._redis.close() - del self._redis - self._redis = None - - async def wait_closed(self): - async with self._connection_lock: - if self._redis: - return await self._redis.wait_closed() - return True - - async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None) -> typing.Optional[str]: - chat, user = self.check_address(chat=chat, user=user) - key = self.generate_key(chat, user, STATE_KEY) - redis = await self.redis() - return await redis.get(key, encoding='utf8') or None - - async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - default: typing.Optional[dict] = None) -> typing.Dict: - chat, user = self.check_address(chat=chat, user=user) - key = self.generate_key(chat, user, STATE_DATA_KEY) - redis = await self.redis() - raw_result = await redis.get(key, encoding='utf8') - if raw_result: - return json.loads(raw_result) - return default or {} - - async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - state: typing.Optional[typing.AnyStr] = None): - chat, user = self.check_address(chat=chat, user=user) - key = self.generate_key(chat, user, STATE_KEY) - redis = await self.redis() - if state is None: - await redis.delete(key) - else: - await redis.set(key, state, expire=self._state_ttl) - - async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - data: typing.Dict = None): - chat, user = self.check_address(chat=chat, user=user) - key = self.generate_key(chat, user, STATE_DATA_KEY) - redis = await self.redis() - await redis.set(key, json.dumps(data), expire=self._data_ttl) - - async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - data: typing.Dict = None, **kwargs): - if data is None: - data = {} - temp_data = await self.get_data(chat=chat, user=user, default={}) - temp_data.update(data, **kwargs) - await self.set_data(chat=chat, user=user, data=temp_data) - - def has_bucket(self): - return True - - async def get_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - default: typing.Optional[dict] = None) -> typing.Dict: - chat, user = self.check_address(chat=chat, user=user) - key = self.generate_key(chat, user, STATE_BUCKET_KEY) - redis = await self.redis() - raw_result = await redis.get(key, encoding='utf8') - if raw_result: - return json.loads(raw_result) - return default or {} - - async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, - bucket: typing.Dict = None): - chat, user = self.check_address(chat=chat, user=user) - key = self.generate_key(chat, user, STATE_BUCKET_KEY) - redis = await self.redis() - await redis.set(key, json.dumps(bucket), expire=self._bucket_ttl) - - async def update_bucket(self, *, chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - bucket: typing.Dict = None, **kwargs): - if bucket is None: - bucket = {} - temp_bucket = await self.get_bucket(chat=chat, user=user) - temp_bucket.update(bucket, **kwargs) - await self.set_bucket(chat=chat, user=user, bucket=temp_bucket) - - async def reset_all(self, full=True): - """ - Reset states in DB - - :param full: clean DB or clean only states - :return: - """ - conn = await self.redis() - - if full: - await conn.flushdb() - else: - keys = await conn.keys(self.generate_key('*')) - await conn.delete(*keys) - - async def get_states_list(self) -> typing.List[typing.Tuple[int]]: - """ - Get list of all stored chat's and user's - - :return: list of tuples where first element is chat id and second is user id - """ - conn = await self.redis() - result = [] - - keys = await conn.keys(self.generate_key('*', '*', STATE_KEY), encoding='utf8') - for item in keys: - *_, chat, user, _ = item.split(':') - result.append((chat, user)) - - return result - - async def import_redis1(self, redis1): - await migrate_redis1_to_redis2(redis1, self) - - -async def migrate_redis1_to_redis2(storage1: RedisStorage, storage2: RedisStorage2): - """ - Helper for migrating from RedisStorage to RedisStorage2 - - :param storage1: instance of RedisStorage - :param storage2: instance of RedisStorage2 - :return: - """ - if not isinstance(storage1, RedisStorage): # better than assertion - raise TypeError(f"{type(storage1)} is not RedisStorage instance.") - if not isinstance(storage2, RedisStorage): - raise TypeError(f"{type(storage2)} is not RedisStorage instance.") - - log = logging.getLogger('aiogram.RedisStorage') - - for chat, user in await storage1.get_states_list(): - state = await storage1.get_state(chat=chat, user=user) - await storage2.set_state(chat=chat, user=user, state=state) - - data = await storage1.get_data(chat=chat, user=user) - await storage2.set_data(chat=chat, user=user, data=data) - - bucket = await storage1.get_bucket(chat=chat, user=user) - await storage2.set_bucket(chat=chat, user=user, bucket=bucket) - - log.info(f"Migrated user {user} in chat {chat}") diff --git a/aiogram/contrib/fsm_storage/rethinkdb.py b/aiogram/contrib/fsm_storage/rethinkdb.py deleted file mode 100644 index d669cfff..00000000 --- a/aiogram/contrib/fsm_storage/rethinkdb.py +++ /dev/null @@ -1,242 +0,0 @@ -import asyncio -import contextlib -import typing - -import rethinkdb -from rethinkdb.asyncio_net.net_asyncio import Connection - -from ...dispatcher.storage import BaseStorage - -__all__ = ["RethinkDBStorage"] - -r = rethinkdb.RethinkDB() -r.set_loop_type("asyncio") - - -class RethinkDBStorage(BaseStorage): - """ - RethinkDB-based storage for FSM. - - Usage: - - ..code-block:: python3 - - storage = RethinkDBStorage(db='aiogram', table='aiogram', user='aiogram', password='aiogram_secret') - dispatcher = Dispatcher(bot, storage=storage) - - And need to close connection when shutdown - - ..code-clock:: python3 - - await storage.close() - - """ - - def __init__( - self, - host: str = "localhost", - port: int = 28015, - db: str = "aiogram", - table: str = "aiogram", - auth_key: typing.Optional[str] = None, - user: typing.Optional[str] = None, - password: typing.Optional[str] = None, - timeout: int = 20, - ssl: typing.Optional[dict] = None, - loop: typing.Optional[asyncio.AbstractEventLoop] = None, - ): - self._host = host - self._port = port - self._db = db - self._table = table - self._auth_key = auth_key - self._user = user - self._password = password - self._timeout = timeout - self._ssl = ssl or {} - self._loop = loop - - self._conn: typing.Union[Connection, None] = None - - async def connect(self) -> Connection: - """ - Get or create a connection. - """ - if self._conn is None: - self._conn = await r.connect( - host=self._host, - port=self._port, - db=self._db, - auth_key=self._auth_key, - user=self._user, - password=self._password, - timeout=self._timeout, - ssl=self._ssl, - io_loop=self._loop, - ) - return self._conn - - @contextlib.asynccontextmanager - async def connection(self): - conn = await self.connect() - yield conn - - async def close(self): - """ - Close a connection. - """ - self._conn.close() - self._conn = None - - async def wait_closed(self): - """ - Does nothing - """ - pass - - async def get_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None, - ) -> typing.Optional[str]: - chat, user = map(str, self.check_address(chat=chat, user=user)) - async with self.connection() as conn: - return ( - await r.table(self._table) - .get(chat)[user]["state"] - .default(default or None) - .run(conn) - ) - - async def get_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None, - ) -> typing.Dict: - chat, user = map(str, self.check_address(chat=chat, user=user)) - async with self.connection() as conn: - return ( - await r.table(self._table).get(chat)[user]["data"].default(default or {}).run(conn) - ) - - async def set_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - state: typing.Optional[typing.AnyStr] = None, - ): - chat, user = map(str, self.check_address(chat=chat, user=user)) - async with self.connection() as conn: - await r.table(self._table).insert( - {"id": chat, user: {"state": state}}, conflict="update" - ).run(conn) - - async def set_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - data: typing.Dict = None, - ): - chat, user = map(str, self.check_address(chat=chat, user=user)) - async with self.connection() as conn: - if await r.table(self._table).get(chat).run(conn): - await r.table(self._table).get(chat).update({user: {"data": r.literal(data)}}).run( - conn - ) - else: - await r.table(self._table).insert({"id": chat, user: {"data": data}}).run(conn) - - async def update_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - data: typing.Dict = None, - **kwargs, - ): - chat, user = map(str, self.check_address(chat=chat, user=user)) - async with self.connection() as conn: - await r.table(self._table).insert( - {"id": chat, user: {"data": data}}, conflict="update" - ).run(conn) - - def has_bucket(self): - return True - - async def get_bucket( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[dict] = None, - ) -> typing.Dict: - chat, user = map(str, self.check_address(chat=chat, user=user)) - async with self.connection() as conn: - return ( - await r.table(self._table) - .get(chat)[user]["bucket"] - .default(default or {}) - .run(conn) - ) - - async def set_bucket( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - bucket: typing.Dict = None, - ): - chat, user = map(str, self.check_address(chat=chat, user=user)) - async with self.connection() as conn: - if await r.table(self._table).get(chat).run(conn): - await r.table(self._table).get(chat).update( - {user: {"bucket": r.literal(bucket)}} - ).run(conn) - else: - await r.table(self._table).insert({"id": chat, user: {"bucket": bucket}}).run(conn) - - async def update_bucket( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - bucket: typing.Dict = None, - **kwargs, - ): - chat, user = map(str, self.check_address(chat=chat, user=user)) - async with self.connection() as conn: - await r.table(self._table).insert( - {"id": chat, user: {"bucket": bucket}}, conflict="update" - ).run(conn) - - async def get_states_list(self) -> typing.List[typing.Tuple[int, int]]: - """ - Get list of all stored chat's and user's - - :return: list of tuples where first element is chat id and second is user id - """ - async with self.connection() as conn: - result = [] - - items = (await r.table(self._table).run(conn)).items - - for item in items: - chat = int(item.pop("id")) - for key in item.keys(): - user = int(key) - result.append((chat, user)) - - return result - - async def reset_all(self): - """ - Reset states in DB - """ - async with self.connection() as conn: - await r.table(self._table).delete().run(conn) diff --git a/aiogram/contrib/middlewares/__init__.py b/aiogram/contrib/middlewares/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/aiogram/contrib/middlewares/environment.py b/aiogram/contrib/middlewares/environment.py deleted file mode 100644 index 3b08c507..00000000 --- a/aiogram/contrib/middlewares/environment.py +++ /dev/null @@ -1,21 +0,0 @@ -from aiogram.dispatcher.middlewares import BaseMiddleware - - -class EnvironmentMiddleware(BaseMiddleware): - def __init__(self, context=None): - super(EnvironmentMiddleware, self).__init__() - - if context is None: - context = {} - self.context = context - - def update_data(self, data): - dp = self.manager.dispatcher - data.update(bot=dp.bot, dispatcher=dp, loop=dp.loop) - if self.context: - data.update(self.context) - - async def trigger(self, action, args): - if "error" not in action and action.startswith("pre_process_"): - self.update_data(args[-1]) - return True diff --git a/aiogram/contrib/middlewares/fsm.py b/aiogram/contrib/middlewares/fsm.py deleted file mode 100644 index 0823bfbd..00000000 --- a/aiogram/contrib/middlewares/fsm.py +++ /dev/null @@ -1,80 +0,0 @@ -import copy -import weakref - -from aiogram.dispatcher.middlewares import LifetimeControllerMiddleware -from aiogram.dispatcher.storage import FSMContext - - -class FSMMiddleware(LifetimeControllerMiddleware): - skip_patterns = ["error", "update"] - - def __init__(self): - super(FSMMiddleware, self).__init__() - self._proxies = weakref.WeakKeyDictionary() - - async def pre_process(self, obj, data, *args): - proxy = await FSMSStorageProxy.create(self.manager.dispatcher.current_state()) - data["state_data"] = proxy - - async def post_process(self, obj, data, *args): - proxy = data.get("state_data", None) - if isinstance(proxy, FSMSStorageProxy): - await proxy.save() - - -class FSMSStorageProxy(dict): - def __init__(self, fsm_context: FSMContext): - super(FSMSStorageProxy, self).__init__() - self.fsm_context = fsm_context - self._copy = {} - self._data = {} - self._state = None - self._is_dirty = False - - @classmethod - async def create(cls, fsm_context: FSMContext): - """ - :param fsm_context: - :return: - """ - proxy = cls(fsm_context) - await proxy.load() - return proxy - - async def load(self): - self.clear() - self._state = await self.fsm_context.get_state() - self.update(await self.fsm_context.get_data()) - self._copy = copy.deepcopy(self) - self._is_dirty = False - - @property - def state(self): - return self._state - - @state.setter - def state(self, value): - self._state = value - self._is_dirty = True - - @state.deleter - def state(self): - self._state = None - self._is_dirty = True - - async def save(self, force=False): - if self._copy != self or force: - await self.fsm_context.set_data(data=self) - if self._is_dirty or force: - await self.fsm_context.set_state(self.state) - self._is_dirty = False - self._copy = copy.deepcopy(self) - - def __str__(self): - s = super(FSMSStorageProxy, self).__str__() - readable_state = f"'{self.state}'" if self.state else "''" - return f"<{self.__class__.__name__}(state={readable_state}, data={s})>" - - def clear(self): - del self.state - return super(FSMSStorageProxy, self).clear() diff --git a/aiogram/contrib/middlewares/i18n.py b/aiogram/contrib/middlewares/i18n.py deleted file mode 100644 index 610edffa..00000000 --- a/aiogram/contrib/middlewares/i18n.py +++ /dev/null @@ -1,150 +0,0 @@ -import gettext -import os -from contextvars import ContextVar -from typing import Any, Dict, Tuple - -from babel import Locale -from babel.support import LazyProxy - -from ... import types -from ...dispatcher.middlewares import BaseMiddleware - - -class I18nMiddleware(BaseMiddleware): - """ - I18n middleware based on gettext util - - >>> dp = Dispatcher(bot) - >>> i18n = I18nMiddleware(DOMAIN, LOCALES_DIR) - >>> dp.middleware.setup(i18n) - and then - >>> _ = i18n.gettext - or - >>> _ = i18n = I18nMiddleware(DOMAIN_NAME, LOCALES_DIR) - """ - - ctx_locale = ContextVar("ctx_user_locale", default=None) - - def __init__(self, domain, path=None, default="en"): - """ - :param domain: domain - :param path: path where located all *.mo files - :param default: default locale name - """ - super(I18nMiddleware, self).__init__() - - if path is None: - path = os.path.join(os.getcwd(), "locales") - - self.domain = domain - self.path = path - self.default = default - - self.locales = self.find_locales() - - def find_locales(self) -> Dict[str, gettext.GNUTranslations]: - """ - Load all compiled locales from path - - :return: dict with locales - """ - translations = {} - - for name in os.listdir(self.path): - if not os.path.isdir(os.path.join(self.path, name)): - continue - mo_path = os.path.join(self.path, name, "LC_MESSAGES", self.domain + ".mo") - - if os.path.exists(mo_path): - with open(mo_path, "rb") as fp: - translations[name] = gettext.GNUTranslations(fp) - elif os.path.exists(mo_path[:-2] + "po"): - raise RuntimeError(f"Found locale '{name} but this language is not compiled!") - - return translations - - def reload(self): - """ - Hot reload locles - """ - self.locales = self.find_locales() - - @property - def available_locales(self) -> Tuple[str]: - """ - list of loaded locales - - :return: - """ - return tuple(self.locales.keys()) - - def __call__(self, singular, plural=None, n=1, locale=None) -> str: - return self.gettext(singular, plural, n, locale) - - def gettext(self, singular, plural=None, n=1, locale=None) -> str: - """ - Get text - - :param singular: - :param plural: - :param n: - :param locale: - :return: - """ - if locale is None: - locale = self.ctx_locale.get() - - if locale not in self.locales: - if n is 1: - return singular - return plural - - translator = self.locales[locale] - - if plural is None: - return translator.gettext(singular) - return translator.ngettext(singular, plural, n) - - def lazy_gettext(self, singular, plural=None, n=1, locale=None, enable_cache=False) -> LazyProxy: - """ - Lazy get text - - :param singular: - :param plural: - :param n: - :param locale: - :param enable_cache: - :return: - """ - return LazyProxy(self.gettext, singular, plural, n, locale, enable_cache=enable_cache) - - # noinspection PyMethodMayBeStatic,PyUnusedLocal - async def get_user_locale(self, action: str, args: Tuple[Any]) -> str: - """ - User locale getter - You can override the method if you want to use different way of getting user language. - - :param action: event name - :param args: event arguments - :return: locale name - """ - user: types.User = types.User.get_current() - locale: Locale = user.locale - - if locale: - *_, data = args - language = data["locale"] = locale.language - return language - - async def trigger(self, action, args): - """ - Event trigger - - :param action: event name - :param args: event arguments - :return: - """ - if "update" not in action and "error" not in action and action.startswith("pre_process"): - locale = await self.get_user_locale(action, args) - self.ctx_locale.set(locale) - return True diff --git a/aiogram/contrib/middlewares/logging.py b/aiogram/contrib/middlewares/logging.py deleted file mode 100644 index 9f389b60..00000000 --- a/aiogram/contrib/middlewares/logging.py +++ /dev/null @@ -1,427 +0,0 @@ -import time - -import logging - -from aiogram import types -from aiogram.dispatcher.middlewares import BaseMiddleware - -HANDLED_STR = ['Unhandled', 'Handled'] - - -class LoggingMiddleware(BaseMiddleware): - def __init__(self, logger=__name__): - if not isinstance(logger, logging.Logger): - logger = logging.getLogger(logger) - - self.logger = logger - - super(LoggingMiddleware, self).__init__() - - def check_timeout(self, obj): - start = obj.conf.get('_start', None) - if start: - del obj.conf['_start'] - return round((time.time() - start) * 1000) - return -1 - - async def on_pre_process_update(self, update: types.Update, data: dict): - update.conf['_start'] = time.time() - self.logger.debug(f"Received update [ID:{update.update_id}]") - - async def on_post_process_update(self, update: types.Update, result, data: dict): - timeout = self.check_timeout(update) - if timeout > 0: - self.logger.info(f"Process update [ID:{update.update_id}]: [success] (in {timeout} ms)") - - async def on_pre_process_message(self, message: types.Message, data: dict): - self.logger.info(f"Received message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]") - - async def on_post_process_message(self, message: types.Message, results, data: dict): - self.logger.debug(f"{HANDLED_STR[bool(len(results))]} " - f"message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]") - - async def on_pre_process_edited_message(self, edited_message, data: dict): - self.logger.info(f"Received edited message [ID:{edited_message.message_id}] " - f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]") - - async def on_post_process_edited_message(self, edited_message, results, data: dict): - self.logger.debug(f"{HANDLED_STR[bool(len(results))]} " - f"edited message [ID:{edited_message.message_id}] " - f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]") - - async def on_pre_process_channel_post(self, channel_post: types.Message, data: dict): - self.logger.info(f"Received channel post [ID:{channel_post.message_id}] " - f"in channel [ID:{channel_post.chat.id}]") - - async def on_post_process_channel_post(self, channel_post: types.Message, results, data: dict): - self.logger.debug(f"{HANDLED_STR[bool(len(results))]} " - f"channel post [ID:{channel_post.message_id}] " - f"in chat [{channel_post.chat.type}:{channel_post.chat.id}]") - - async def on_pre_process_edited_channel_post(self, edited_channel_post: types.Message, data: dict): - self.logger.info(f"Received edited channel post [ID:{edited_channel_post.message_id}] " - f"in channel [ID:{edited_channel_post.chat.id}]") - - async def on_post_process_edited_channel_post(self, edited_channel_post: types.Message, results, data: dict): - self.logger.debug(f"{HANDLED_STR[bool(len(results))]} " - f"edited channel post [ID:{edited_channel_post.message_id}] " - f"in channel [ID:{edited_channel_post.chat.id}]") - - async def on_pre_process_inline_query(self, inline_query: types.InlineQuery, data: dict): - self.logger.info(f"Received inline query [ID:{inline_query.id}] " - f"from user [ID:{inline_query.from_user.id}]") - - async def on_post_process_inline_query(self, inline_query: types.InlineQuery, results, data: dict): - self.logger.debug(f"{HANDLED_STR[bool(len(results))]} " - f"inline query [ID:{inline_query.id}] " - f"from user [ID:{inline_query.from_user.id}]") - - async def on_pre_process_chosen_inline_result(self, chosen_inline_result: types.ChosenInlineResult, data: dict): - self.logger.info(f"Received chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] " - f"from user [ID:{chosen_inline_result.from_user.id}] " - f"result [ID:{chosen_inline_result.result_id}]") - - async def on_post_process_chosen_inline_result(self, chosen_inline_result, results, data: dict): - self.logger.debug(f"{HANDLED_STR[bool(len(results))]} " - f"chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] " - f"from user [ID:{chosen_inline_result.from_user.id}] " - f"result [ID:{chosen_inline_result.result_id}]") - - async def on_pre_process_callback_query(self, callback_query: types.CallbackQuery, data: dict): - if callback_query.message: - text = (f"Received callback query [ID:{callback_query.id}] " - f"from user [ID:{callback_query.from_user.id}] " - f"for message [ID:{callback_query.message.message_id}] " - f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]") - - if callback_query.message.from_user: - text += f" originally posted by user [ID:{callback_query.message.from_user.id}]" - - self.logger.info(text) - - else: - self.logger.info(f"Received callback query [ID:{callback_query.id}] " - f"from user [ID:{callback_query.from_user.id}] " - f"for inline message [ID:{callback_query.inline_message_id}] ") - - async def on_post_process_callback_query(self, callback_query, results, data: dict): - if callback_query.message: - text = (f"{HANDLED_STR[bool(len(results))]} " - f"callback query [ID:{callback_query.id}] " - f"from user [ID:{callback_query.from_user.id}] " - f"for message [ID:{callback_query.message.message_id}] " - f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]") - - if callback_query.message.from_user: - text += f" originally posted by user [ID:{callback_query.message.from_user.id}]" - - self.logger.info(text) - - else: - self.logger.debug(f"{HANDLED_STR[bool(len(results))]} " - f"callback query [ID:{callback_query.id}] " - f"from user [ID:{callback_query.from_user.id}]" - f"from inline message [ID:{callback_query.inline_message_id}]") - - async def on_pre_process_shipping_query(self, shipping_query: types.ShippingQuery, data: dict): - self.logger.info(f"Received shipping query [ID:{shipping_query.id}] " - f"from user [ID:{shipping_query.from_user.id}]") - - async def on_post_process_shipping_query(self, shipping_query, results, data: dict): - self.logger.debug(f"{HANDLED_STR[bool(len(results))]} " - f"shipping query [ID:{shipping_query.id}] " - f"from user [ID:{shipping_query.from_user.id}]") - - async def on_pre_process_pre_checkout_query(self, pre_checkout_query: types.PreCheckoutQuery, data: dict): - self.logger.info(f"Received pre-checkout query [ID:{pre_checkout_query.id}] " - f"from user [ID:{pre_checkout_query.from_user.id}]") - - async def on_post_process_pre_checkout_query(self, pre_checkout_query, results, data: dict): - self.logger.debug(f"{HANDLED_STR[bool(len(results))]} " - f"pre-checkout query [ID:{pre_checkout_query.id}] " - f"from user [ID:{pre_checkout_query.from_user.id}]") - - async def on_pre_process_error(self, update, error, data: dict): - timeout = self.check_timeout(update) - if timeout > 0: - self.logger.info(f"Process update [ID:{update.update_id}]: [failed] (in {timeout} ms)") - - -class LoggingFilter(logging.Filter): - """ - Extend LogRecord by data from Telegram Update object. - - Can be used in logging config: - .. code-block: python3 - - 'filters': { - 'telegram': { - '()': LoggingFilter, - 'include_content': True, - } - }, - ... - 'handlers': { - 'graypy': { - '()': GELFRabbitHandler, - 'url': 'amqp://localhost:5672/', - 'routing_key': '#', - 'localname': 'testapp', - 'filters': ['telegram'] - }, - }, - - """ - - def __init__(self, name='', prefix='tg', include_content=False): - """ - :param name: - :param prefix: prefix for all records - :param include_content: pass into record all data from Update object - """ - super(LoggingFilter, self).__init__(name=name) - - self.prefix = prefix - self.include_content = include_content - - def filter(self, record: logging.LogRecord): - """ - Extend LogRecord by data from Telegram Update object. - - :param record: - :return: - """ - update = types.Update.get_current(True) - if update: - for key, value in self.make_prefix(self.prefix, self.process_update(update)): - setattr(record, key, value) - - return True - - def process_update(self, update: types.Update): - """ - Parse Update object - - :param update: - :return: - """ - yield 'update_id', update.update_id - - if update.message: - yield 'update_type', 'message' - yield from self.process_message(update.message) - if update.edited_message: - yield 'update_type', 'edited_message' - yield from self.process_message(update.edited_message) - if update.channel_post: - yield 'update_type', 'channel_post' - yield from self.process_message(update.channel_post) - if update.edited_channel_post: - yield 'update_type', 'edited_channel_post' - yield from self.process_message(update.edited_channel_post) - if update.inline_query: - yield 'update_type', 'inline_query' - yield from self.process_inline_query(update.inline_query) - if update.chosen_inline_result: - yield 'update_type', 'chosen_inline_result' - yield from self.process_chosen_inline_result(update.chosen_inline_result) - if update.callback_query: - yield 'update_type', 'callback_query' - yield from self.process_callback_query(update.callback_query) - if update.shipping_query: - yield 'update_type', 'shipping_query' - yield from self.process_shipping_query(update.shipping_query) - if update.pre_checkout_query: - yield 'update_type', 'pre_checkout_query' - yield from self.process_pre_checkout_query(update.pre_checkout_query) - - def make_prefix(self, prefix, iterable): - """ - Add prefix to the label - - :param prefix: - :param iterable: - :return: - """ - if not prefix: - yield from iterable - - for key, value in iterable: - yield f"{prefix}_{key}", value - - def process_user(self, user: types.User): - """ - Generate user data - - :param user: - :return: - """ - if not user: - return - - yield 'user_id', user.id - if self.include_content: - yield 'user_full_name', user.full_name - if user.username: - yield 'user_name', f"@{user.username}" - - def process_chat(self, chat: types.Chat): - """ - Generate chat data - - :param chat: - :return: - """ - if not chat: - return - - yield 'chat_id', chat.id - yield 'chat_type', chat.type - if self.include_content: - yield 'chat_title', chat.full_name - if chat.username: - yield 'chat_name', f"@{chat.username}" - - def process_message(self, message: types.Message): - yield 'message_content_type', message.content_type - yield from self.process_user(message.from_user) - yield from self.process_chat(message.chat) - - if not self.include_content: - return - - if message.reply_to_message: - yield from self.make_prefix('reply_to', self.process_message(message.reply_to_message)) - if message.forward_from: - yield from self.make_prefix('forward_from', self.process_user(message.forward_from)) - if message.forward_from_chat: - yield from self.make_prefix('forward_from_chat', self.process_chat(message.forward_from_chat)) - if message.forward_from_message_id: - yield 'message_forward_from_message_id', message.forward_from_message_id - if message.forward_date: - yield 'message_forward_date', message.forward_date - if message.edit_date: - yield 'message_edit_date', message.edit_date - if message.media_group_id: - yield 'message_media_group_id', message.media_group_id - if message.author_signature: - yield 'message_author_signature', message.author_signature - - if message.text: - yield 'text', message.text or message.caption - yield 'html_text', message.html_text - elif message.audio: - yield 'audio', message.audio.file_id - elif message.animation: - yield 'animation', message.animation.file_id - elif message.document: - yield 'document', message.document.file_id - elif message.game: - yield 'game', message.game.title - elif message.photo: - yield 'photo', message.photo[-1].file_id - elif message.sticker: - yield 'sticker', message.sticker.file_id - elif message.video: - yield 'video', message.video.file_id - elif message.video_note: - yield 'video_note', message.video_note.file_id - elif message.voice: - yield 'voice', message.voice.file_id - elif message.contact: - yield 'contact_full_name', message.contact.full_name - yield 'contact_phone_number', message.contact.phone_number - elif message.venue: - yield 'venue_address', message.venue.address - yield 'location_latitude', message.venue.location.latitude - yield 'location_longitude', message.venue.location.longitude - elif message.location: - yield 'location_latitude', message.location.latitude - yield 'location_longitude', message.location.longitude - elif message.new_chat_members: - yield 'new_chat_members', [user.id for user in message.new_chat_members] - elif message.left_chat_member: - yield 'left_chat_member', [user.id for user in message.new_chat_members] - elif message.invoice: - yield 'invoice_title', message.invoice.title - yield 'invoice_description', message.invoice.description - yield 'invoice_start_parameter', message.invoice.start_parameter - yield 'invoice_currency', message.invoice.currency - yield 'invoice_total_amount', message.invoice.total_amount - elif message.successful_payment: - yield 'successful_payment_currency', message.successful_payment.currency - yield 'successful_payment_total_amount', message.successful_payment.total_amount - yield 'successful_payment_invoice_payload', message.successful_payment.invoice_payload - yield 'successful_payment_shipping_option_id', message.successful_payment.shipping_option_id - yield 'successful_payment_telegram_payment_charge_id', message.successful_payment.telegram_payment_charge_id - yield 'successful_payment_provider_payment_charge_id', message.successful_payment.provider_payment_charge_id - elif message.connected_website: - yield 'connected_website', message.connected_website - elif message.migrate_from_chat_id: - yield 'migrate_from_chat_id', message.migrate_from_chat_id - elif message.migrate_to_chat_id: - yield 'migrate_to_chat_id', message.migrate_to_chat_id - elif message.pinned_message: - yield from self.make_prefix('pinned_message', message.pinned_message) - elif message.new_chat_title: - yield 'new_chat_title', message.new_chat_title - elif message.new_chat_photo: - yield 'new_chat_photo', message.new_chat_photo[-1].file_id - # elif message.delete_chat_photo: - # yield 'delete_chat_photo', message.delete_chat_photo - # elif message.group_chat_created: - # yield 'group_chat_created', message.group_chat_created - # elif message.passport_data: - # yield 'passport_data', message.passport_data - - def process_inline_query(self, inline_query: types.InlineQuery): - yield 'inline_query_id', inline_query.id - yield from self.process_user(inline_query.from_user) - - if self.include_content: - yield 'inline_query_text', inline_query.query - if inline_query.location: - yield 'location_latitude', inline_query.location.latitude - yield 'location_longitude', inline_query.location.longitude - if inline_query.offset: - yield 'inline_query_offset', inline_query.offset - - def process_chosen_inline_result(self, chosen_inline_result: types.ChosenInlineResult): - yield 'chosen_inline_result_id', chosen_inline_result.result_id - yield from self.process_user(chosen_inline_result.from_user) - - if self.include_content: - yield 'inline_query_text', chosen_inline_result.query - if chosen_inline_result.location: - yield 'location_latitude', chosen_inline_result.location.latitude - yield 'location_longitude', chosen_inline_result.location.longitude - - def process_callback_query(self, callback_query: types.CallbackQuery): - yield from self.process_user(callback_query.from_user) - yield 'callback_query_data', callback_query.data - - if callback_query.message: - yield from self.make_prefix('callback_query_message', self.process_message(callback_query.message)) - if callback_query.inline_message_id: - yield 'callback_query_inline_message_id', callback_query.inline_message_id - if callback_query.chat_instance: - yield 'callback_query_chat_instance', callback_query.chat_instance - if callback_query.game_short_name: - yield 'callback_query_game_short_name', callback_query.game_short_name - - def process_shipping_query(self, shipping_query: types.ShippingQuery): - yield 'shipping_query_id', shipping_query.id - yield from self.process_user(shipping_query.from_user) - - if self.include_content: - yield 'shipping_query_invoice_payload', shipping_query.invoice_payload - - def process_pre_checkout_query(self, pre_checkout_query: types.PreCheckoutQuery): - yield 'pre_checkout_query_id', pre_checkout_query.id - yield from self.process_user(pre_checkout_query.from_user) - - if self.include_content: - yield 'pre_checkout_query_currency', pre_checkout_query.currency - yield 'pre_checkout_query_total_amount', pre_checkout_query.total_amount - yield 'pre_checkout_query_invoice_payload', pre_checkout_query.invoice_payload - yield 'pre_checkout_query_shipping_option_id', pre_checkout_query.shipping_option_id diff --git a/aiogram/dispatcher/__init__.py b/aiogram/dispatcher/__init__.py deleted file mode 100644 index 7c039873..00000000 --- a/aiogram/dispatcher/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from . import filters -from . import handler -from . import middlewares -from . import storage -from . import webhook -from .dispatcher import Dispatcher, FSMContext, DEFAULT_RATE_LIMIT - -__all__ = [ - "DEFAULT_RATE_LIMIT", - "Dispatcher", - "FSMContext", - "filters", - "handler", - "middlewares", - "storage", - "webhook", -] diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py deleted file mode 100644 index 600e25ba..00000000 --- a/aiogram/dispatcher/dispatcher.py +++ /dev/null @@ -1,1133 +0,0 @@ -import asyncio -import functools -import itertools -import logging -import time -import typing - -import aiohttp -from aiohttp.helpers import sentinel - -from aiogram.utils.deprecated import renamed_argument -from .filters import Command, ContentTypeFilter, ExceptionsFilter, FiltersFactory, HashTag, Regexp, \ - RegexpCommandsFilter, StateFilter, Text, IDFilter, AdminFilter, IsReplyFilter -from .handler import Handler -from .middlewares import MiddlewareManager -from .storage import BaseStorage, DELTA, DisabledStorage, EXCEEDED_COUNT, FSMContext, \ - LAST_CALL, RATE_LIMIT, RESULT -from .webhook import BaseResponse -from .. import types -from ..bot import Bot -from ..utils.exceptions import TelegramAPIError, Throttled -from ..utils.mixins import ContextInstanceMixin, DataMixin - -log = logging.getLogger(__name__) - -DEFAULT_RATE_LIMIT = .1 - - -class Dispatcher(DataMixin, ContextInstanceMixin): - """ - Simple Updates dispatcher - - It will process incoming updates: messages, edited messages, channel posts, edited channel posts, - inline queries, chosen inline results, callback queries, shipping queries, pre-checkout queries. - """ - - def __init__(self, bot, loop=None, storage: typing.Optional[BaseStorage] = None, - run_tasks_by_default: bool = False, - throttling_rate_limit=DEFAULT_RATE_LIMIT, no_throttle_error=False, - filters_factory=None): - - if not isinstance(bot, Bot): - raise TypeError(f"Argument 'bot' must be an instance of Bot, not '{type(bot).__name__}'") - - if loop is None: - loop = bot.loop - if storage is None: - storage = DisabledStorage() - if filters_factory is None: - filters_factory = FiltersFactory(self) - - self.bot: Bot = bot - self.loop = loop - self.storage = storage - self.run_tasks_by_default = run_tasks_by_default - - self.throttling_rate_limit = throttling_rate_limit - self.no_throttle_error = no_throttle_error - - self.filters_factory: FiltersFactory = filters_factory - self.updates_handler = Handler(self, middleware_key='update') - self.message_handlers = Handler(self, middleware_key='message') - self.edited_message_handlers = Handler(self, middleware_key='edited_message') - self.channel_post_handlers = Handler(self, middleware_key='channel_post') - self.edited_channel_post_handlers = Handler(self, middleware_key='edited_channel_post') - self.inline_query_handlers = Handler(self, middleware_key='inline_query') - self.chosen_inline_result_handlers = Handler(self, middleware_key='chosen_inline_result') - self.callback_query_handlers = Handler(self, middleware_key='callback_query') - self.shipping_query_handlers = Handler(self, middleware_key='shipping_query') - self.pre_checkout_query_handlers = Handler(self, middleware_key='pre_checkout_query') - self.poll_handlers = Handler(self, middleware_key='poll') - self.errors_handlers = Handler(self, once=False, middleware_key='error') - - self.middleware = MiddlewareManager(self) - - self.updates_handler.register(self.process_update) - - self._polling = False - self._closed = True - self._close_waiter = loop.create_future() - - self._setup_filters() - - def _setup_filters(self): - filters_factory = self.filters_factory - - filters_factory.bind(StateFilter, exclude_event_handlers=[ - self.errors_handlers, - self.poll_handlers, - ]) - filters_factory.bind(ContentTypeFilter, event_handlers=[ - self.message_handlers, - self.edited_message_handlers, - self.channel_post_handlers, - self.edited_channel_post_handlers, - ]), - filters_factory.bind(Command, event_handlers=[ - self.message_handlers, - self.edited_message_handlers - ]) - filters_factory.bind(Text, event_handlers=[ - self.message_handlers, - self.edited_message_handlers, - self.channel_post_handlers, - self.edited_channel_post_handlers, - self.callback_query_handlers, - self.poll_handlers, - self.inline_query_handlers, - ]) - filters_factory.bind(HashTag, event_handlers=[ - self.message_handlers, - self.edited_message_handlers, - self.channel_post_handlers, - self.edited_channel_post_handlers, - ]) - filters_factory.bind(Regexp, event_handlers=[ - self.message_handlers, - self.edited_message_handlers, - self.channel_post_handlers, - self.edited_channel_post_handlers, - self.callback_query_handlers, - self.poll_handlers, - self.inline_query_handlers, - ]) - filters_factory.bind(RegexpCommandsFilter, event_handlers=[ - self.message_handlers, - self.edited_message_handlers, - ]) - filters_factory.bind(ExceptionsFilter, event_handlers=[ - self.errors_handlers, - ]) - filters_factory.bind(AdminFilter, event_handlers=[ - self.message_handlers, - self.edited_message_handlers, - self.channel_post_handlers, - self.edited_channel_post_handlers, - self.callback_query_handlers, - self.inline_query_handlers, - ]) - filters_factory.bind(IDFilter, event_handlers=[ - self.message_handlers, - self.edited_message_handlers, - self.channel_post_handlers, - self.edited_channel_post_handlers, - self.callback_query_handlers, - self.inline_query_handlers, - ]) - filters_factory.bind(IsReplyFilter, event_handlers=[ - self.message_handlers, - self.edited_message_handlers, - self.channel_post_handlers, - self.edited_channel_post_handlers, - ]) - - def __del__(self): - self.stop_polling() - - async def skip_updates(self): - """ - You can skip old incoming updates from queue. - This method is not recommended to use if you use payments or you bot has high-load. - - :return: None - """ - await self.bot.get_updates(offset=-1, timeout=1) - - async def process_updates(self, updates, fast: typing.Optional[bool] = True): - """ - Process list of updates - - :param updates: - :param fast: - :return: - """ - if fast: - tasks = [] - for update in updates: - tasks.append(self.updates_handler.notify(update)) - return await asyncio.gather(*tasks) - - results = [] - for update in updates: - results.append(await self.updates_handler.notify(update)) - return results - - async def process_update(self, update: types.Update): - """ - Process single update object - - :param update: - :return: - """ - types.Update.set_current(update) - - try: - if update.message: - types.User.set_current(update.message.from_user) - types.Chat.set_current(update.message.chat) - return await self.message_handlers.notify(update.message) - if update.edited_message: - types.User.set_current(update.edited_message.from_user) - types.Chat.set_current(update.edited_message.chat) - return await self.edited_message_handlers.notify(update.edited_message) - if update.channel_post: - types.Chat.set_current(update.channel_post.chat) - return await self.channel_post_handlers.notify(update.channel_post) - if update.edited_channel_post: - types.Chat.set_current(update.edited_channel_post.chat) - return await self.edited_channel_post_handlers.notify(update.edited_channel_post) - if update.inline_query: - types.User.set_current(update.inline_query.from_user) - return await self.inline_query_handlers.notify(update.inline_query) - if update.chosen_inline_result: - types.User.set_current(update.chosen_inline_result.from_user) - return await self.chosen_inline_result_handlers.notify(update.chosen_inline_result) - if update.callback_query: - if update.callback_query.message: - types.Chat.set_current(update.callback_query.message.chat) - types.User.set_current(update.callback_query.from_user) - return await self.callback_query_handlers.notify(update.callback_query) - if update.shipping_query: - types.User.set_current(update.shipping_query.from_user) - return await self.shipping_query_handlers.notify(update.shipping_query) - if update.pre_checkout_query: - types.User.set_current(update.pre_checkout_query.from_user) - return await self.pre_checkout_query_handlers.notify(update.pre_checkout_query) - if update.poll: - return await self.poll_handlers.notify(update.poll) - except Exception as e: - err = await self.errors_handlers.notify(update, e) - if err: - return err - raise - - async def reset_webhook(self, check=True) -> bool: - """ - Reset webhook - - :param check: check before deleting - :return: - """ - if check: - wh = await self.bot.get_webhook_info() - if not wh.url: - return False - - return await self.bot.delete_webhook() - - async def start_polling(self, - timeout=20, - relax=0.1, - limit=None, - reset_webhook=None, - fast: typing.Optional[bool] = True, - error_sleep: int = 5): - """ - Start long-polling - - :param timeout: - :param relax: - :param limit: - :param reset_webhook: - :param fast: - :return: - """ - if self._polling: - raise RuntimeError('Polling already started') - - log.info('Start polling.') - - # context.set_value(MODE, LONG_POLLING) - Dispatcher.set_current(self) - Bot.set_current(self.bot) - - if reset_webhook is None: - await self.reset_webhook(check=False) - if reset_webhook: - await self.reset_webhook(check=True) - - self._polling = True - offset = None - try: - current_request_timeout = self.bot.timeout - if current_request_timeout is not sentinel and timeout is not None: - request_timeout = aiohttp.ClientTimeout(total=current_request_timeout.total + timeout or 1) - else: - request_timeout = None - - while self._polling: - try: - with self.bot.request_timeout(request_timeout): - updates = await self.bot.get_updates(limit=limit, offset=offset, timeout=timeout) - except asyncio.CancelledError: - break - except: - log.exception('Cause exception while getting updates.') - await asyncio.sleep(error_sleep) - continue - - if updates: - log.debug(f"Received {len(updates)} updates.") - offset = updates[-1].update_id + 1 - - self.loop.create_task(self._process_polling_updates(updates, fast)) - - if relax: - await asyncio.sleep(relax) - - finally: - self._close_waiter.set_result(None) - log.warning('Polling is stopped.') - - async def _process_polling_updates(self, updates, fast: typing.Optional[bool] = True): - """ - Process updates received from long-polling. - - :param updates: list of updates. - :param fast: - """ - need_to_call = [] - for responses in itertools.chain.from_iterable(await self.process_updates(updates, fast)): - for response in responses: - if not isinstance(response, BaseResponse): - continue - need_to_call.append(response.execute_response(self.bot)) - if need_to_call: - try: - asyncio.gather(*need_to_call) - except TelegramAPIError: - log.exception('Cause exception while processing updates.') - - def stop_polling(self): - """ - Break long-polling process. - - :return: - """ - if hasattr(self, '_polling') and self._polling: - log.info('Stop polling...') - self._polling = False - - async def wait_closed(self): - """ - Wait for the long-polling to close - - :return: - """ - await asyncio.shield(self._close_waiter, loop=self.loop) - - def is_polling(self): - """ - Check if polling is enabled - - :return: - """ - return self._polling - - def register_message_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None, - state=None, run_task=None, **kwargs): - """ - Register handler for message - - .. code-block:: python3 - - # This handler works only if state is None (by default). - dp.register_message_handler(cmd_start, commands=['start', 'about']) - dp.register_message_handler(entry_point, commands=['setup']) - - # This handler works only if current state is "first_step" - dp.register_message_handler(step_handler_1, state="first_step") - - # If you want to handle all states by one handler, use `state="*"`. - dp.register_message_handler(cancel_handler, commands=['cancel'], state="*") - dp.register_message_handler(cancel_handler, lambda msg: msg.text.lower() == 'cancel', state="*") - - :param callback: - :param commands: list of commands - :param regexp: REGEXP - :param content_types: List of content types. - :param custom_filters: list of custom filters - :param kwargs: - :param state: - :return: decorated function - """ - filters_set = self.filters_factory.resolve(self.message_handlers, - *custom_filters, - commands=commands, - regexp=regexp, - content_types=content_types, - state=state, - **kwargs) - self.message_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def message_handler(self, *custom_filters, commands=None, regexp=None, content_types=None, state=None, - run_task=None, **kwargs): - """ - Decorator for message handler - - Examples: - - Simple commands handler: - - .. code-block:: python3 - - @dp.message_handler(commands=['start', 'welcome', 'about']) - async def cmd_handler(message: types.Message): - - Filter messages by regular expression: - - .. code-block:: python3 - - @dp.message_handler(rexexp='^[a-z]+-[0-9]+') - async def msg_handler(message: types.Message): - - Filter messages by command regular expression: - - .. code-block:: python3 - - @dp.message_handler(filters.RegexpCommandsFilter(regexp_commands=['item_([0-9]*)'])) - async def send_welcome(message: types.Message): - - Filter by content type: - - .. code-block:: python3 - - @dp.message_handler(content_types=ContentType.PHOTO | ContentType.DOCUMENT) - async def audio_handler(message: types.Message): - - Filter by custom function: - - .. code-block:: python3 - - @dp.message_handler(lambda message: message.text and 'hello' in message.text.lower()) - async def text_handler(message: types.Message): - - Use multiple filters: - - .. code-block:: python3 - - @dp.message_handler(commands=['command'], content_types=ContentType.TEXT) - async def text_handler(message: types.Message): - - Register multiple filters set for one handler: - - .. code-block:: python3 - - @dp.message_handler(commands=['command']) - @dp.message_handler(lambda message: demojize(message.text) == ':new_moon_with_face:') - async def text_handler(message: types.Message): - - This handler will be called if the message starts with '/command' OR is some emoji - - By default content_type is :class:`ContentType.TEXT` - - :param commands: list of commands - :param regexp: REGEXP - :param content_types: List of content types. - :param custom_filters: list of custom filters - :param kwargs: - :param state: - :param run_task: run callback in task (no wait results) - :return: decorated function - """ - - def decorator(callback): - self.register_message_handler(callback, *custom_filters, - commands=commands, regexp=regexp, content_types=content_types, - state=state, run_task=run_task, **kwargs) - return callback - - return decorator - - def register_edited_message_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None, - state=None, run_task=None, **kwargs): - """ - Register handler for edited message - - :param callback: - :param commands: list of commands - :param regexp: REGEXP - :param content_types: List of content types. - :param state: - :param custom_filters: list of custom filters - :param run_task: run callback in task (no wait results) - :param kwargs: - :return: decorated function - """ - filters_set = self.filters_factory.resolve(self.edited_message_handlers, - *custom_filters, - commands=commands, - regexp=regexp, - content_types=content_types, - state=state, - **kwargs) - self.edited_message_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def edited_message_handler(self, *custom_filters, commands=None, regexp=None, content_types=None, - state=None, run_task=None, **kwargs): - """ - Decorator for edited message handler - - You can use combination of different handlers - - .. code-block:: python3 - - @dp.message_handler() - @dp.edited_message_handler() - async def msg_handler(message: types.Message): - - :param commands: list of commands - :param regexp: REGEXP - :param content_types: List of content types. - :param state: - :param custom_filters: list of custom filters - :param run_task: run callback in task (no wait results) - :param kwargs: - :return: decorated function - """ - - def decorator(callback): - self.register_edited_message_handler(callback, *custom_filters, commands=commands, regexp=regexp, - content_types=content_types, state=state, run_task=run_task, **kwargs) - return callback - - return decorator - - def register_channel_post_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None, - state=None, run_task=None, **kwargs): - """ - Register handler for channel post - - :param callback: - :param commands: list of commands - :param regexp: REGEXP - :param content_types: List of content types. - :param state: - :param custom_filters: list of custom filters - :param run_task: run callback in task (no wait results) - :param kwargs: - :return: decorated function - """ - filters_set = self.filters_factory.resolve(self.channel_post_handlers, - *custom_filters, - commands=commands, - regexp=regexp, - content_types=content_types, - state=state, - **kwargs) - self.channel_post_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def channel_post_handler(self, *custom_filters, commands=None, regexp=None, content_types=None, - state=None, run_task=None, **kwargs): - """ - Decorator for channel post handler - - :param commands: list of commands - :param regexp: REGEXP - :param content_types: List of content types. - :param state: - :param custom_filters: list of custom filters - :param run_task: run callback in task (no wait results) - :param kwargs: - :return: decorated function - """ - - def decorator(callback): - self.register_channel_post_handler(callback, *custom_filters, commands=commands, regexp=regexp, - content_types=content_types, state=state, run_task=run_task, **kwargs) - return callback - - return decorator - - def register_edited_channel_post_handler(self, callback, *custom_filters, commands=None, regexp=None, - content_types=None, state=None, run_task=None, **kwargs): - """ - Register handler for edited channel post - - :param callback: - :param commands: list of commands - :param regexp: REGEXP - :param content_types: List of content types. - :param state: - :param custom_filters: list of custom filters - :param run_task: run callback in task (no wait results) - :param kwargs: - :return: decorated function - """ - filters_set = self.filters_factory.resolve(self.edited_message_handlers, - *custom_filters, - commands=commands, - regexp=regexp, - content_types=content_types, - state=state, - **kwargs) - self.edited_channel_post_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def edited_channel_post_handler(self, *custom_filters, commands=None, regexp=None, content_types=None, - state=None, run_task=None, **kwargs): - """ - Decorator for edited channel post handler - - :param commands: list of commands - :param regexp: REGEXP - :param content_types: List of content types. - :param custom_filters: list of custom filters - :param state: - :param run_task: run callback in task (no wait results) - :param kwargs: - :return: decorated function - """ - - def decorator(callback): - self.register_edited_channel_post_handler(callback, *custom_filters, commands=commands, regexp=regexp, - content_types=content_types, state=state, run_task=run_task, - **kwargs) - return callback - - return decorator - - def register_inline_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs): - """ - Register handler for inline query - - Example: - - .. code-block:: python3 - - dp.register_inline_handler(some_inline_handler, lambda inline_query: True) - - :param callback: - :param custom_filters: list of custom filters - :param state: - :param run_task: run callback in task (no wait results) - :param kwargs: - :return: decorated function - """ - if custom_filters is None: - custom_filters = [] - filters_set = self.filters_factory.resolve(self.inline_query_handlers, - *custom_filters, - state=state, - **kwargs) - self.inline_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def inline_handler(self, *custom_filters, state=None, run_task=None, **kwargs): - """ - Decorator for inline query handler - - Example: - - .. code-block:: python3 - - @dp.inline_handler(lambda inline_query: True) - async def some_inline_handler(inline_query: types.InlineQuery) - - :param state: - :param custom_filters: list of custom filters - :param run_task: run callback in task (no wait results) - :param kwargs: - :return: decorated function - """ - - def decorator(callback): - self.register_inline_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs) - return callback - - return decorator - - def register_chosen_inline_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs): - """ - Register handler for chosen inline query - - Example: - - .. code-block:: python3 - - dp.register_chosen_inline_handler(some_chosen_inline_handler, lambda chosen_inline_query: True) - - :param callback: - :param state: - :param custom_filters: - :param run_task: run callback in task (no wait results) - :param kwargs: - :return: - """ - if custom_filters is None: - custom_filters = [] - filters_set = self.filters_factory.resolve(self.chosen_inline_result_handlers, - *custom_filters, - state=state, - **kwargs) - self.chosen_inline_result_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def chosen_inline_handler(self, *custom_filters, state=None, run_task=None, **kwargs): - """ - Decorator for chosen inline query handler - - Example: - - .. code-block:: python3 - - @dp.chosen_inline_handler(lambda chosen_inline_query: True) - async def some_chosen_inline_handler(chosen_inline_query: types.ChosenInlineResult) - - :param state: - :param custom_filters: - :param run_task: run callback in task (no wait results) - :param kwargs: - :return: - """ - - def decorator(callback): - self.register_chosen_inline_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs) - return callback - - return decorator - - def register_callback_query_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs): - """ - Register handler for callback query - - Example: - - .. code-block:: python3 - - dp.register_callback_query_handler(some_callback_handler, lambda callback_query: True) - - :param callback: - :param state: - :param custom_filters: - :param run_task: run callback in task (no wait results) - :param kwargs: - """ - filters_set = self.filters_factory.resolve(self.callback_query_handlers, - *custom_filters, - state=state, - **kwargs) - self.callback_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def callback_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs): - """ - Decorator for callback query handler - - Example: - - .. code-block:: python3 - - @dp.callback_query_handler(lambda callback_query: True) - async def some_callback_handler(callback_query: types.CallbackQuery) - - :param state: - :param custom_filters: - :param run_task: run callback in task (no wait results) - :param kwargs: - """ - - def decorator(callback): - self.register_callback_query_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs) - return callback - - return decorator - - def register_shipping_query_handler(self, callback, *custom_filters, state=None, run_task=None, - **kwargs): - """ - Register handler for shipping query - - Example: - - .. code-block:: python3 - - dp.register_shipping_query_handler(some_shipping_query_handler, lambda shipping_query: True) - - :param callback: - :param state: - :param custom_filters: - :param run_task: run callback in task (no wait results) - :param kwargs: - """ - filters_set = self.filters_factory.resolve(self.shipping_query_handlers, - *custom_filters, - state=state, - **kwargs) - self.shipping_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def shipping_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs): - """ - Decorator for shipping query handler - - Example: - - .. code-block:: python3 - - @dp.shipping_query_handler(lambda shipping_query: True) - async def some_shipping_query_handler(shipping_query: types.ShippingQuery) - - :param state: - :param custom_filters: - :param run_task: run callback in task (no wait results) - :param kwargs: - """ - - def decorator(callback): - self.register_shipping_query_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs) - return callback - - return decorator - - def register_pre_checkout_query_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs): - """ - Register handler for pre-checkout query - - Example: - - .. code-block:: python3 - - dp.register_pre_checkout_query_handler(some_pre_checkout_query_handler, lambda shipping_query: True) - - :param callback: - :param state: - :param custom_filters: - :param run_task: run callback in task (no wait results) - :param kwargs: - """ - filters_set = self.filters_factory.resolve(self.pre_checkout_query_handlers, - *custom_filters, - state=state, - **kwargs) - self.pre_checkout_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def pre_checkout_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs): - """ - Decorator for pre-checkout query handler - - Example: - - .. code-block:: python3 - - @dp.pre_checkout_query_handler(lambda shipping_query: True) - async def some_pre_checkout_query_handler(shipping_query: types.ShippingQuery) - - :param state: - :param custom_filters: - :param run_task: run callback in task (no wait results) - :param kwargs: - """ - - def decorator(callback): - self.register_pre_checkout_query_handler(callback, *custom_filters, state=state, run_task=run_task, - **kwargs) - return callback - - return decorator - - def register_poll_handler(self, callback, *custom_filters, run_task=None, **kwargs): - filters_set = self.filters_factory.resolve(self.poll_handlers, - *custom_filters, - **kwargs) - self.poll_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def poll_handler(self, *custom_filters, run_task=None, **kwargs): - def decorator(callback): - self.register_poll_handler(callback, *custom_filters, run_task=run_task, - **kwargs) - return callback - - return decorator - - def register_errors_handler(self, callback, *custom_filters, exception=None, run_task=None, **kwargs): - """ - Register handler for errors - - :param callback: - :param exception: you can make handler for specific errors type - :param run_task: run callback in task (no wait results) - """ - filters_set = self.filters_factory.resolve(self.errors_handlers, - *custom_filters, - exception=exception, - **kwargs) - self.errors_handlers.register(self._wrap_async_task(callback, run_task), filters_set) - - def errors_handler(self, *custom_filters, exception=None, run_task=None, **kwargs): - """ - Decorator for errors handler - - :param exception: you can make handler for specific errors type - :param run_task: run callback in task (no wait results) - :return: - """ - - def decorator(callback): - self.register_errors_handler(self._wrap_async_task(callback, run_task), - *custom_filters, exception=exception, **kwargs) - return callback - - return decorator - - def current_state(self, *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None) -> FSMContext: - """ - Get current state for user in chat as context - - .. code-block:: python3 - - with dp.current_state(chat=message.chat.id, user=message.user.id) as state: - pass - - state = dp.current_state() - state.set_state('my_state') - - :param chat: - :param user: - :return: - """ - if chat is None: - chat_obj = types.Chat.get_current() - chat = chat_obj.id if chat_obj else None - if user is None: - user_obj = types.User.get_current() - user = user_obj.id if user_obj else None - - return FSMContext(storage=self.storage, chat=chat, user=user) - - @renamed_argument(old_name='user', new_name='user_id', until_version='3.0', stacklevel=3) - @renamed_argument(old_name='chat', new_name='chat_id', until_version='3.0', stacklevel=4) - async def throttle(self, key, *, rate=None, user_id=None, chat_id=None, no_error=None) -> bool: - """ - Execute throttling manager. - Returns True if limit has not exceeded otherwise raises ThrottleError or returns False - - :param key: key in storage - :param rate: limit (by default is equal to default rate limit) - :param user_id: user id - :param chat_id: chat id - :param no_error: return boolean value instead of raising error - :return: bool - """ - if not self.storage.has_bucket(): - raise RuntimeError('This storage does not provide Leaky Bucket') - - if no_error is None: - no_error = self.no_throttle_error - if rate is None: - rate = self.throttling_rate_limit - if user_id is None and chat_id is None: - user_id = types.User.get_current().id - chat_id = types.Chat.get_current().id - - # Detect current time - now = time.time() - - bucket = await self.storage.get_bucket(chat=chat_id, user=user_id) - - # Fix bucket - if bucket is None: - bucket = {key: {}} - if key not in bucket: - bucket[key] = {} - data = bucket[key] - - # Calculate - called = data.get(LAST_CALL, now) - delta = now - called - result = delta >= rate or delta <= 0 - - # Save results - data[RESULT] = result - data[RATE_LIMIT] = rate - data[LAST_CALL] = now - data[DELTA] = delta - if not result: - data[EXCEEDED_COUNT] += 1 - else: - data[EXCEEDED_COUNT] = 1 - bucket[key].update(data) - await self.storage.set_bucket(chat=chat_id, user=user_id, bucket=bucket) - - if not result and not no_error: - # Raise if it is allowed - raise Throttled(key=key, chat=chat_id, user=user_id, **data) - return result - - @renamed_argument(old_name='user', new_name='user_id', until_version='3.0', stacklevel=3) - @renamed_argument(old_name='chat', new_name='chat_id', until_version='3.0', stacklevel=4) - async def check_key(self, key, chat_id=None, user_id=None): - """ - Get information about key in bucket - - :param key: - :param chat_id: - :param user_id: - :return: - """ - if not self.storage.has_bucket(): - raise RuntimeError('This storage does not provide Leaky Bucket') - - if user_id is None and chat_id is None: - user_id = types.User.get_current() - chat_id = types.Chat.get_current() - - bucket = await self.storage.get_bucket(chat=chat_id, user=user_id) - data = bucket.get(key, {}) - return Throttled(key=key, chat=chat_id, user=user_id, **data) - - @renamed_argument(old_name='user', new_name='user_id', until_version='3.0', stacklevel=3) - @renamed_argument(old_name='chat', new_name='chat_id', until_version='3.0', stacklevel=4) - async def release_key(self, key, chat_id=None, user_id=None): - """ - Release blocked key - - :param key: - :param chat_id: - :param user_id: - :return: - """ - if not self.storage.has_bucket(): - raise RuntimeError('This storage does not provide Leaky Bucket') - - if user_id is None and chat_id is None: - user_id = types.User.get_current() - chat_id = types.Chat.get_current() - - bucket = await self.storage.get_bucket(chat=chat_id, user=user_id) - if bucket and key in bucket: - del bucket['key'] - await self.storage.set_bucket(chat=chat_id, user=user_id, bucket=bucket) - return True - return False - - def async_task(self, func): - """ - Execute handler as task and return None. - Use this decorator for slow handlers (with timeouts) - - .. code-block:: python3 - - @dp.message_handler(commands=['command']) - @dp.async_task - async def cmd_with_timeout(message: types.Message): - await asyncio.sleep(120) - return SendMessage(message.chat.id, 'KABOOM').reply(message) - - :param func: - :return: - """ - - def process_response(task): - try: - response = task.result() - except Exception as e: - self.loop.create_task( - self.errors_handlers.notify(types.Update.get_current(), e)) - else: - if isinstance(response, BaseResponse): - self.loop.create_task(response.execute_response(self.bot)) - - @functools.wraps(func) - async def wrapper(*args, **kwargs): - task = self.loop.create_task(func(*args, **kwargs)) - task.add_done_callback(process_response) - - return wrapper - - def _wrap_async_task(self, callback, run_task=None) -> callable: - if run_task is None: - run_task = self.run_tasks_by_default - - if run_task: - return self.async_task(callback) - return callback - - def throttled(self, on_throttled: typing.Optional[typing.Callable] = None, - key=None, rate=None, - user_id=None, chat_id=None): - """ - Meta-decorator for throttling. - Invokes on_throttled if the handler was throttled. - - Example: - - .. code-block:: python3 - - async def handler_throttled(message: types.Message, **kwargs): - await message.answer("Throttled!") - - @dp.throttled(handler_throttled) - async def some_handler(message: types.Message): - await message.answer("Didn't throttled!") - - :param on_throttled: the callable object that should be either a function or return a coroutine - :param key: key in storage - :param rate: limit (by default is equal to default rate limit) - :param user_id: user id - :param chat_id: chat id - :return: decorator - """ - def decorator(func): - @functools.wraps(func) - async def wrapped(*args, **kwargs): - is_not_throttled = await self.throttle(key if key is not None else func.__name__, - rate=rate, - user_id=user_id, chat_id=chat_id, - no_error=True) - if is_not_throttled: - return await func(*args, **kwargs) - else: - kwargs.update( - { - 'rate': rate, - 'key': key, - 'user_id': user_id, - 'chat_id': chat_id - } - ) # update kwargs with parameters which were given to throttled - - if on_throttled: - if asyncio.iscoroutinefunction(on_throttled): - await on_throttled(*args, **kwargs) - else: - kwargs.update( - { - 'loop': asyncio.get_running_loop() - } - ) - partial_func = functools.partial(on_throttled, *args, **kwargs) - asyncio.get_running_loop().run_in_executor(None, - partial_func - ) - return wrapped - - return decorator diff --git a/aiogram/dispatcher/filters/__init__.py b/aiogram/dispatcher/filters/__init__.py deleted file mode 100644 index 67c13872..00000000 --- a/aiogram/dispatcher/filters/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -from .builtin import Command, CommandHelp, CommandPrivacy, CommandSettings, CommandStart, ContentTypeFilter, \ - ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, \ - Text, IDFilter, AdminFilter, IsReplyFilter -from .factory import FiltersFactory -from .filters import AbstractFilter, BoundFilter, Filter, FilterNotPassed, FilterRecord, execute_filter, \ - check_filters, get_filter_spec, get_filters_spec - -__all__ = [ - 'AbstractFilter', - 'BoundFilter', - 'Command', - 'CommandStart', - 'CommandHelp', - 'CommandPrivacy', - 'CommandSettings', - 'ContentTypeFilter', - 'ExceptionsFilter', - 'HashTag', - 'Filter', - 'FilterNotPassed', - 'FilterRecord', - 'FiltersFactory', - 'RegexpCommandsFilter', - 'Regexp', - 'StateFilter', - 'Text', - 'IDFilter', - 'IsReplyFilter', - 'AdminFilter', - 'get_filter_spec', - 'get_filters_spec', - 'execute_filter', - 'check_filters', -] diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py deleted file mode 100644 index 55ed63e5..00000000 --- a/aiogram/dispatcher/filters/builtin.py +++ /dev/null @@ -1,646 +0,0 @@ -import inspect -import re -import typing -from contextvars import ContextVar -from dataclasses import dataclass, field -from typing import Any, Dict, Iterable, Optional, Union - -from babel.support import LazyProxy - -from aiogram import types -from aiogram.dispatcher.filters.filters import BoundFilter, Filter -from aiogram.types import CallbackQuery, Message, InlineQuery, Poll, ChatType - - -class Command(Filter): - """ - You can handle commands by using this filter. - - If filter is successful processed the :obj:`Command.CommandObj` will be passed to the handler arguments. - - By default this filter is registered for messages and edited messages handlers. - """ - - def __init__(self, commands: Union[Iterable, str], - prefixes: Union[Iterable, str] = '/', - ignore_case: bool = True, - ignore_mention: bool = False): - """ - Filter can be initialized from filters factory or by simply creating instance of this class. - - Examples: - - .. code-block:: python - - @dp.message_handler(commands=['myCommand']) - @dp.message_handler(Command(['myCommand'])) - @dp.message_handler(commands=['myCommand'], commands_prefix='!/') - - :param commands: Command or list of commands always without leading slashes (prefix) - :param prefixes: Allowed commands prefix. By default is slash. - If you change the default behavior pass the list of prefixes to this argument. - :param ignore_case: Ignore case of the command - :param ignore_mention: Ignore mention in command - (By default this filter pass only the commands addressed to current bot) - """ - if isinstance(commands, str): - commands = (commands,) - - self.commands = list(map(str.lower, commands)) if ignore_case else commands - self.prefixes = prefixes - self.ignore_case = ignore_case - self.ignore_mention = ignore_mention - - @classmethod - def validate(cls, full_config: Dict[str, Any]) -> Optional[Dict[str, Any]]: - """ - Validator for filters factory - - From filters factory this filter can be registered with arguments: - - - ``command`` - - ``commands_prefix`` (will be passed as ``prefixes``) - - ``commands_ignore_mention`` (will be passed as ``ignore_mention`` - - :param full_config: - :return: config or empty dict - """ - config = {} - if 'commands' in full_config: - config['commands'] = full_config.pop('commands') - if config and 'commands_prefix' in full_config: - config['prefixes'] = full_config.pop('commands_prefix') - if config and 'commands_ignore_mention' in full_config: - config['ignore_mention'] = full_config.pop('commands_ignore_mention') - return config - - async def check(self, message: types.Message): - return await self.check_command(message, self.commands, self.prefixes, self.ignore_case, self.ignore_mention) - - @staticmethod - async def check_command(message: types.Message, commands, prefixes, ignore_case=True, ignore_mention=False): - if not message.text: # Prevent to use with non-text content types - return False - - full_command = message.text.split()[0] - prefix, (command, _, mention) = full_command[0], full_command[1:].partition('@') - - if not ignore_mention and mention and (await message.bot.me).username.lower() != mention.lower(): - return False - if prefix not in prefixes: - return False - if (command.lower() if ignore_case else command) not in commands: - return False - - return {'command': Command.CommandObj(command=command, prefix=prefix, mention=mention)} - - @dataclass - class CommandObj: - """ - Instance of this object is always has command and it prefix. - - Can be passed as keyword argument ``command`` to the handler - """ - - """Command prefix""" - prefix: str = '/' - """Command without prefix and mention""" - command: str = '' - """Mention (if available)""" - mention: str = None - """Command argument""" - args: str = field(repr=False, default=None) - - @property - def mentioned(self) -> bool: - """ - This command has mention? - - :return: - """ - return bool(self.mention) - - @property - def text(self) -> str: - """ - Generate original text from object - - :return: - """ - line = self.prefix + self.command - if self.mentioned: - line += '@' + self.mention - if self.args: - line += ' ' + self.args - return line - - -class CommandStart(Command): - """ - This filter based on :obj:`Command` filter but can handle only ``/start`` command. - """ - - def __init__(self, deep_link: typing.Optional[typing.Union[str, re.Pattern]] = None): - """ - Also this filter can handle `deep-linking `_ arguments. - - Example: - - .. code-block:: python - - @dp.message_handler(CommandStart(re.compile(r'ref-([\\d]+)'))) - - :param deep_link: string or compiled regular expression (by ``re.compile(...)``). - """ - super().__init__(['start']) - self.deep_link = deep_link - - async def check(self, message: types.Message): - """ - If deep-linking is passed to the filter result of the matching will be passed as ``deep_link`` to the handler - - :param message: - :return: - """ - check = await super().check(message) - - if check and self.deep_link is not None: - if not isinstance(self.deep_link, re.Pattern): - return message.get_args() == self.deep_link - - match = self.deep_link.match(message.get_args()) - if match: - return {'deep_link': match} - return False - - return check - - -class CommandHelp(Command): - """ - This filter based on :obj:`Command` filter but can handle only ``/help`` command. - """ - - def __init__(self): - super().__init__(['help']) - - -class CommandSettings(Command): - """ - This filter based on :obj:`Command` filter but can handle only ``/settings`` command. - """ - - def __init__(self): - super().__init__(['settings']) - - -class CommandPrivacy(Command): - """ - This filter based on :obj:`Command` filter but can handle only ``/privacy`` command. - """ - - def __init__(self): - super().__init__(['privacy']) - - -class Text(Filter): - """ - Simple text filter - """ - - _default_params = ( - ('text', 'equals'), - ('text_contains', 'contains'), - ('text_startswith', 'startswith'), - ('text_endswith', 'endswith'), - ) - - def __init__(self, - equals: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None, - contains: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None, - startswith: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None, - endswith: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None, - ignore_case=False): - """ - Check text for one of pattern. Only one mode can be used in one filter. - In every pattern, a single string is treated as a list with 1 element. - - :param equals: True if object's text in the list - :param contains: True if object's text contains all strings from the list - :param startswith: True if object's text starts with any of strings from the list - :param endswith: True if object's text ends with any of strings from the list - :param ignore_case: case insensitive - """ - # Only one mode can be used. check it. - check = sum(map(lambda s: s is not None, (equals, contains, startswith, endswith))) - if check > 1: - args = "' and '".join([arg[0] for arg in [('equals', equals), - ('contains', contains), - ('startswith', startswith), - ('endswith', endswith) - ] if arg[1] is not None]) - raise ValueError(f"Arguments '{args}' cannot be used together.") - elif check == 0: - raise ValueError(f"No one mode is specified!") - - equals, contains, endswith, startswith = map(lambda e: [e] if isinstance(e, str) or isinstance(e, LazyProxy) - else e, - (equals, contains, endswith, startswith)) - self.equals = equals - self.contains = contains - self.endswith = endswith - self.startswith = startswith - self.ignore_case = ignore_case - - @classmethod - def validate(cls, full_config: Dict[str, Any]): - for param, key in cls._default_params: - if param in full_config: - return {key: full_config.pop(param)} - - async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, Poll]): - if isinstance(obj, Message): - text = obj.text or obj.caption or '' - if not text and obj.poll: - text = obj.poll.question - elif isinstance(obj, CallbackQuery): - text = obj.data - elif isinstance(obj, InlineQuery): - text = obj.query - elif isinstance(obj, Poll): - text = obj.question - else: - return False - - if self.ignore_case: - text = text.lower() - _pre_process_func = lambda s: str(s).lower() - else: - _pre_process_func = str - - # now check - if self.equals is not None: - equals = list(map(_pre_process_func, self.equals)) - return text in equals - - if self.contains is not None: - contains = list(map(_pre_process_func, self.contains)) - return all(map(text.__contains__, contains)) - - if self.startswith is not None: - startswith = list(map(_pre_process_func, self.startswith)) - return any(map(text.startswith, startswith)) - - if self.endswith is not None: - endswith = list(map(_pre_process_func, self.endswith)) - return any(map(text.endswith, endswith)) - - return False - - -class HashTag(Filter): - """ - Filter for hashtag's and cashtag's - """ - - # TODO: allow to use regexp - - def __init__(self, hashtags=None, cashtags=None): - if not hashtags and not cashtags: - raise ValueError('No one hashtag or cashtag is specified!') - - if hashtags is None: - hashtags = [] - elif isinstance(hashtags, str): - hashtags = [hashtags] - - if cashtags is None: - cashtags = [] - elif isinstance(cashtags, str): - cashtags = [cashtags.upper()] - else: - cashtags = list(map(str.upper, cashtags)) - - self.hashtags = hashtags - self.cashtags = cashtags - - @classmethod - def validate(cls, full_config: Dict[str, Any]): - config = {} - if 'hashtags' in full_config: - config['hashtags'] = full_config.pop('hashtags') - if 'cashtags' in full_config: - config['cashtags'] = full_config.pop('cashtags') - return config - - async def check(self, message: types.Message): - if message.caption: - text = message.caption - entities = message.caption_entities - elif message.text: - text = message.text - entities = message.entities - else: - return False - - hashtags, cashtags = self._get_tags(text, entities) - if self.hashtags and set(hashtags) & set(self.hashtags) \ - or self.cashtags and set(cashtags) & set(self.cashtags): - return {'hashtags': hashtags, 'cashtags': cashtags} - - def _get_tags(self, text, entities): - hashtags = [] - cashtags = [] - - for entity in entities: - if entity.type == types.MessageEntityType.HASHTAG: - value = entity.get_text(text).lstrip('#') - hashtags.append(value) - - elif entity.type == types.MessageEntityType.CASHTAG: - value = entity.get_text(text).lstrip('$') - cashtags.append(value) - - return hashtags, cashtags - - -class Regexp(Filter): - """ - Regexp filter for messages and callback query - """ - - def __init__(self, regexp): - if not isinstance(regexp, re.Pattern): - regexp = re.compile(regexp, flags=re.IGNORECASE | re.MULTILINE) - self.regexp = regexp - - @classmethod - def validate(cls, full_config: Dict[str, Any]): - if 'regexp' in full_config: - return {'regexp': full_config.pop('regexp')} - - async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, Poll]): - if isinstance(obj, Message): - content = obj.text or obj.caption or '' - if not content and obj.poll: - content = obj.poll.question - elif isinstance(obj, CallbackQuery) and obj.data: - content = obj.data - elif isinstance(obj, InlineQuery): - content = obj.query - elif isinstance(obj, Poll): - content = obj.question - else: - return False - - match = self.regexp.search(content) - - if match: - return {'regexp': match} - return False - - -class RegexpCommandsFilter(BoundFilter): - """ - Check commands by regexp in message - """ - - key = 'regexp_commands' - - def __init__(self, regexp_commands): - self.regexp_commands = [re.compile(command, flags=re.IGNORECASE | re.MULTILINE) for command in regexp_commands] - - async def check(self, message): - if not message.is_command(): - return False - - command = message.text.split()[0][1:] - command, _, mention = command.partition('@') - - if mention and mention != (await message.bot.me).username: - return False - - for command in self.regexp_commands: - search = command.search(message.text) - if search: - return {'regexp_command': search} - return False - - -class ContentTypeFilter(BoundFilter): - """ - Check message content type - """ - - key = 'content_types' - required = True - default = types.ContentTypes.TEXT - - def __init__(self, content_types): - self.content_types = content_types - - async def check(self, message): - return types.ContentType.ANY in self.content_types or \ - message.content_type in self.content_types - - -class StateFilter(BoundFilter): - """ - Check user state - """ - key = 'state' - required = True - - ctx_state = ContextVar('user_state') - - def __init__(self, dispatcher, state): - from aiogram.dispatcher.filters.state import State, StatesGroup - - self.dispatcher = dispatcher - states = [] - if not isinstance(state, (list, set, tuple, frozenset)) or state is None: - state = [state, ] - for item in state: - if isinstance(item, State): - states.append(item.state) - elif inspect.isclass(item) and issubclass(item, StatesGroup): - states.extend(item.all_states_names) - else: - states.append(item) - self.states = states - - def get_target(self, obj): - return getattr(getattr(obj, 'chat', None), 'id', None), getattr(getattr(obj, 'from_user', None), 'id', None) - - async def check(self, obj): - if '*' in self.states: - return {'state': self.dispatcher.current_state()} - - try: - state = self.ctx_state.get() - except LookupError: - chat, user = self.get_target(obj) - - if chat or user: - state = await self.dispatcher.storage.get_state(chat=chat, user=user) - self.ctx_state.set(state) - if state in self.states: - return {'state': self.dispatcher.current_state(), 'raw_state': state} - - else: - if state in self.states: - return {'state': self.dispatcher.current_state(), 'raw_state': state} - - return False - - -class ExceptionsFilter(BoundFilter): - """ - Filter for exceptions - """ - - key = 'exception' - - def __init__(self, exception): - self.exception = exception - - async def check(self, update, exception): - try: - raise exception - except self.exception: - return True - except: - return False - - -class IDFilter(Filter): - - def __init__(self, - user_id: Optional[Union[Iterable[Union[int, str]], str, int]] = None, - chat_id: Optional[Union[Iterable[Union[int, str]], str, int]] = None, - ): - """ - :param user_id: - :param chat_id: - """ - if user_id is None and chat_id is None: - raise ValueError("Both user_id and chat_id can't be None") - - self.user_id = None - self.chat_id = None - if user_id: - if isinstance(user_id, Iterable): - self.user_id = list(map(int, user_id)) - else: - self.user_id = [int(user_id), ] - if chat_id: - if isinstance(chat_id, Iterable): - self.chat_id = list(map(int, chat_id)) - else: - self.chat_id = [int(chat_id), ] - - @classmethod - def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]: - result = {} - if 'user_id' in full_config: - result['user_id'] = full_config.pop('user_id') - - if 'chat_id' in full_config: - result['chat_id'] = full_config.pop('chat_id') - - return result - - async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]): - if isinstance(obj, Message): - user_id = obj.from_user.id - chat_id = obj.chat.id - elif isinstance(obj, CallbackQuery): - user_id = obj.from_user.id - chat_id = None - if obj.message is not None: - # if the button was sent with message - chat_id = obj.message.chat.id - elif isinstance(obj, InlineQuery): - user_id = obj.from_user.id - chat_id = None - else: - return False - - if self.user_id and self.chat_id: - return user_id in self.user_id and chat_id in self.chat_id - if self.user_id: - return user_id in self.user_id - if self.chat_id: - return chat_id in self.chat_id - - return False - - -class AdminFilter(Filter): - """ - Checks if user is admin in a chat. - If is_chat_admin is not set, the filter will check in the current chat (correct only for messages). - is_chat_admin is required for InlineQuery. - """ - - def __init__(self, is_chat_admin: Optional[Union[Iterable[Union[int, str]], str, int, bool]] = None): - self._check_current = False - self._chat_ids = None - - if is_chat_admin is False: - raise ValueError("is_chat_admin cannot be False") - - if is_chat_admin: - if isinstance(is_chat_admin, bool): - self._check_current = is_chat_admin - if isinstance(is_chat_admin, Iterable): - self._chat_ids = list(is_chat_admin) - else: - self._chat_ids = [is_chat_admin] - else: - self._check_current = True - - @classmethod - def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]: - result = {} - - if "is_chat_admin" in full_config: - result["is_chat_admin"] = full_config.pop("is_chat_admin") - - return result - - async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]) -> bool: - user_id = obj.from_user.id - - if self._check_current: - if isinstance(obj, Message): - message = obj - elif isinstance(obj, CallbackQuery) and obj.message: - message = obj.message - else: - return False - if ChatType.is_private(message): # there is no admin in private chats - return False - chat_ids = [message.chat.id] - else: - chat_ids = self._chat_ids - - admins = [member.user.id for chat_id in chat_ids for member in await obj.bot.get_chat_administrators(chat_id)] - - return user_id in admins - - -class IsReplyFilter(BoundFilter): - """ - Check if message is replied and send reply message to handler - """ - key = 'is_reply' - - def __init__(self, is_reply): - self.is_reply = is_reply - - async def check(self, msg: Message): - if msg.reply_to_message and self.is_reply: - return {'reply': msg.reply_to_message} - elif not msg.reply_to_message and not self.is_reply: - return True diff --git a/aiogram/dispatcher/filters/factory.py b/aiogram/dispatcher/filters/factory.py deleted file mode 100644 index 57468475..00000000 --- a/aiogram/dispatcher/filters/factory.py +++ /dev/null @@ -1,80 +0,0 @@ -import typing - -from .filters import AbstractFilter, FilterRecord -from ..handler import Handler - - -class FiltersFactory: - """ - Filters factory - """ - - def __init__(self, dispatcher): - self._dispatcher = dispatcher - self._registered: typing.List[FilterRecord] = [] - - def bind( - self, - callback: typing.Union[typing.Callable, AbstractFilter], - validator: typing.Optional[typing.Callable] = None, - event_handlers: typing.Optional[typing.List[Handler]] = None, - exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None, - ): - """ - Register filter - - :param callback: callable or subclass of :obj:`AbstractFilter` - :param validator: custom validator. - :param event_handlers: list of instances of :obj:`Handler` - :param exclude_event_handlers: list of excluded event handlers (:obj:`Handler`) - """ - record = FilterRecord(callback, validator, event_handlers, exclude_event_handlers) - self._registered.append(record) - - def unbind(self, callback: typing.Union[typing.Callable, AbstractFilter]): - """ - Unregister callback - - :param callback: callable of subclass of :obj:`AbstractFilter` - """ - for record in self._registered: - if record.callback == callback: - self._registered.remove(record) - - def resolve( - self, event_handler, *custom_filters, **full_config - ) -> typing.List[typing.Union[typing.Callable, AbstractFilter]]: - """ - Resolve filters to filters-set - - :param event_handler: - :param custom_filters: - :param full_config: - :return: - """ - filters_set = [] - filters_set.extend( - self._resolve_registered( - event_handler, {k: v for k, v in full_config.items() if v is not None} - ) - ) - if custom_filters: - filters_set.extend(custom_filters) - - return filters_set - - def _resolve_registered(self, event_handler, full_config) -> typing.Generator: - """ - Resolve registered filters - - :param event_handler: - :param full_config: - :return: - """ - for record in self._registered: - filter_ = record.resolve(self._dispatcher, event_handler, full_config) - if filter_: - yield filter_ - - if full_config: - raise NameError("Invalid filter name(s): '" + "', ".join(full_config.keys()) + "'") diff --git a/aiogram/dispatcher/filters/filters.py b/aiogram/dispatcher/filters/filters.py deleted file mode 100644 index 220ef96c..00000000 --- a/aiogram/dispatcher/filters/filters.py +++ /dev/null @@ -1,289 +0,0 @@ -import abc -import inspect -import typing - -from ..handler import Handler, FilterObj - - -class FilterNotPassed(Exception): - pass - - -def wrap_async(func): - async def async_wrapper(*args, **kwargs): - return func(*args, **kwargs) - - if inspect.isawaitable(func) \ - or inspect.iscoroutinefunction(func) \ - or isinstance(func, AbstractFilter): - return func - return async_wrapper - - -def get_filter_spec(dispatcher, filter_: callable): - kwargs = {} - if not callable(filter_): - raise TypeError('Filter must be callable and/or awaitable!') - - spec = inspect.getfullargspec(filter_) - if 'dispatcher' in spec: - kwargs['dispatcher'] = dispatcher - if inspect.isawaitable(filter_) \ - or inspect.iscoroutinefunction(filter_) \ - or isinstance(filter_, AbstractFilter): - return FilterObj(filter=filter_, kwargs=kwargs, is_async=True) - else: - return FilterObj(filter=filter_, kwargs=kwargs, is_async=False) - - -def get_filters_spec(dispatcher, filters: typing.Iterable[callable]): - data = [] - if filters is not None: - for i in filters: - data.append(get_filter_spec(dispatcher, i)) - return data - - -async def execute_filter(filter_: FilterObj, args): - """ - Helper for executing filter - - :param filter_: - :param args: - :return: - """ - if filter_.is_async: - return await filter_.filter(*args, **filter_.kwargs) - else: - return filter_.filter(*args, **filter_.kwargs) - - -async def check_filters(filters: typing.Iterable[FilterObj], args): - """ - Check list of filters - - :param filters: - :param args: - :return: - """ - data = {} - if filters is not None: - for filter_ in filters: - f = await execute_filter(filter_, args) - if not f: - raise FilterNotPassed() - elif isinstance(f, dict): - data.update(f) - return data - - -class FilterRecord: - """ - Filters record for factory - """ - - def __init__(self, callback: typing.Union[typing.Callable, 'AbstractFilter'], - validator: typing.Optional[typing.Callable] = None, - event_handlers: typing.Optional[typing.Iterable[Handler]] = None, - exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None): - if event_handlers and exclude_event_handlers: - raise ValueError("'event_handlers' and 'exclude_event_handlers' arguments cannot be used together.") - - self.callback = callback - self.event_handlers = event_handlers - self.exclude_event_handlers = exclude_event_handlers - - if validator is not None: - if not callable(validator): - raise TypeError(f"validator must be callable, not {type(validator)}") - self.resolver = validator - elif issubclass(callback, AbstractFilter): - self.resolver = callback.validate - else: - raise RuntimeError('validator is required!') - - def resolve(self, dispatcher, event_handler, full_config): - if not self._check_event_handler(event_handler): - return - config = self.resolver(full_config) - if config: - if 'dispatcher' not in config: - spec = inspect.getfullargspec(self.callback) - if 'dispatcher' in spec.args: - config['dispatcher'] = dispatcher - - for key in config: - if key in full_config: - full_config.pop(key) - - return self.callback(**config) - - def _check_event_handler(self, event_handler) -> bool: - if self.event_handlers: - return event_handler in self.event_handlers - elif self.exclude_event_handlers: - return event_handler not in self.exclude_event_handlers - return True - - -class AbstractFilter(abc.ABC): - """ - Abstract class for custom filters. - """ - - @classmethod - @abc.abstractmethod - def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]: - """ - Validate and parse config. - - This method will be called by the filters factory when you bind this filter. - Must be overridden. - - :param full_config: dict with arguments passed to handler registrar - :return: Current filter config - """ - pass - - @abc.abstractmethod - async def check(self, *args) -> bool: - """ - Will be called when filters checks. - - This method must be overridden. - - :param args: - :return: - """ - pass - - async def __call__(self, *args) -> bool: - return await self.check(*args) - - def __invert__(self): - return NotFilter(self) - - def __and__(self, other): - if isinstance(self, AndFilter): - self.append(other) - return self - return AndFilter(self, other) - - def __or__(self, other): - if isinstance(self, OrFilter): - self.append(other) - return self - return OrFilter(self, other) - - -class Filter(AbstractFilter): - """ - You can make subclasses of that class for custom filters. - - Method ``check`` must be overridden - """ - - @classmethod - def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]: - """ - Here method ``validate`` is optional. - If you need to use filter from filters factory you need to override this method. - - :param full_config: dict with arguments passed to handler registrar - :return: Current filter config - """ - pass - - -class BoundFilter(Filter): - """ - To easily create your own filters with one parameter, you can inherit from this filter. - - You need to implement ``__init__`` method with single argument related with key attribute - and ``check`` method where you need to implement filter logic. - """ - - key = None - """Unique name of the filter argument. You need to override this attribute.""" - required = False - """If :obj:`True` this filter will be added to the all of the registered handlers""" - default = None - """Default value for configure required filters""" - - @classmethod - def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]: - """ - If ``cls.key`` is not :obj:`None` and that is in config returns config with that argument. - - :param full_config: - :return: - """ - if cls.key is not None: - if cls.key in full_config: - return {cls.key: full_config[cls.key]} - elif cls.required: - return {cls.key: cls.default} - - -class _LogicFilter(Filter): - @classmethod - def validate(cls, full_config: typing.Dict[str, typing.Any]): - raise ValueError('That filter can\'t be used in filters factory!') - - -class NotFilter(_LogicFilter): - def __init__(self, target): - self.target = wrap_async(target) - - async def check(self, *args): - return not bool(await self.target(*args)) - - -class AndFilter(_LogicFilter): - - def __init__(self, *targets): - self.targets = list(wrap_async(target) for target in targets) - - async def check(self, *args): - """ - All filters must return a positive result - - :param args: - :return: - """ - data = {} - for target in self.targets: - result = await target(*args) - if not result: - return False - if isinstance(result, dict): - data.update(result) - if not data: - return True - return data - - def append(self, target): - self.targets.append(wrap_async(target)) - - -class OrFilter(_LogicFilter): - def __init__(self, *targets): - self.targets = list(wrap_async(target) for target in targets) - - async def check(self, *args): - """ - One of filters must return a positive result - - :param args: - :return: - """ - for target in self.targets: - result = await target(*args) - if result: - if isinstance(result, dict): - return result - return True - return False - - def append(self, target): - self.targets.append(wrap_async(target)) diff --git a/aiogram/dispatcher/filters/state.py b/aiogram/dispatcher/filters/state.py deleted file mode 100644 index 16937e1c..00000000 --- a/aiogram/dispatcher/filters/state.py +++ /dev/null @@ -1,197 +0,0 @@ -import inspect -from typing import Optional - -from ..dispatcher import Dispatcher - - -class State: - """ - State object - """ - - def __init__(self, state: Optional[str] = None, group_name: Optional[str] = None): - self._state = state - self._group_name = group_name - self._group = None - - @property - def group(self): - if not self._group: - raise RuntimeError('This state is not in any group.') - return self._group - - def get_root(self): - return self.group.get_root() - - @property - def state(self): - if self._state is None or self._state == '*': - return self._state - - if self._group_name is None and self._group: - group = self._group.__full_group_name__ - elif self._group_name: - group = self._group_name - else: - group = '@' - - return f'{group}:{self._state}' - - def set_parent(self, group): - if not issubclass(group, StatesGroup): - raise ValueError('Group must be subclass of StatesGroup') - self._group = group - - def __set_name__(self, owner, name): - if self._state is None: - self._state = name - self.set_parent(owner) - - def __str__(self): - return f"" - - __repr__ = __str__ - - async def set(self): - state = Dispatcher.get_current().current_state() - await state.set_state(self.state) - - -class StatesGroupMeta(type): - def __new__(mcs, name, bases, namespace, **kwargs): - cls = super(StatesGroupMeta, mcs).__new__(mcs, name, bases, namespace) - - states = [] - childs = [] - - cls._group_name = name - - for name, prop in namespace.items(): - - if isinstance(prop, State): - states.append(prop) - elif inspect.isclass(prop) and issubclass(prop, StatesGroup): - childs.append(prop) - prop._parent = cls - - cls._parent = None - cls._childs = tuple(childs) - cls._states = tuple(states) - cls._state_names = tuple(state.state for state in states) - - return cls - - @property - def __group_name__(cls) -> str: - return cls._group_name - - @property - def __full_group_name__(cls) -> str: - if cls._parent: - return '.'.join((cls._parent.__full_group_name__, cls._group_name)) - return cls._group_name - - @property - def states(cls) -> tuple: - return cls._states - - @property - def childs(cls) -> tuple: - return cls._childs - - @property - def all_childs(cls): - result = cls.childs - for child in cls.childs: - result += child.childs - return result - - @property - def all_states(cls): - result = cls.states - for group in cls.childs: - result += group.all_states - return result - - @property - def all_states_names(cls): - return tuple(state.state for state in cls.all_states) - - @property - def states_names(cls) -> tuple: - return tuple(state.state for state in cls.states) - - def get_root(cls): - if cls._parent is None: - return cls - return cls._parent.get_root() - - def __contains__(cls, item): - if isinstance(item, str): - return item in cls.all_states_names - if isinstance(item, State): - return item in cls.all_states - if isinstance(item, StatesGroup): - return item in cls.all_childs - return False - - def __str__(self): - return f"" - - -class StatesGroup(metaclass=StatesGroupMeta): - @classmethod - async def next(cls) -> str: - state = Dispatcher.get_current().current_state() - state_name = await state.get_state() - - try: - next_step = cls.states_names.index(state_name) + 1 - except ValueError: - next_step = 0 - - try: - next_state_name = cls.states[next_step].state - except IndexError: - next_state_name = None - - await state.set_state(next_state_name) - return next_state_name - - @classmethod - async def previous(cls) -> str: - state = Dispatcher.get_current().current_state() - state_name = await state.get_state() - - try: - previous_step = cls.states_names.index(state_name) - 1 - except ValueError: - previous_step = 0 - - if previous_step < 0: - previous_state_name = None - else: - previous_state_name = cls.states[previous_step].state - - await state.set_state(previous_state_name) - return previous_state_name - - @classmethod - async def first(cls) -> str: - state = Dispatcher.get_current().current_state() - first_step_name = cls.states_names[0] - - await state.set_state(first_step_name) - return first_step_name - - @classmethod - async def last(cls) -> str: - state = Dispatcher.get_current().current_state() - last_step_name = cls.states_names[-1] - - await state.set_state(last_step_name) - return last_step_name - - -default_state = State() -any_state = State(state='*') diff --git a/aiogram/dispatcher/handler.py b/aiogram/dispatcher/handler.py deleted file mode 100644 index cd5e9b50..00000000 --- a/aiogram/dispatcher/handler.py +++ /dev/null @@ -1,139 +0,0 @@ -import inspect -from contextvars import ContextVar -from dataclasses import dataclass -from typing import Optional, Iterable, List - -ctx_data = ContextVar('ctx_handler_data') -current_handler = ContextVar('current_handler') - - -@dataclass -class FilterObj: - filter: callable - kwargs: dict - is_async: bool - - -class SkipHandler(Exception): - pass - - -class CancelHandler(Exception): - pass - - -def _get_spec(func: callable): - while hasattr(func, '__wrapped__'): # Try to resolve decorated callbacks - func = func.__wrapped__ - spec = inspect.getfullargspec(func) - return spec - - -def _check_spec(spec: inspect.FullArgSpec, kwargs: dict): - if spec.varkw: - return kwargs - - return {k: v for k, v in kwargs.items() if k in spec.args} - - -class Handler: - def __init__(self, dispatcher, once=True, middleware_key=None): - self.dispatcher = dispatcher - self.once = once - - self.handlers: List[Handler.HandlerObj] = [] - self.middleware_key = middleware_key - - def register(self, handler, filters=None, index=None): - """ - Register callback - - Filters can be awaitable or not. - - :param handler: coroutine - :param filters: list of filters - :param index: you can reorder handlers - """ - from .filters import get_filters_spec - - spec = _get_spec(handler) - - if filters and not isinstance(filters, (list, tuple, set)): - filters = [filters] - filters = get_filters_spec(self.dispatcher, filters) - - record = Handler.HandlerObj(handler=handler, spec=spec, filters=filters) - if index is None: - self.handlers.append(record) - else: - self.handlers.insert(index, record) - - def unregister(self, handler): - """ - Remove handler - - :param handler: callback - :return: - """ - for handler_obj in self.handlers: - registered = handler_obj.handler - if handler is registered: - self.handlers.remove(handler_obj) - return True - raise ValueError('This handler is not registered!') - - async def notify(self, *args): - """ - Notify handlers - - :param args: - :return: - """ - from .filters import check_filters, FilterNotPassed - - results = [] - - data = {} - ctx_data.set(data) - - if self.middleware_key: - try: - await self.dispatcher.middleware.trigger(f"pre_process_{self.middleware_key}", args + (data,)) - except CancelHandler: # Allow to cancel current event - return results - - try: - for handler_obj in self.handlers: - try: - data.update(await check_filters(handler_obj.filters, args)) - except FilterNotPassed: - continue - else: - ctx_token = current_handler.set(handler_obj.handler) - try: - if self.middleware_key: - await self.dispatcher.middleware.trigger(f"process_{self.middleware_key}", args + (data,)) - partial_data = _check_spec(handler_obj.spec, data) - response = await handler_obj.handler(*args, **partial_data) - if response is not None: - results.append(response) - if self.once: - break - except SkipHandler: - continue - except CancelHandler: - break - finally: - current_handler.reset(ctx_token) - finally: - if self.middleware_key: - await self.dispatcher.middleware.trigger(f"post_process_{self.middleware_key}", - args + (results, data,)) - - return results - - @dataclass - class HandlerObj: - handler: callable - spec: inspect.FullArgSpec - filters: Optional[Iterable[FilterObj]] = None diff --git a/aiogram/dispatcher/middlewares.py b/aiogram/dispatcher/middlewares.py deleted file mode 100644 index 7d195607..00000000 --- a/aiogram/dispatcher/middlewares.py +++ /dev/null @@ -1,130 +0,0 @@ -import logging -import typing - -log = logging.getLogger("aiogram.Middleware") - - -class MiddlewareManager: - """ - Middlewares manager. Works only with dispatcher. - """ - - def __init__(self, dispatcher): - """ - Init - - :param dispatcher: instance of Dispatcher - """ - self.dispatcher = dispatcher - self.loop = dispatcher.loop - self.bot = dispatcher.bot - self.storage = dispatcher.storage - self.applications = [] - - def setup(self, middleware): - """ - Setup middleware - - :param middleware: - :return: - """ - if not isinstance(middleware, BaseMiddleware): - raise TypeError( - f"`middleware` must be an instance of BaseMiddleware, not {type(middleware)}" - ) - if middleware.is_configured(): - raise ValueError("That middleware is already used!") - - self.applications.append(middleware) - middleware.setup(self) - log.debug(f"Loaded middleware '{middleware.__class__.__name__}'") - return middleware - - async def trigger(self, action: str, args: typing.Iterable): - """ - Call action to middlewares with args lilt. - - :param action: - :param args: - :return: - """ - for app in self.applications: - await app.trigger(action, args) - - -class BaseMiddleware: - """ - Base class for middleware. - - All methods on the middle always must be coroutines and name starts with "on_" like "on_process_message". - """ - - def __init__(self): - self._configured = False - self._manager = None - - @property - def manager(self) -> MiddlewareManager: - """ - Instance of MiddlewareManager - """ - if self._manager is None: - raise RuntimeError("Middleware is not configured!") - return self._manager - - def setup(self, manager): - """ - Mark middleware as configured - - :param manager: - :return: - """ - self._manager = manager - self._configured = True - - def is_configured(self) -> bool: - """ - Check middleware is configured - - :return: - """ - return self._configured - - async def trigger(self, action, args): - """ - Trigger action. - - :param action: - :param args: - :return: - """ - handler_name = f"on_{action}" - handler = getattr(self, handler_name, None) - if not handler: - return None - await handler(*args) - - -class LifetimeControllerMiddleware(BaseMiddleware): - # TODO: Rename class - - skip_patterns = None - - async def pre_process(self, obj, data, *args): - pass - - async def post_process(self, obj, data, *args): - pass - - async def trigger(self, action, args): - if self.skip_patterns is not None and any(item in action for item in self.skip_patterns): - return False - - obj, *args, data = args - if action.startswith("pre_process_"): - await self.pre_process(obj, data, *args) - elif action.startswith("post_process_"): - await self.post_process(obj, data, *args) - else: - return False - return True diff --git a/aiogram/dispatcher/storage.py b/aiogram/dispatcher/storage.py deleted file mode 100644 index c0c108f2..00000000 --- a/aiogram/dispatcher/storage.py +++ /dev/null @@ -1,562 +0,0 @@ -import copy -import typing - -from ..utils.deprecated import warn_deprecated as warn -from ..utils.exceptions import FSMStorageWarning - -# Leak bucket -KEY = "key" -LAST_CALL = "called_at" -RATE_LIMIT = "rate_limit" -RESULT = "result" -EXCEEDED_COUNT = "exceeded" -DELTA = "delta" -THROTTLE_MANAGER = "$throttle_manager" - - -class BaseStorage: - """ - You are able to save current user's state - and data for all steps in states-storage - """ - - async def close(self): - """ - You have to override this method and use when application shutdowns. - Perhaps you would like to save data and etc. - - :return: - """ - raise NotImplementedError - - async def wait_closed(self): - """ - You have to override this method for all asynchronous storages (e.g., Redis). - - :return: - """ - raise NotImplementedError - - @classmethod - def check_address( - cls, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - ) -> (typing.Union[str, int], typing.Union[str, int]): - """ - In all storage's methods chat or user is always required. - If one of them is not provided, you have to set missing value based on the provided one. - - This method performs the check described above. - - :param chat: - :param user: - :return: - """ - if chat is None and user is None: - raise ValueError("`user` or `chat` parameter is required but no one is provided!") - - if user is None and chat is not None: - user = chat - elif user is not None and chat is None: - chat = user - return chat, user - - async def get_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None, - ) -> typing.Optional[str]: - """ - Get current state of user in chat. Return `default` if no record is found. - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param chat: - :param user: - :param default: - :return: - """ - raise NotImplementedError - - async def get_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[typing.Dict] = None, - ) -> typing.Dict: - """ - Get state-data for user in chat. Return `default` if no data is provided in storage. - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param chat: - :param user: - :param default: - :return: - """ - raise NotImplementedError - - async def set_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - state: typing.Optional[typing.AnyStr] = None, - ): - """ - Set new state for user in chat - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param chat: - :param user: - :param state: - """ - raise NotImplementedError - - async def set_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - data: typing.Dict = None, - ): - """ - Set data for user in chat - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param chat: - :param user: - :param data: - """ - raise NotImplementedError - - async def update_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - data: typing.Dict = None, - **kwargs, - ): - """ - Update data for user in chat - - You can use data parameter or|and kwargs. - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param data: - :param chat: - :param user: - :param kwargs: - :return: - """ - raise NotImplementedError - - async def reset_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - ): - """ - Reset data for user in chat. - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param chat: - :param user: - :return: - """ - await self.set_data(chat=chat, user=user, data={}) - - async def reset_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - with_data: typing.Optional[bool] = True, - ): - """ - Reset state for user in chat. - You may desire to use this method when finishing conversations. - - Chat or user is always required. If one of this is not presented, - you have to set missing value based on the provided one. - - :param chat: - :param user: - :param with_data: - :return: - """ - chat, user = self.check_address(chat=chat, user=user) - await self.set_state(chat=chat, user=user, state=None) - if with_data: - await self.set_data(chat=chat, user=user, data={}) - - async def finish( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - ): - """ - Finish conversation for user in chat. - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param chat: - :param user: - :return: - """ - await self.reset_state(chat=chat, user=user, with_data=True) - - def has_bucket(self): - return False - - async def get_bucket( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[dict] = None, - ) -> typing.Dict: - """ - Get bucket for user in chat. Return `default` if no data is provided in storage. - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param chat: - :param user: - :param default: - :return: - """ - raise NotImplementedError - - async def set_bucket( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - bucket: typing.Dict = None, - ): - """ - Set bucket for user in chat - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param chat: - :param user: - :param bucket: - """ - raise NotImplementedError - - async def update_bucket( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - bucket: typing.Dict = None, - **kwargs, - ): - """ - Update bucket for user in chat - - You can use bucket parameter or|and kwargs. - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param bucket: - :param chat: - :param user: - :param kwargs: - :return: - """ - raise NotImplementedError - - async def reset_bucket( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - ): - """ - Reset bucket dor user in chat. - - Chat or user is always required. If one of them is not provided, - you have to set missing value based on the provided one. - - :param chat: - :param user: - :return: - """ - await self.set_data(chat=chat, user=user, data={}) - - -class FSMContext: - def __init__(self, storage, chat, user): - self.storage: BaseStorage = storage - self.chat, self.user = self.storage.check_address(chat=chat, user=user) - - def proxy(self): - return FSMContextProxy(self) - - @staticmethod - def _resolve_state(value): - from .filters.state import State - - if value is None: - return - elif isinstance(value, str): - return value - elif isinstance(value, State): - return value.state - return str(value) - - async def get_state(self, default: typing.Optional[str] = None) -> typing.Optional[str]: - return await self.storage.get_state( - chat=self.chat, user=self.user, default=self._resolve_state(default) - ) - - async def get_data(self, default: typing.Optional[str] = None) -> typing.Dict: - return await self.storage.get_data(chat=self.chat, user=self.user, default=default) - - async def update_data(self, data: typing.Dict = None, **kwargs): - await self.storage.update_data(chat=self.chat, user=self.user, data=data, **kwargs) - - async def set_state(self, state: typing.Union[typing.AnyStr, None] = None): - await self.storage.set_state( - chat=self.chat, user=self.user, state=self._resolve_state(state) - ) - - async def set_data(self, data: typing.Dict = None): - await self.storage.set_data(chat=self.chat, user=self.user, data=data) - - async def reset_state(self, with_data: typing.Optional[bool] = True): - await self.storage.reset_state(chat=self.chat, user=self.user, with_data=with_data) - - async def reset_data(self): - await self.storage.reset_data(chat=self.chat, user=self.user) - - async def finish(self): - await self.storage.finish(chat=self.chat, user=self.user) - - -class FSMContextProxy: - def __init__(self, fsm_context: FSMContext): - super(FSMContextProxy, self).__init__() - self.fsm_context = fsm_context - self._copy = {} - self._data = {} - self._state = None - self._is_dirty = False - - self._closed = True - - async def __aenter__(self): - await self.load() - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - if exc_type is None: - await self.save() - self._closed = True - - def _check_closed(self): - if self._closed: - raise LookupError("Proxy is closed!") - - @classmethod - async def create(cls, fsm_context: FSMContext): - """ - :param fsm_context: - :return: - """ - proxy = cls(fsm_context) - await proxy.load() - return proxy - - async def load(self): - self._closed = False - - self.clear() - self._state = await self.fsm_context.get_state() - self.update(await self.fsm_context.get_data()) - self._copy = copy.deepcopy(self._data) - self._is_dirty = False - - @property - def state(self): - return self._state - - @state.setter - def state(self, value): - self._check_closed() - - self._state = value - self._is_dirty = True - - @state.deleter - def state(self): - self._check_closed() - - self._state = None - self._is_dirty = True - - async def save(self, force=False): - self._check_closed() - - if self._copy != self._data or force: - await self.fsm_context.set_data(data=self._data) - if self._is_dirty or force: - await self.fsm_context.set_state(self.state) - self._is_dirty = False - self._copy = copy.deepcopy(self._data) - - def clear(self): - del self.state - return self._data.clear() - - def get(self, value, default=None): - return self._data.get(value, default) - - def setdefault(self, key, default): - self._check_closed() - - self._data.setdefault(key, default) - - def update(self, data=None, **kwargs): - self._check_closed() - - self._data.update(data, **kwargs) - - def pop(self, key, default=None): - self._check_closed() - - return self._data.pop(key, default) - - def keys(self): - return self._data.keys() - - def values(self): - return self._data.values() - - def items(self): - return self._data.items() - - def as_dict(self): - return copy.deepcopy(self._data) - - def __len__(self): - return len(self._data) - - def __iter__(self): - return self._data.__iter__() - - def __getitem__(self, item): - return self._data[item] - - def __setitem__(self, key, value): - self._check_closed() - - self._data[key] = value - - def __delitem__(self, key): - self._check_closed() - - del self._data[key] - - def __contains__(self, item): - return item in self._data - - def __str__(self): - readable_state = f"'{self.state}'" if self.state else "" - result = f"{self.__class__.__name__} state = {readable_state}, data = {self._data}" - if self._closed: - result += ", closed = True" - return result - - -class DisabledStorage(BaseStorage): - """ - Empty storage. Use it if you don't want to use Finite-State Machine - """ - - async def close(self): - pass - - async def wait_closed(self): - pass - - async def get_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None, - ) -> typing.Optional[str]: - return None - - async def get_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None, - ) -> typing.Dict: - self._warn() - return {} - - async def update_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - data: typing.Dict = None, - **kwargs, - ): - self._warn() - - async def set_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - state: typing.Optional[typing.AnyStr] = None, - ): - self._warn() - - async def set_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - data: typing.Dict = None, - ): - self._warn() - - @staticmethod - def _warn(): - warn( - f"You haven’t set any storage yet so no states and no data will be saved. \n" - f"You can connect MemoryStorage for debug purposes or non-essential data.", - FSMStorageWarning, - 5, - ) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py deleted file mode 100644 index 135fe21e..00000000 --- a/aiogram/dispatcher/webhook.py +++ /dev/null @@ -1,2181 +0,0 @@ -import asyncio -import asyncio.tasks -import datetime -import functools -import ipaddress -import itertools -import typing -import logging -from typing import Dict, List, Optional, Union - -from aiohttp import web -from aiohttp.web_exceptions import HTTPGone - -from .. import types -from ..bot import api -from ..types import ParseMode -from ..types.base import Boolean, Float, Integer, String -from ..utils import helper, markdown -from ..utils import json -from ..utils.deprecated import warn_deprecated as warn -from ..utils.exceptions import TimeoutWarning -from ..utils.payload import prepare_arg - -DEFAULT_WEB_PATH = '/webhook' -DEFAULT_ROUTE_NAME = 'webhook_handler' -BOT_DISPATCHER_KEY = 'BOT_DISPATCHER' - -RESPONSE_TIMEOUT = 55 - -WEBHOOK = 'webhook' -WEBHOOK_CONNECTION = 'WEBHOOK_CONNECTION' -WEBHOOK_REQUEST = 'WEBHOOK_REQUEST' - -TELEGRAM_SUBNET_1 = ipaddress.IPv4Network('149.154.160.0/20') -TELEGRAM_SUBNET_2 = ipaddress.IPv4Network('91.108.4.0/22') - -allowed_ips = set() - -log = logging.getLogger(__name__) - - -def _check_ip(ip: str) -> bool: - """ - Check IP in range - - :param ip: - :return: - """ - address = ipaddress.IPv4Address(ip) - return address in allowed_ips - - -def allow_ip(*ips: typing.Union[str, ipaddress.IPv4Network, ipaddress.IPv4Address]): - """ - Allow ip address. - - :param ips: - :return: - """ - for ip in ips: - if isinstance(ip, ipaddress.IPv4Address): - allowed_ips.add(ip) - elif isinstance(ip, str): - allowed_ips.add(ipaddress.IPv4Address(ip)) - elif isinstance(ip, ipaddress.IPv4Network): - allowed_ips.update(ip.hosts()) - else: - raise ValueError(f"Bad type of ipaddress: {type(ip)} ('{ip}')") - - -# Allow access from Telegram servers -allow_ip(TELEGRAM_SUBNET_1, TELEGRAM_SUBNET_2) - - -class WebhookRequestHandler(web.View): - """ - Simple Wehhook request handler for aiohttp web server. - - You need to register that in app: - - .. code-block:: python3 - - app.router.add_route('*', '/your/webhook/path', WebhookRequestHandler, name='webhook_handler') - - But first you need to configure application for getting Dispatcher instance from request handler! - It must always be with key 'BOT_DISPATCHER' - - .. code-block:: python3 - - bot = Bot(TOKEN, loop) - dp = Dispatcher(bot) - app['BOT_DISPATCHER'] = dp - - """ - - def get_dispatcher(self): - """ - Get Dispatcher instance from environment - - :return: :class:`aiogram.Dispatcher` - """ - dp = self.request.app[BOT_DISPATCHER_KEY] - try: - from aiogram import Bot, Dispatcher - Dispatcher.set_current(dp) - Bot.set_current(dp.bot) - except RuntimeError: - pass - return dp - - async def parse_update(self, bot): - """ - Read update from stream and deserialize it. - - :param bot: bot instance. You an get it from Dispatcher - :return: :class:`aiogram.types.Update` - """ - data = await self.request.json() - update = types.Update(**data) - return update - - async def post(self): - """ - Process POST request - - if one of handler returns instance of :class:`aiogram.dispatcher.webhook.BaseResponse` return it to webhook. - Otherwise do nothing (return 'ok') - - :return: :class:`aiohttp.web.Response` - """ - self.validate_ip() - - # context.update_state({'CALLER': WEBHOOK, - # WEBHOOK_CONNECTION: True, - # WEBHOOK_REQUEST: self.request}) - - dispatcher = self.get_dispatcher() - update = await self.parse_update(dispatcher.bot) - - results = await self.process_update(update) - response = self.get_response(results) - - if response: - web_response = response.get_web_response() - else: - web_response = web.Response(text='ok') - - if self.request.app.get('RETRY_AFTER', None): - web_response.headers['Retry-After'] = self.request.app['RETRY_AFTER'] - - return web_response - - async def get(self): - self.validate_ip() - return web.Response(text='') - - async def head(self): - self.validate_ip() - return web.Response(text='') - - async def process_update(self, update): - """ - Need respond in less than 60 seconds in to webhook. - - So... If you respond greater than 55 seconds webhook automatically respond 'ok' - and execute callback response via simple HTTP request. - - :param update: - :return: - """ - dispatcher = self.get_dispatcher() - loop = dispatcher.loop - - # Analog of `asyncio.wait_for` but without cancelling task - waiter = loop.create_future() - timeout_handle = loop.call_later(RESPONSE_TIMEOUT, asyncio.tasks._release_waiter, waiter) - cb = functools.partial(asyncio.tasks._release_waiter, waiter) - - fut = asyncio.ensure_future(dispatcher.updates_handler.notify(update), loop=loop) - fut.add_done_callback(cb) - - try: - try: - await waiter - except asyncio.futures.CancelledError: - fut.remove_done_callback(cb) - fut.cancel() - raise - - if fut.done(): - return fut.result() - else: - # context.set_value(WEBHOOK_CONNECTION, False) - fut.remove_done_callback(cb) - fut.add_done_callback(self.respond_via_request) - finally: - timeout_handle.cancel() - - def respond_via_request(self, task): - """ - Handle response after 55 second. - - :param task: - :return: - """ - warn(f"Detected slow response into webhook. " - f"(Greater than {RESPONSE_TIMEOUT} seconds)\n" - f"Recommended to use 'async_task' decorator from Dispatcher for handler with long timeouts.", - TimeoutWarning) - - dispatcher = self.get_dispatcher() - loop = dispatcher.loop - - try: - results = task.result() - except Exception as e: - loop.create_task( - dispatcher.errors_handlers.notify(dispatcher, types.Update.get_current(), e)) - else: - response = self.get_response(results) - if response is not None: - asyncio.ensure_future(response.execute_response(dispatcher.bot), loop=loop) - - def get_response(self, results): - """ - Get response object from results. - - :param results: list - :return: - """ - if results is None: - return None - for result in itertools.chain.from_iterable(results): - if isinstance(result, BaseResponse): - return result - - def check_ip(self): - """ - Check client IP. Accept requests only from telegram servers. - - :return: - """ - # For reverse proxy (nginx) - forwarded_for = self.request.headers.get('X-Forwarded-For', None) - if forwarded_for: - return forwarded_for, _check_ip(forwarded_for) - - # For default method - peer_name = self.request.transport.get_extra_info('peername') - if peer_name is not None: - host, _ = peer_name - return host, _check_ip(host) - - # Not allowed and can't get client IP - return None, False - - def validate_ip(self): - """ - Check ip if that is needed. Raise web.HTTPUnauthorized for not allowed hosts. - """ - if self.request.app.get('_check_ip', False): - ip_address, accept = self.check_ip() - if not accept: - log.warning(f"Blocking request from an unauthorized IP: {ip_address}") - raise web.HTTPUnauthorized() - - # context.set_value('TELEGRAM_IP', ip_address) - - -class GoneRequestHandler(web.View): - """ - If a webhook returns the HTTP error 410 Gone for all requests for more than 23 hours successively, - it can be automatically removed. - """ - - async def get(self): - raise HTTPGone() - - async def post(self): - raise HTTPGone() - - -def configure_app(dispatcher, app: web.Application, path=DEFAULT_WEB_PATH, route_name=DEFAULT_ROUTE_NAME): - """ - You can prepare web.Application for working with webhook handler. - - :param dispatcher: Dispatcher instance - :param app: :class:`aiohttp.web.Application` - :param path: Path to your webhook. - :param route_name: Name of webhook handler route - :return: - """ - app.router.add_route('*', path, WebhookRequestHandler, name=route_name) - app[BOT_DISPATCHER_KEY] = dispatcher - - -def get_new_configured_app(dispatcher, path=DEFAULT_WEB_PATH): - """ - Create new :class:`aiohttp.web.Application` and configure it. - - :param dispatcher: Dispatcher instance - :param path: Path to your webhook. - :return: - """ - app = web.Application() - configure_app(dispatcher, app, path) - return app - - -class BaseResponse: - """ - Base class for webhook responses. - """ - - @property - def method(self) -> str: - """ - In all subclasses of that class you need to override this property - - :return: str - """ - raise NotImplementedError - - def prepare(self) -> typing.Dict: - """ - You need to override this method. - - :return: response parameters dict - """ - raise NotImplementedError - - def cleanup(self) -> typing.Dict: - """ - Cleanup response after preparing. Remove empty fields. - - :return: response parameters dict - """ - return {k: v for k, v in self.prepare().items() if v is not None} - - def get_response(self): - """ - Get response object - - :return: - """ - return {'method': self.method, **self.cleanup()} - - def get_web_response(self): - """ - Get prepared web response with JSON data. - - :return: :class:`aiohttp.web.Response` - """ - return web.json_response(self.get_response(), dumps=json.dumps) - - async def execute_response(self, bot): - """ - Use this method if you want to execute response as simple HTTP request. - - :param bot: Bot instance. - :return: - """ - method_name = helper.HelperMode.apply(self.method, helper.HelperMode.snake_case) - method = getattr(bot, method_name, None) - if method: - return await method(**self.cleanup()) - return await bot.request(self.method, self.cleanup()) - - async def __call__(self, bot=None): - if bot is None: - from aiogram import Bot - bot = Bot.get_current() - return await self.execute_response(bot) - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - return await self() - - -class ReplyToMixin: - """ - Mixin for responses where from which can reply to messages. - """ - - def reply(self, message: typing.Union[int, types.Message]): - """ - Reply to message - - :param message: :obj:`int` or :obj:`types.Message` - :return: self - """ - setattr(self, 'reply_to_message_id', message.message_id if isinstance(message, types.Message) else message) - return self - - def to(self, target: typing.Union[types.Message, types.Chat, types.base.Integer, types.base.String]): - """ - Send to chat - - :param target: message or chat or id - :return: - """ - if isinstance(target, types.Message): - chat_id = target.chat.id - elif isinstance(target, types.Chat): - chat_id = target.id - elif isinstance(target, (int, str)): - chat_id = target - else: - raise TypeError(f"Bad type of target. ({type(target)})") - - setattr(self, 'chat_id', chat_id) - return self - - -class DisableNotificationMixin: - def without_notification(self): - """ - Disable notification - - :return: - """ - setattr(self, 'disable_notification', True) - return self - - -class DisableWebPagePreviewMixin: - def no_web_page_preview(self): - """ - Disable web page preview - - :return: - """ - setattr(self, 'disable_web_page_preview', True) - return self - - -class ParseModeMixin: - def as_html(self): - """ - Set parse_mode to HTML - - :return: - """ - setattr(self, 'parse_mode', ParseMode.HTML) - return self - - def as_markdown(self): - """ - Set parse_mode to Markdown - - :return: - """ - setattr(self, 'parse_mode', ParseMode.MARKDOWN) - return self - - @staticmethod - def _global_parse_mode(): - """ - Detect global parse mode - - :return: - """ - from aiogram import Bot - bot = Bot.get_current() - if bot is not None: - return bot.parse_mode - - -class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificationMixin, DisableWebPagePreviewMixin): - """ - You can send message with webhook by using this instance of this object. - All arguments is equal with Bot.send_message method. - """ - - __slots__ = ('chat_id', 'text', 'parse_mode', - 'disable_web_page_preview', 'disable_notification', - 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_MESSAGE - - def __init__(self, chat_id: Union[Integer, String] = None, - text: String = None, - parse_mode: Optional[String] = None, - disable_web_page_preview: Optional[Boolean] = None, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param text: String - Text of the message to be sent - :param parse_mode: String (Optional) - Send Markdown or HTML, if you want Telegram apps to show bold, - italic, fixed-width text or inline URLs in your bot's message. - :param disable_web_page_preview: Boolean (Optional) - Disables link previews for links in this message - :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive - a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - """ - if text is None: - text = '' - if parse_mode is None: - parse_mode = self._global_parse_mode() - - self.chat_id = chat_id - self.text = text - self.parse_mode = parse_mode - self.disable_web_page_preview = disable_web_page_preview - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self) -> dict: - return { - 'chat_id': self.chat_id, - 'text': self.text, - 'parse_mode': self.parse_mode, - 'disable_web_page_preview': self.disable_web_page_preview, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - def write(self, *text, sep=' '): - """ - Write text to response - - :param text: - :param sep: - :return: - """ - self.text += markdown.text(*text, sep) - return self - - def write_ln(self, *text, sep=' '): - """ - Write line - - :param text: - :param sep: - :return: - """ - if self.text and self.text[-1] != '\n': - self.text += '\n' - self.text += markdown.text(*text, sep) + '\n' - return self - - -class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for forward messages of any kind on to webhook. - """ - __slots__ = ('chat_id', 'from_chat_id', 'message_id', 'disable_notification') - - method = api.Methods.FORWARD_MESSAGE - - def __init__(self, chat_id: Union[Integer, String] = None, - from_chat_id: Union[Integer, String] = None, - message_id: Integer = None, - disable_notification: Optional[Boolean] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username of the - target channel (in the format @channelusername) - :param from_chat_id: Union[Integer, String] - Unique identifier for the chat where the original - message was sent (or channel username in the format @channelusername) - :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a - notification with no sound. - :param message_id: Integer - Message identifier in the chat specified in from_chat_id - """ - self.chat_id = chat_id - self.from_chat_id = from_chat_id - self.message_id = message_id - self.disable_notification = disable_notification - - def message(self, message: types.Message): - """ - Select target message - - :param message: - :return: - """ - setattr(self, 'from_chat_id', message.chat.id) - setattr(self, 'message_id', message.message_id) - return self - - def prepare(self) -> dict: - return { - 'chat_id': self.chat_id, - 'from_chat_id': self.from_chat_id, - 'message_id': self.message_id, - 'disable_notification': self.disable_notification - } - - -class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send photo on to webhook. - """ - - __slots__ = ('chat_id', 'photo', 'caption', 'disable_notification', 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_PHOTO - - def __init__(self, chat_id: Union[Integer, String], - photo: String, - caption: Optional[String] = None, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username of - the target channel (in the format @channelusername) - :param photo: String - 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. - :param caption: String (Optional) - Photo caption (may also be used when resending photos by file_id), - 0-200 characters - :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive - a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - """ - self.chat_id = chat_id - self.photo = photo - self.caption = caption - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'photo': self.photo, - 'caption': self.caption, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send audio on to webhook. - """ - - __slots__ = ('chat_id', 'audio', 'caption', 'duration', 'performer', 'title', - 'disable_notification', 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_AUDIO - - def __init__(self, chat_id: Union[Integer, String], - audio: String, - caption: Optional[String] = None, - duration: Optional[Integer] = None, - performer: Optional[String] = None, - title: Optional[String] = None, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param audio: String - 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. - :param caption: String (Optional) - Audio caption, 0-200 characters - :param duration: Integer (Optional) - Duration of the audio in seconds - :param performer: String (Optional) - Performer - :param title: String (Optional) - Track name - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - """ - self.chat_id = chat_id - self.audio = audio - self.caption = caption - self.duration = duration - self.performer = performer - self.title = title - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'audio': self.audio, - 'caption': self.caption, - 'duration': self.duration, - 'performer': self.performer, - 'title': self.title, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send document on to webhook. - """ - - __slots__ = ('chat_id', 'document', 'caption', 'disable_notification', 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_DOCUMENT - - def __init__(self, chat_id: Union[Integer, String], - document: String, - caption: Optional[String] = None, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param document: String - 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. - :param caption: String (Optional) - Document caption - (may also be used when resending documents by file_id), 0-200 characters - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - """ - self.chat_id = chat_id - self.document = document - self.caption = caption - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'document': self.document, - 'caption': self.caption, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send video on to webhook. - """ - - __slots__ = ('chat_id', 'video', 'duration', 'width', 'height', 'caption', 'disable_notification', - 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_VIDEO - - def __init__(self, chat_id: Union[Integer, String], - video: String, - duration: Optional[Integer] = None, - width: Optional[Integer] = None, - height: Optional[Integer] = None, - caption: Optional[String] = None, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param video: String - 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. - :param duration: Integer (Optional) - Duration of sent video in seconds - :param width: Integer (Optional) - Video width - :param height: Integer (Optional) - Video height - :param caption: String (Optional) - Video caption (may also be used when resending videos by file_id), - 0-200 characters - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - """ - self.chat_id = chat_id - self.video = video - self.duration = duration - self.width = width - self.height = height - self.caption = caption - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'video': self.video, - 'duration': self.duration, - 'width': self.width, - 'height': self.height, - 'caption': self.caption, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send voice on to webhook. - """ - - __slots__ = ('chat_id', 'voice', 'caption', 'duration', 'disable_notification', - 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_VOICE - - def __init__(self, chat_id: Union[Integer, String], - voice: String, - caption: Optional[String] = None, - duration: Optional[Integer] = None, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param voice: String - 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. - :param caption: String (Optional) - Voice message caption, 0-200 characters - :param duration: Integer (Optional) - Duration of the voice message in seconds - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - """ - self.chat_id = chat_id - self.voice = voice - self.caption = caption - self.duration = duration - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'voice': self.voice, - 'caption': self.caption, - 'duration': self.duration, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send video note on to webhook. - """ - - __slots__ = ('chat_id', 'video_note', 'duration', 'length', 'disable_notification', - 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_VIDEO_NOTE - - def __init__(self, chat_id: Union[Integer, String], - video_note: String, - duration: Optional[Integer] = None, - length: Optional[Integer] = None, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param video_note: String - 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. Sending video notes by a URL is currently unsupported - :param duration: Integer (Optional) - Duration of sent video in seconds - :param length: Integer (Optional) - Video width and height - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - """ - self.chat_id = chat_id - self.video_note = video_note - self.duration = duration - self.length = length - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'video_note': self.video_note, - 'duration': self.duration, - 'length': self.length, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use this method to send a group of photos or videos as an album. - """ - - __slots__ = ('chat_id', 'media', 'disable_notification', 'reply_to_message_id') - - method = api.Methods.SEND_MEDIA_GROUP - - def __init__(self, chat_id: Union[Integer, String], - media: Union[types.MediaGroup, List] = None, - disable_notification: typing.Union[Boolean, None] = None, - reply_to_message_id: typing.Union[Integer, None] = None): - """ - Use this method to send a group of photos or videos as an album. - - Source: https://core.telegram.org/bots/api#sendmediagroup - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param media: A JSON-serialized array describing photos and videos to be sent - :type media: :obj:`typing.Union[types.MediaGroup, typing.List]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_to_message_id: If the message is a reply, ID of the original message - :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]` - :return: On success, an array of the sent Messages is returned. - :rtype: typing.List[types.Message] - """ - if media is None: - media = types.MediaGroup() - elif isinstance(media, list): - # Convert list to MediaGroup - media = types.MediaGroup(media) - - self.chat_id = chat_id - self.media = media - self.disable_notifications = disable_notification - self.reply_to_message_id = reply_to_message_id - - def prepare(self): - files = dict(self.media.get_files()) - if files: - raise TypeError('Allowed only file ID or URL\'s') - - media = prepare_arg(self.media) - - return { - 'chat_id': self.chat_id, - 'media': media, - 'disable_notifications': self.disable_notifications, - 'reply_to_message_id': self.reply_to_message_id - } - - def attach_photo(self, photo: String, caption: String = None): - """ - Attach photo - - :param photo: - :param caption: - :return: self - """ - self.media.attach_photo(photo, caption) - return self - - def attach_video(self, video: String, caption: String = None, width: Integer = None, - height: Integer = None, duration: Integer = None): - """ - Attach video - - :param video: - :param caption: - :param width: - :param height: - :param duration: - :return: self - """ - self.media.attach_video(video, caption, width=width, height=height, duration=duration) - return self - - -class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send location on to webhook. - """ - - __slots__ = ('chat_id', 'latitude', 'longitude', 'disable_notification', 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_LOCATION - - def __init__(self, chat_id: Union[Integer, String], - latitude: Float, longitude: Float, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param latitude: Float - Latitude of location - :param longitude: Float - Longitude of location - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - """ - self.chat_id = chat_id - self.latitude = latitude - self.longitude = longitude - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'latitude': self.latitude, - 'longitude': self.longitude, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send venue on to webhook. - """ - - __slots__ = ('chat_id', 'latitude', 'longitude', 'title', 'address', 'foursquare_id', - 'disable_notification', 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_VENUE - - def __init__(self, chat_id: Union[Integer, String], - latitude: Float, - longitude: Float, - title: String, - address: String, - foursquare_id: Optional[String] = None, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param latitude: Float - Latitude of the venue - :param longitude: Float - Longitude of the venue - :param title: String - Name of the venue - :param address: String - Address of the venue - :param foursquare_id: String (Optional) - Foursquare identifier of the venue - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - """ - self.chat_id = chat_id - self.latitude = latitude - self.longitude = longitude - self.title = title - self.address = address - self.foursquare_id = foursquare_id - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'latitude': self.latitude, - 'longitude': self.longitude, - 'title': self.title, - 'address': self.address, - 'foursquare_id': self.foursquare_id, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send contact on to webhook. - """ - - __slots__ = ('chat_id', 'phone_number', 'first_name', 'last_name', 'disable_notification', - 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_CONTACT - - def __init__(self, chat_id: Union[Integer, String], - phone_number: String, - first_name: String, - last_name: Optional[String] = None, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or - username of the target channel (in the format @channelusername) - :param phone_number: String - Contact's phone number - :param first_name: String - Contact's first name - :param last_name: String (Optional) - Contact's last name - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove keyboard or to force a reply from the user. - """ - self.chat_id = chat_id - self.phone_number = phone_number - self.first_name = first_name - self.last_name = last_name - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'phone_number': self.phone_number, - 'first_name': self.first_name, - 'last_name': self.last_name, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class SendChatAction(BaseResponse): - """ - Use that response type for send chat action on to webhook. - """ - - __slots__ = ('chat_id', 'action') - - method = api.Methods.SEND_CHAT_ACTION - - def __init__(self, chat_id: Union[Integer, String], action: String): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param action: String - Type of action to broadcast. Choose one, depending on what the user is about to receive: - typing for text messages, upload_photo for photos, record_video or upload_video for videos, - record_audio or upload_audio for audio files, upload_document for general files, - find_location for location data, record_video_note or upload_video_note for video notes. - """ - self.chat_id = chat_id - self.action = action - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'action': self.action - } - - -class KickChatMember(BaseResponse): - """ - Use that response type for kick chat member on to webhook. - """ - - __slots__ = ('chat_id', 'user_id', 'until_date') - - method = api.Methods.KICK_CHAT_MEMBER - - def __init__(self, chat_id: Union[Integer, String], - user_id: Integer, - until_date: Optional[ - Union[Integer, datetime.datetime, datetime.timedelta]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target group or username - of the target supergroup or channel (in the format @channelusername) - :param user_id: Integer - Unique identifier of the target user - :param until_date: Integer - Date when the user will be unbanned, unix time. If user is banned for - more than 366 days or less than 30 seconds from the current time they are considered to be banned forever - """ - self.chat_id = chat_id - self.user_id = user_id - self.until_date = until_date - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'user_id': self.user_id, - 'until_date': prepare_arg(self.until_date), - } - - -class UnbanChatMember(BaseResponse): - """ - Use that response type for unban chat member on to webhook. - """ - - __slots__ = ('chat_id', 'user_id') - - method = api.Methods.UNBAN_CHAT_MEMBER - - def __init__(self, chat_id: Union[Integer, String], user_id: Integer): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target group or - username of the target supergroup or channel (in the format @username) - :param user_id: Integer - Unique identifier of the target user - """ - self.chat_id = chat_id - self.user_id = user_id - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'user_id': self.user_id - } - - -class RestrictChatMember(BaseResponse): - """ - Use that response type for restrict chat member on to webhook. - """ - - __slots__ = ('chat_id', 'user_id', 'until_date', 'can_send_messages', 'can_send_media_messages', - 'can_send_other_messages', 'can_add_web_page_previews') - - method = api.Methods.RESTRICT_CHAT_MEMBER - - def __init__(self, chat_id: Union[Integer, String], - user_id: Integer, - until_date: Optional[Union[Integer, datetime.datetime, datetime.timedelta]] = None, - can_send_messages: Optional[Boolean] = None, - can_send_media_messages: Optional[Boolean] = None, - can_send_other_messages: Optional[Boolean] = None, - can_add_web_page_previews: Optional[Boolean] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat - or username of the target supergroup (in the format @supergroupusername) - :param user_id: Integer - Unique identifier of the target user - :param until_date: Integer - Date when restrictions will be lifted for the user, unix time. - If user is restricted for more than 366 days or less than 30 seconds from the current time, - they are considered to be restricted forever - :param can_send_messages: Boolean - Pass True, if the user can send text messages, contacts, - locations and venues - :param can_send_media_messages: Boolean - Pass True, if the user can send audios, documents, - photos, videos, video notes and voice notes, implies can_send_messages - :param can_send_other_messages: Boolean - Pass True, if the user can send animations, games, - stickers and use inline bots, implies can_send_media_messages - :param can_add_web_page_previews: Boolean - Pass True, if the user may add web page previews - to their messages, implies can_send_media_messages - """ - self.chat_id = chat_id - self.user_id = user_id - self.until_date = until_date - self.can_send_messages = can_send_messages - self.can_send_media_messages = can_send_media_messages - self.can_send_other_messages = can_send_other_messages - self.can_add_web_page_previews = can_add_web_page_previews - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'user_id': self.user_id, - 'until_date': prepare_arg(self.until_date), - 'can_send_messages': self.can_send_messages, - 'can_send_media_messages': self.can_send_media_messages, - 'can_send_other_messages': self.can_send_other_messages, - 'can_add_web_page_previews': self.can_add_web_page_previews - } - - -class PromoteChatMember(BaseResponse): - """ - Use that response type for promote chat member on to webhook. - """ - - __slots__ = ('chat_id', 'user_id', 'can_change_info', 'can_post_messages', 'can_edit_messages', - 'can_delete_messages', 'can_invite_users', 'can_restrict_members', 'can_pin_messages', - 'can_promote_members') - - method = api.Methods.PROMOTE_CHAT_MEMBER - - def __init__(self, chat_id: Union[Integer, String], - user_id: Integer, - can_change_info: Optional[Boolean] = None, - can_post_messages: Optional[Boolean] = None, - can_edit_messages: Optional[Boolean] = None, - can_delete_messages: Optional[Boolean] = None, - can_invite_users: Optional[Boolean] = None, - can_restrict_members: Optional[Boolean] = None, - can_pin_messages: Optional[Boolean] = None, - can_promote_members: Optional[Boolean] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat - or username of the target channel (in the format @channelusername) - :param user_id: Integer - Unique identifier of the target user - :param can_change_info: Boolean - Pass True, if the administrator can change chat title, - photo and other settings - :param can_post_messages: Boolean - Pass True, if the administrator can create channel posts, channels only - :param can_edit_messages: Boolean - Pass True, if the administrator can edit messages of other users, - channels only - :param can_delete_messages: Boolean - Pass True, if the administrator can delete messages of other users - :param can_invite_users: Boolean - Pass True, if the administrator can invite new users to the chat - :param can_restrict_members: Boolean - Pass True, if the administrator can restrict, ban or unban chat members - :param can_pin_messages: Boolean - Pass True, if the administrator can pin messages, supergroups only - :param can_promote_members: Boolean - Pass True, if the administrator can add new administrators - with a subset of his own privileges or demote administrators that he has promoted, - directly or indirectly (promoted by administrators that were appointed by him) - """ - self.chat_id = chat_id - self.user_id = user_id - self.can_change_info = can_change_info - self.can_post_messages = can_post_messages - self.can_edit_messages = can_edit_messages - self.can_delete_messages = can_delete_messages - self.can_invite_users = can_invite_users - self.can_restrict_members = can_restrict_members - self.can_pin_messages = can_pin_messages - self.can_promote_members = can_promote_members - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'user_id': self.user_id, - 'can_change_info': self.can_change_info, - 'can_post_messages': self.can_post_messages, - 'can_edit_messages': self.can_edit_messages, - 'can_delete_messages': self.can_delete_messages, - 'can_invite_users': self.can_invite_users, - 'can_restrict_members': self.can_restrict_members, - 'can_pin_messages': self.can_pin_messages, - 'can_promote_members': self.can_promote_members - } - - -class DeleteChatPhoto(BaseResponse): - """ - Use that response type for delete chat photo on to webhook. - """ - - __slots__ = ('chat_id',) - - method = api.Methods.DELETE_CHAT_PHOTO - - def __init__(self, chat_id: Union[Integer, String]): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat - or username of the target channel (in the format @channelusername) - """ - self.chat_id = chat_id - - def prepare(self): - return { - 'chat_id': self.chat_id - } - - -class SetChatTitle(BaseResponse): - """ - Use that response type for set chat title on to webhook. - """ - - __slots__ = ('chat_id', 'title') - - method = api.Methods.SET_CHAT_TITLE - - def __init__(self, chat_id: Union[Integer, String], title: String): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param title: String - New chat title, 1-255 characters - """ - self.chat_id = chat_id - self.title = title - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'title': self.title - } - - -class SetChatDescription(BaseResponse): - """ - Use that response type for set chat description on to webhook. - """ - - __slots__ = ('chat_id', 'description') - - method = api.Methods.SET_CHAT_DESCRIPTION - - def __init__(self, chat_id: Union[Integer, String], description: String): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat - or username of the target channel (in the format @channelusername) - :param description: String - New chat description, 0-255 characters - """ - self.chat_id = chat_id - self.description = description - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'description': self.description - } - - -class PinChatMessage(BaseResponse, DisableNotificationMixin): - """ - Use that response type for pin chat message on to webhook. - """ - - __slots__ = ('chat_id', 'message_id', 'disable_notification') - - method = api.Methods.PIN_CHAT_MESSAGE - - def __init__(self, chat_id: Union[Integer, String], message_id: Integer, - disable_notification: Optional[Boolean] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat - or username of the target supergroup (in the format @supergroupusername) - :param message_id: Integer - Identifier of a message to pin - :param disable_notification: Boolean - Pass True, if it is not necessary to send a notification - to all group members about the new pinned message - """ - self.chat_id = chat_id - self.message_id = message_id - self.disable_notification = disable_notification - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'message_id': self.message_id, - 'disable_notification': self.disable_notification, - } - - -class UnpinChatMessage(BaseResponse): - """ - Use that response type for unpin chat message on to webhook. - """ - - __slots__ = ('chat_id',) - - method = api.Methods.UNPIN_CHAT_MESSAGE - - def __init__(self, chat_id: Union[Integer, String]): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or - username of the target supergroup (in the format @supergroupusername) - """ - self.chat_id = chat_id - - def prepare(self): - return { - 'chat_id': self.chat_id - } - - -class LeaveChat(BaseResponse): - """ - Use that response type for leave chat on to webhook. - """ - - __slots__ = ('chat_id',) - - method = api.Methods.LEAVE_CHAT - - def __init__(self, chat_id: Union[Integer, String]): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat - or username of the target supergroup or channel (in the format @channelusername) - """ - self.chat_id = chat_id - - def prepare(self): - return { - 'chat_id': self.chat_id - } - - -class AnswerCallbackQuery(BaseResponse): - """ - Use that response type for answer callback query on to webhook. - """ - - __slots__ = ('callback_query_id', 'text', 'show_alert', 'url', 'cache_time') - - method = api.Methods.ANSWER_CALLBACK_QUERY - - def __init__(self, callback_query_id: String, - text: Optional[String] = None, - show_alert: Optional[Boolean] = None, - url: Optional[String] = None, - cache_time: Optional[Integer] = None): - """ - :param callback_query_id: String - Unique identifier for the query to be answered - :param text: String (Optional) - Text of the notification. If not specified, nothing will be shown to the user, - 0-200 characters - :param show_alert: Boolean (Optional) - If true, an alert will be shown by the client instead - of a notification at the top of the chat screen. Defaults to false. - :param url: String (Optional) - URL that will be opened by the user's client. - If you have created a Game and accepted the conditions via @Botfather, - specify the URL that opens your game – note that this will only work - if the query comes from a callback_game button. - Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. - :param cache_time: Integer (Optional) - 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. - """ - self.callback_query_id = callback_query_id - self.text = text - self.show_alert = show_alert - self.url = url - self.cache_time = cache_time - - def prepare(self): - return { - 'callback_query_id': self.callback_query_id, - 'text': self.text, - 'show_alert': self.show_alert, - 'url': self.url, - 'cache_time': self.cache_time - } - - -class EditMessageText(BaseResponse, ParseModeMixin, DisableWebPagePreviewMixin): - """ - Use that response type for edit message text on to webhook. - """ - - __slots__ = ('chat_id', 'message_id', 'inline_message_id', 'text', 'parse_mode', - 'disable_web_page_preview', 'reply_markup') - - method = api.Methods.EDIT_MESSAGE_TEXT - - def __init__(self, text: String, - chat_id: Optional[Union[Integer, String]] = None, - message_id: Optional[Integer] = None, - inline_message_id: Optional[String] = None, - parse_mode: Optional[String] = None, - disable_web_page_preview: Optional[Boolean] = None, - reply_markup: Optional[types.InlineKeyboardMarkup] = None): - """ - :param chat_id: Union[Integer, String] (Optional) - Required if inline_message_id - is not specified. Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param message_id: Integer (Optional) - Required if inline_message_id is not specified. - Identifier of the sent message - :param inline_message_id: String (Optional) - Required if chat_id and message_id are not specified. - Identifier of the inline message - :param text: String - New text of the message - :param parse_mode: String (Optional) - Send Markdown or HTML, if you want Telegram apps to show bold, - italic, fixed-width text or inline URLs in your bot's message. - :param disable_web_page_preview: Boolean (Optional) - Disables link previews for links in this message - :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for - an inline keyboard. - """ - if parse_mode is None: - parse_mode = self._global_parse_mode() - - self.chat_id = chat_id - self.message_id = message_id - self.inline_message_id = inline_message_id - self.text = text - self.parse_mode = parse_mode - self.disable_web_page_preview = disable_web_page_preview - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'message_id': self.message_id, - 'inline_message_id': self.inline_message_id, - 'text': self.text, - 'parse_mode': self.parse_mode, - 'disable_web_page_preview': self.disable_web_page_preview, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class EditMessageCaption(BaseResponse): - """ - Use that response type for edit message caption on to webhook. - """ - - __slots__ = ('chat_id', 'message_id', 'inline_message_id', 'caption', 'reply_markup') - - method = api.Methods.EDIT_MESSAGE_CAPTION - - def __init__(self, chat_id: Optional[Union[Integer, String]] = None, - message_id: Optional[Integer] = None, - inline_message_id: Optional[String] = None, - caption: Optional[String] = None, - reply_markup: Optional[types.InlineKeyboardMarkup] = None): - """ - :param chat_id: Union[Integer, String] (Optional) - Required if inline_message_id - is not specified. Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param message_id: Integer (Optional) - Required if inline_message_id is not specified. - Identifier of the sent message - :param inline_message_id: String (Optional) - Required if chat_id and message_id are not specified. - Identifier of the inline message - :param caption: String (Optional) - New caption of the message - :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for an inline keyboard. - """ - self.chat_id = chat_id - self.message_id = message_id - self.inline_message_id = inline_message_id - self.caption = caption - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'message_id': self.message_id, - 'inline_message_id': self.inline_message_id, - 'caption': self.caption, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class EditMessageReplyMarkup(BaseResponse): - """ - Use that response type for edit message reply markup on to webhook. - """ - - __slots__ = ('chat_id', 'message_id', 'inline_message_id', 'reply_markup') - - method = api.Methods.EDIT_MESSAGE_REPLY_MARKUP - - def __init__(self, chat_id: Optional[Union[Integer, String]] = None, - message_id: Optional[Integer] = None, - inline_message_id: Optional[String] = None, - reply_markup: Optional[types.InlineKeyboardMarkup] = None): - """ - :param chat_id: Union[Integer, String] (Optional) - Required if inline_message_id is not specified. - Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param message_id: Integer (Optional) - Required if inline_message_id is not specified. - Identifier of the sent message - :param inline_message_id: String (Optional) - Required if chat_id and message_id are not specified. - Identifier of the inline message - :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for an inline keyboard. - """ - self.chat_id = chat_id - self.message_id = message_id - self.inline_message_id = inline_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'message_id': self.message_id, - 'inline_message_id': self.inline_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class DeleteMessage(BaseResponse): - """ - Use that response type for delete message on to webhook. - """ - - __slots__ = ('chat_id', 'message_id') - - method = api.Methods.DELETE_MESSAGE - - def __init__(self, chat_id: Union[Integer, String], message_id: Integer): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param message_id: Integer - Identifier of the message to delete - """ - self.chat_id = chat_id - self.message_id = message_id - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'message_id': self.message_id - } - - -class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send sticker on to webhook. - """ - - __slots__ = ('chat_id', 'sticker', 'disable_notification', 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_STICKER - - def __init__(self, chat_id: Union[Integer, String], - sticker: String, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[ - Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, Dict, String]] = None): - """ - :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username - of the target channel (in the format @channelusername) - :param sticker: String - 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. More info on Sending Files » - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - - Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - """ - self.chat_id = chat_id - self.sticker = sticker - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'sticker': self.sticker, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class CreateNewStickerSet(BaseResponse): - """ - Use that response type for create new sticker set on to webhook. - """ - - __slots__ = ('user_id', 'name', 'title', 'png_sticker', 'emojis', 'contains_masks', 'mask_position') - - method = api.Methods.CREATE_NEW_STICKER_SET - - def __init__(self, user_id: Integer, - name: String, title: String, - png_sticker: String, - emojis: String, - contains_masks: Optional[Boolean] = None, - mask_position: Optional[types.MaskPosition] = None): - """ - :param user_id: Integer - User identifier of created sticker set owner - :param name: String - Short name of sticker set, to be used in 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_”. - is case insensitive. 1-64 characters. - :param title: String - Sticker set title, 1-64 characters - :param png_sticker: String - 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. - :param emojis: String - One or more emoji corresponding to the sticker - :param contains_masks: Boolean (Optional) - Pass True, if a set of mask stickers should be created - :param mask_position: types.MaskPosition (Optional) - Position where the mask should be placed on faces - """ - self.user_id = user_id - self.name = name - self.title = title - self.png_sticker = png_sticker - self.emojis = emojis - self.contains_masks = contains_masks - self.mask_position = mask_position - - def prepare(self): - return { - 'user_id': self.user_id, - 'name': self.name, - 'title': self.title, - 'png_sticker': self.png_sticker, - 'emojis': self.emojis, - 'contains_masks': self.contains_masks, - 'mask_position': self.mask_position - } - - -class AddStickerToSet(BaseResponse): - """ - Use that response type for add sticker to set on to webhook. - """ - - __slots__ = ('user_id', 'name', 'png_sticker', 'emojis', 'mask_position') - - method = api.Methods.ADD_STICKER_TO_SET - - def __init__(self, user_id: Integer, - name: String, - png_sticker: String, - emojis: String, - mask_position: Optional[types.MaskPosition] = None): - """ - :param user_id: Integer - User identifier of sticker set owner - :param name: String - Sticker set name - :param png_sticker: String - 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. - :param emojis: String - One or more emoji corresponding to the sticker - :param mask_position: types.MaskPosition (Optional) - Position where the mask should be placed on faces - """ - self.user_id = user_id - self.name = name - self.png_sticker = png_sticker - self.emojis = emojis - self.mask_position = mask_position - - def prepare(self): - return { - 'user_id': self.user_id, - 'name': self.name, - 'png_sticker': self.png_sticker, - 'emojis': self.emojis, - 'mask_position': prepare_arg(self.mask_position), - } - - -class SetStickerPositionInSet(BaseResponse): - """ - Use that response type for set sticker position in set on to webhook. - """ - - __slots__ = ('sticker', 'position') - - method = api.Methods.SET_STICKER_POSITION_IN_SET - - def __init__(self, sticker: String, position: Integer): - """ - :param sticker: String - File identifier of the sticker - :param position: Integer - New sticker position in the set, zero-based - """ - self.sticker = sticker - self.position = position - - def prepare(self): - return { - 'sticker': self.sticker, - 'position': self.position - } - - -class DeleteStickerFromSet(BaseResponse): - """ - Use that response type for delete sticker from set on to webhook. - """ - - __slots__ = ('sticker',) - - method = api.Methods.DELETE_STICKER_FROM_SET - - def __init__(self, sticker: String): - """ - :param sticker: String - File identifier of the sticker - """ - self.sticker = sticker - - def prepare(self): - return { - 'sticker': self.sticker - } - - -class AnswerInlineQuery(BaseResponse): - """ - Use that response type for answer inline query on to webhook. - """ - - __slots__ = ('inline_query_id', 'results', 'cache_time', 'is_personal', 'next_offset', - 'switch_pm_text', 'switch_pm_parameter') - - method = api.Methods.ANSWER_INLINE_QUERY - - def __init__(self, inline_query_id: String, - results: [types.InlineQueryResult], - cache_time: Optional[Integer] = None, - is_personal: Optional[Boolean] = None, - next_offset: Optional[String] = None, - switch_pm_text: Optional[String] = None, - switch_pm_parameter: Optional[String] = None): - """ - :param inline_query_id: String - Unique identifier for the answered query - :param results: [types.InlineQueryResult] - A JSON-serialized array of results for the inline query - :param cache_time: Integer (Optional) - The maximum amount of time in seconds that the result - of the inline query may be cached on the server. Defaults to 300. - :param is_personal: Boolean (Optional) - Pass True, if results may be cached on the server side - only for the user that sent the query. By default, results may be returned - to any user who sends the same query - :param next_offset: String (Optional) - Pass the offset that a client should send in the - next query with the same text to receive more results. - Pass an empty string if there are no more results or if you don‘t support pagination. - Offset length can’t exceed 64 bytes. - :param switch_pm_text: String (Optional) - If passed, clients will display a button with specified text - that switches the user to a private chat with the bot and sends the bot a start - message with the parameter switch_pm_parameter - :param switch_pm_parameter: String (Optional) - Deep-linking parameter for the /start message - sent to the bot when user presses the switch button. 1-64 characters, - only A-Z, a-z, 0-9, _ and - are allowed. - Example: An inline bot that sends YouTube videos can ask the user to connect the bot to their - YouTube account to adapt search results accordingly. To do this, - it displays a ‘Connect your YouTube account’ button above the results, or even before showing any. - The user presses the button, switches to a private chat with the bot and, - in doing so, passes a start parameter that instructs the bot to return an oauth link. - Once done, the bot can offer a switch_inline button so that the user can easily return - to the chat where they wanted to use the bot's inline capabilities. - """ - self.inline_query_id = inline_query_id - self.results = results - self.cache_time = cache_time - self.is_personal = is_personal - self.next_offset = next_offset - self.switch_pm_text = switch_pm_text - self.switch_pm_parameter = switch_pm_parameter - - def prepare(self): - return { - 'inline_query_id': self.inline_query_id, - 'results': prepare_arg(self.results), - 'cache_time': self.cache_time, - 'is_personal': self.is_personal, - 'next_offset': self.next_offset, - 'switch_pm_text': self.switch_pm_text, - 'switch_pm_parameter': self.switch_pm_parameter, - } - - -class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send invoice on to webhook. - """ - - __slots__ = ('chat_id', 'title', 'description', 'payload', 'provider_token', 'start_parameter', - 'currency', 'prices', 'photo_url', 'photo_size', 'photo_width', 'photo_height', - 'need_name', 'need_phone_number', 'need_email', 'need_shipping_address', 'is_flexible', - 'disable_notification', 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_INVOICE - - def __init__(self, chat_id: Integer, - title: String, - description: String, - payload: String, - provider_token: String, - start_parameter: String, - currency: String, - prices: [types.LabeledPrice], - photo_url: Optional[String] = None, - photo_size: Optional[Integer] = None, - photo_width: Optional[Integer] = None, - photo_height: Optional[Integer] = None, - need_name: Optional[Boolean] = None, - need_phone_number: Optional[Boolean] = None, - need_email: Optional[Boolean] = None, - need_shipping_address: Optional[Boolean] = None, - is_flexible: Optional[Boolean] = None, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[types.InlineKeyboardMarkup] = None): - """ - :param chat_id: Integer - Unique identifier for the target private chat - :param title: String - Product name, 1-32 characters - :param description: String - Product description, 1-255 characters - :param payload: String - Bot-defined invoice payload, 1-128 bytes. - This will not be displayed to the user, use for your internal processes. - :param provider_token: String - Payments provider token, obtained via Botfather - :param start_parameter: String - Unique deep-linking parameter that can be used to - generate this invoice when used as a start parameter - :param currency: String - Three-letter ISO 4217 currency code, see more on currencies - :param prices: [types.LabeledPrice] - Price breakdown, a list of components - (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) - :param photo_url: String (Optional) - 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. - :param photo_size: Integer (Optional) - Photo size - :param photo_width: Integer (Optional) - Photo width - :param photo_height: Integer (Optional) - Photo height - :param need_name: Boolean (Optional) - Pass True, if you require the user's full name to complete the order - :param need_phone_number: Boolean (Optional) - Pass True, if you require - the user's phone number to complete the order - :param need_email: Boolean (Optional) - Pass True, if you require the user's email to complete the order - :param need_shipping_address: Boolean (Optional) - Pass True, if you require the user's - shipping address to complete the order - :param is_flexible: Boolean (Optional) - Pass True, if the final price depends on the shipping method - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for an inline keyboard. - If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button. - """ - self.chat_id = chat_id - self.title = title - self.description = description - self.payload = payload - self.provider_token = provider_token - self.start_parameter = start_parameter - self.currency = currency - self.prices = prices - self.photo_url = photo_url - self.photo_size = photo_size - self.photo_width = photo_width - self.photo_height = photo_height - self.need_name = need_name - self.need_phone_number = need_phone_number - self.need_email = need_email - self.need_shipping_address = need_shipping_address - self.is_flexible = is_flexible - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'title': self.title, - 'description': self.description, - 'payload': self.payload, - 'provider_token': self.provider_token, - 'start_parameter': self.start_parameter, - 'currency': self.currency, - 'prices': prepare_arg(self.prices), - 'photo_url': self.photo_url, - 'photo_size': self.photo_size, - 'photo_width': self.photo_width, - 'photo_height': self.photo_height, - 'need_name': self.need_name, - 'need_phone_number': self.need_phone_number, - 'need_email': self.need_email, - 'need_shipping_address': self.need_shipping_address, - 'is_flexible': self.is_flexible, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } - - -class AnswerShippingQuery(BaseResponse): - """ - Use that response type for answer shipping query on to webhook. - """ - - __slots__ = ('shipping_query_id', 'ok', 'shipping_options', 'error_message') - - method = api.Methods.ANSWER_SHIPPING_QUERY - - def __init__(self, shipping_query_id: String, - ok: Boolean, - shipping_options: Optional[typing.List[types.ShippingOption]] = None, - error_message: Optional[String] = None): - """ - :param shipping_query_id: String - Unique identifier for the query to be answered - :param ok: Boolean - Specify True if delivery to the specified address is possible and - False if there are any problems (for example, if delivery to the specified address is not possible) - :param shipping_options: [types.ShippingOption] (Optional) - Required if ok is True. - A JSON-serialized array of available shipping options. - :param error_message: String (Optional) - Required if ok is False. - Error message in human readable form that explains why it is impossible to complete the order - (e.g. "Sorry, delivery to your desired address is unavailable'). - Telegram will display this message to the user. - """ - self.shipping_query_id = shipping_query_id - self.ok = ok - self.shipping_options = shipping_options - self.error_message = error_message - - def prepare(self): - return { - 'shipping_query_id': self.shipping_query_id, - 'ok': self.ok, - 'shipping_options': prepare_arg(self.shipping_options), - 'error_message': self.error_message - } - - -class AnswerPreCheckoutQuery(BaseResponse): - """ - Use that response type for answer pre checkout query on to webhook. - """ - - __slots__ = ('pre_checkout_query_id', 'ok', 'error_message') - - method = api.Methods.ANSWER_PRE_CHECKOUT_QUERY - - def __init__(self, pre_checkout_query_id: String, - ok: Boolean, - error_message: Optional[String] = None): - """ - :param pre_checkout_query_id: String - Unique identifier for the query to be answered - :param ok: Boolean - Specify True if everything is alright (goods are available, etc.) - and the bot is ready to proceed with the order. Use False if there are any problems. - :param error_message: String (Optional) - Required if ok is False. - Error message in human readable form that explains the reason for failure to proceed with the checkout - (e.g. "Sorry, somebody just bought the last of our amazing black T-shirts while you were busy - filling out your payment details. Please choose a different color or garment!"). - Telegram will display this message to the user. - """ - self.pre_checkout_query_id = pre_checkout_query_id - self.ok = ok - self.error_message = error_message - - def prepare(self): - return { - 'pre_checkout_query_id': self.pre_checkout_query_id, - 'ok': self.ok, - 'error_message': self.error_message - } - - -class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin): - """ - Use that response type for send game on to webhook. - """ - - __slots__ = ('chat_id', 'game_short_name', 'disable_notification', 'reply_to_message_id', 'reply_markup') - - method = api.Methods.SEND_GAME - - def __init__(self, chat_id: Integer, - game_short_name: String, - disable_notification: Optional[Boolean] = None, - reply_to_message_id: Optional[Integer] = None, - reply_markup: Optional[types.InlineKeyboardMarkup] = None): - """ - :param chat_id: Integer - Unique identifier for the target chat - :param game_short_name: String - Short name of the game, serves as the unique identifier for the game. - Set up your games via Botfather. - :param disable_notification: Boolean (Optional) - Sends the message silently. - Users will receive a notification with no sound. - :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message - :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for an inline keyboard. - If empty, one ‘Play game_title’ button will be shown. If not empty, the first button must launch the game. - """ - self.chat_id = chat_id - self.game_short_name = game_short_name - self.disable_notification = disable_notification - self.reply_to_message_id = reply_to_message_id - self.reply_markup = reply_markup - - def prepare(self): - return { - 'chat_id': self.chat_id, - 'game_short_name': self.game_short_name, - 'disable_notification': self.disable_notification, - 'reply_to_message_id': self.reply_to_message_id, - 'reply_markup': prepare_arg(self.reply_markup), - } diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py deleted file mode 100644 index 0da1fe38..00000000 --- a/aiogram/types/__init__.py +++ /dev/null @@ -1,206 +0,0 @@ -from . import base -from . import fields -from .animation import Animation -from .audio import Audio -from .auth_widget_data import AuthWidgetData -from .callback_game import CallbackGame -from .callback_query import CallbackQuery -from .chat import Chat, ChatActions, ChatType -from .chat_member import ChatMember, ChatMemberStatus -from .chat_permissions import ChatPermissions -from .chat_photo import ChatPhoto -from .chosen_inline_result import ChosenInlineResult -from .contact import Contact -from .document import Document -from .encrypted_credentials import EncryptedCredentials -from .encrypted_passport_element import EncryptedPassportElement -from .file import File -from .force_reply import ForceReply -from .game import Game -from .game_high_score import GameHighScore -from .inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup -from .inline_query import InlineQuery -from .inline_query_result import ( - InlineQueryResult, - InlineQueryResultArticle, - InlineQueryResultAudio, - InlineQueryResultCachedAudio, - InlineQueryResultCachedDocument, - InlineQueryResultCachedGif, - InlineQueryResultCachedMpeg4Gif, - InlineQueryResultCachedPhoto, - InlineQueryResultCachedSticker, - InlineQueryResultCachedVideo, - InlineQueryResultCachedVoice, - InlineQueryResultContact, - InlineQueryResultDocument, - InlineQueryResultGame, - InlineQueryResultGif, - InlineQueryResultLocation, - InlineQueryResultMpeg4Gif, - InlineQueryResultPhoto, - InlineQueryResultVenue, - InlineQueryResultVideo, - InlineQueryResultVoice, -) -from .input_file import InputFile -from .input_media import ( - InputMedia, - InputMediaAnimation, - InputMediaAudio, - InputMediaDocument, - InputMediaPhoto, - InputMediaVideo, - MediaGroup, -) -from .input_message_content import ( - InputContactMessageContent, - InputLocationMessageContent, - InputMessageContent, - InputTextMessageContent, - InputVenueMessageContent, -) -from .invoice import Invoice -from .labeled_price import LabeledPrice -from .location import Location -from .login_url import LoginUrl -from .mask_position import MaskPosition -from .message import ContentType, ContentTypes, Message, ParseMode -from .message_entity import MessageEntity, MessageEntityType -from .order_info import OrderInfo -from .passport_data import PassportData -from .passport_element_error import ( - PassportElementError, - PassportElementErrorDataField, - PassportElementErrorFile, - PassportElementErrorFiles, - PassportElementErrorFrontSide, - PassportElementErrorReverseSide, - PassportElementErrorSelfie, -) -from .passport_file import PassportFile -from .photo_size import PhotoSize -from .poll import PollOption, Poll -from .pre_checkout_query import PreCheckoutQuery -from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove -from .response_parameters import ResponseParameters -from .shipping_address import ShippingAddress -from .shipping_option import ShippingOption -from .shipping_query import ShippingQuery -from .sticker import Sticker -from .sticker_set import StickerSet -from .successful_payment import SuccessfulPayment -from .update import AllowedUpdates, Update -from .user import User -from .user_profile_photos import UserProfilePhotos -from .venue import Venue -from .video import Video -from .video_note import VideoNote -from .voice import Voice -from .webhook_info import WebhookInfo - -__all__ = ( - "AllowedUpdates", - "Animation", - "Audio", - "AuthWidgetData", - "CallbackGame", - "CallbackQuery", - "Chat", - "ChatActions", - "ChatMember", - "ChatMemberStatus", - "ChatPhoto", - "ChatType", - "ChosenInlineResult", - "Contact", - "ContentType", - "ContentTypes", - "Document", - "EncryptedCredentials", - "EncryptedPassportElement", - "File", - "ForceReply", - "Game", - "GameHighScore", - "InlineKeyboardButton", - "InlineKeyboardMarkup", - "InlineQuery", - "InlineQueryResult", - "InlineQueryResultArticle", - "InlineQueryResultAudio", - "InlineQueryResultCachedAudio", - "InlineQueryResultCachedDocument", - "InlineQueryResultCachedGif", - "InlineQueryResultCachedMpeg4Gif", - "InlineQueryResultCachedPhoto", - "InlineQueryResultCachedSticker", - "InlineQueryResultCachedVideo", - "InlineQueryResultCachedVoice", - "InlineQueryResultContact", - "InlineQueryResultDocument", - "InlineQueryResultGame", - "InlineQueryResultGif", - "InlineQueryResultLocation", - "InlineQueryResultMpeg4Gif", - "InlineQueryResultPhoto", - "InlineQueryResultVenue", - "InlineQueryResultVideo", - "InlineQueryResultVoice", - "InputContactMessageContent", - "InputFile", - "InputLocationMessageContent", - "InputMedia", - "InputMediaAnimation", - "InputMediaAudio", - "InputMediaDocument", - "InputMediaPhoto", - "InputMediaVideo", - "InputMessageContent", - "InputTextMessageContent", - "InputVenueMessageContent", - "Invoice", - "KeyboardButton", - "LabeledPrice", - "Location", - "LoginUrl", - "MaskPosition", - "MediaGroup", - "Message", - "MessageEntity", - "MessageEntityType", - "OrderInfo", - "ParseMode", - "PassportData", - "PassportElementError", - "PassportElementErrorDataField", - "PassportElementErrorFile", - "PassportElementErrorFiles", - "PassportElementErrorFrontSide", - "PassportElementErrorReverseSide", - "PassportElementErrorSelfie", - "PassportFile", - "PhotoSize", - "Poll", - "PollOption", - "PreCheckoutQuery", - "ReplyKeyboardMarkup", - "ReplyKeyboardRemove", - "ResponseParameters", - "ShippingAddress", - "ShippingOption", - "ShippingQuery", - "Sticker", - "StickerSet", - "SuccessfulPayment", - "Update", - "User", - "UserProfilePhotos", - "Venue", - "Video", - "VideoNote", - "Voice", - "WebhookInfo", - "base", - "fields", -) diff --git a/aiogram/types/animation.py b/aiogram/types/animation.py deleted file mode 100644 index fd470b38..00000000 --- a/aiogram/types/animation.py +++ /dev/null @@ -1,20 +0,0 @@ -from . import base -from . import fields -from . import mixins -from .photo_size import PhotoSize - - -class Animation(base.TelegramObject, mixins.Downloadable): - """ - You can provide an animation for your game so that it looks stylish in chats - (check out Lumberjack for an example). - This object represents an animation file to be displayed in the message containing a game. - - https://core.telegram.org/bots/api#animation - """ - - file_id: base.String = fields.Field() - thumb: PhotoSize = fields.Field(base=PhotoSize) - file_name: base.String = fields.Field() - mime_type: base.String = fields.Field() - file_size: base.Integer = fields.Field() diff --git a/aiogram/types/audio.py b/aiogram/types/audio.py deleted file mode 100644 index ccd89be8..00000000 --- a/aiogram/types/audio.py +++ /dev/null @@ -1,20 +0,0 @@ -from . import base -from . import fields -from . import mixins -from .photo_size import PhotoSize - - -class Audio(base.TelegramObject, mixins.Downloadable): - """ - This object represents an audio file to be treated as music by the Telegram clients. - - https://core.telegram.org/bots/api#audio - """ - - file_id: base.String = fields.Field() - duration: base.Integer = fields.Field() - performer: base.String = fields.Field() - title: base.String = fields.Field() - mime_type: base.String = fields.Field() - file_size: base.Integer = fields.Field() - thumb: PhotoSize = fields.Field(base=PhotoSize) diff --git a/aiogram/types/auth_widget_data.py b/aiogram/types/auth_widget_data.py deleted file mode 100644 index a6b9c18a..00000000 --- a/aiogram/types/auth_widget_data.py +++ /dev/null @@ -1,49 +0,0 @@ -from __future__ import annotations - -from aiohttp import web - -from . import base -from . import fields - - -class AuthWidgetData(base.TelegramObject): - id: base.Integer = fields.Field() - first_name: base.String = fields.Field() - last_name: base.String = fields.Field() - username: base.String = fields.Field() - photo_url: base.String = fields.Field() - auth_date: base.String = fields.DateTimeField() - hash: base.String = fields.Field() - - @classmethod - def parse(cls, request: web.Request) -> AuthWidgetData: - """ - Parse request as Telegram auth widget data. - - :param request: - :return: :obj:`AuthWidgetData` - :raise: :obj:`aiohttp.web.HTTPBadRequest` - """ - try: - query = dict(request.query) - query["id"] = int(query["id"]) - query["auth_date"] = int(query["auth_date"]) - widget = AuthWidgetData(**query) - except (ValueError, KeyError): - raise web.HTTPBadRequest(text="Invalid auth data") - else: - return widget - - def validate(self): - return self.bot.check_auth_widget(self.to_python()) - - @property - def full_name(self): - result = self.first_name - if self.last_name: - result += " " - result += self.last_name - return result - - def __hash__(self): - return self.id diff --git a/aiogram/types/base.py b/aiogram/types/base.py deleted file mode 100644 index 2fd9129d..00000000 --- a/aiogram/types/base.py +++ /dev/null @@ -1,288 +0,0 @@ -from __future__ import annotations - -import io -import typing -from typing import TypeVar - -from babel.support import LazyProxy - -from .fields import BaseField -from ..utils import json -from ..utils.mixins import ContextInstanceMixin -if typing.TYPE_CHECKING: - from ..bot.bot import Bot - -__all__ = ('MetaTelegramObject', 'TelegramObject', 'InputFile', 'String', 'Integer', 'Float', 'Boolean') - -PROPS_ATTR_NAME = '_props' -VALUES_ATTR_NAME = '_values' -ALIASES_ATTR_NAME = '_aliases' - -# Binding of builtin types -InputFile = TypeVar('InputFile', 'InputFile', io.BytesIO, io.FileIO, str) -String = TypeVar('String', bound=str) -Integer = TypeVar('Integer', bound=int) -Float = TypeVar('Float', bound=float) -Boolean = TypeVar('Boolean', bound=bool) -T = TypeVar('T') - - -class MetaTelegramObject(type): - """ - Metaclass for telegram objects - """ - _objects = {} - - def __new__(mcs: typing.Type[T], name: str, bases: typing.Tuple[typing.Type], namespace: typing.Dict[str, typing.Any], **kwargs: typing.Any) -> T: - cls = super(MetaTelegramObject, mcs).__new__(mcs, name, bases, namespace) - - props = {} - values = {} - aliases = {} - - # Get props, values, aliases from parent objects - for base in bases: - if not isinstance(base, MetaTelegramObject): - continue - props.update(getattr(base, PROPS_ATTR_NAME)) - # values.update(getattr(base, VALUES_ATTR_NAME)) - aliases.update(getattr(base, ALIASES_ATTR_NAME)) - - # Scan current object for props - for name, prop in ((name, prop) for name, prop in namespace.items() if isinstance(prop, BaseField)): - props[prop.alias] = prop - if prop.default is not None: - values[prop.alias] = prop.default - aliases[name] = prop.alias - - # Set attributes - setattr(cls, PROPS_ATTR_NAME, props) - # setattr(cls, VALUES_ATTR_NAME, values) - setattr(cls, ALIASES_ATTR_NAME, aliases) - - mcs._objects[cls.__name__] = cls - - return cls - - @property - def telegram_types(cls): - return cls._objects - - -class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject): - """ - Abstract class for telegram objects - """ - - def __init__(self, conf: typing.Dict[str, typing.Any]=None, **kwargs: typing.Any) -> None: - """ - Deserialize object - - :param conf: - :param kwargs: - """ - if conf is None: - conf = {} - self._conf = conf - - # Load data - for key, value in kwargs.items(): - if key in self.props: - self.props[key].set_value(self, value, parent=self) - else: - self.values[key] = value - - # Load default values - for key, value in self.props.items(): - if value.default and key not in self.values: - self.values[key] = value.default - - @property - def conf(self) -> typing.Dict[str, typing.Any]: - return self._conf - - @property - def props(self) -> typing.Dict[str, BaseField]: - """ - Get props - - :return: dict with props - """ - return getattr(self, PROPS_ATTR_NAME, {}) - - @property - def props_aliases(self) -> typing.Dict[str, str]: - """ - Get aliases for props - - :return: - """ - return getattr(self, ALIASES_ATTR_NAME, {}) - - @property - def values(self) -> typing.Tuple[str]: - """ - Get values - - :return: - """ - if not hasattr(self, VALUES_ATTR_NAME): - setattr(self, VALUES_ATTR_NAME, {}) - return getattr(self, VALUES_ATTR_NAME) - - @property - def telegram_types(self) -> typing.List[TelegramObject]: - return type(self).telegram_types - - @classmethod - def to_object(cls: typing.Type[T], data: typing.Dict[str, typing.Any]) -> T: - """ - Deserialize object - - :param data: - :return: - """ - return cls(**data) - - @property - def bot(self) -> Bot: - from ..bot.bot import Bot - - bot = Bot.get_current() - if bot is None: - raise RuntimeError("Can't get bot instance from context. " - "You can fix it with setting current instance: " - "'Bot.set_current(bot_instance)'") - return bot - - def to_python(self) -> typing.Dict[str, typing.Any]: - """ - Get object as JSON serializable - - :return: - """ - self.clean() - result = {} - for name, value in self.values.items(): - if name in self.props: - value = self.props[name].export(self) - if isinstance(value, TelegramObject): - value = value.to_python() - if isinstance(value, LazyProxy): - value = str(value) - result[self.props_aliases.get(name, name)] = value - return result - - def clean(self) -> None: - """ - Remove empty values - """ - for key, value in self.values.copy().items(): - if value is None: - del self.values[key] - - def as_json(self) -> str: - """ - Get object as JSON string - - :return: JSON - :rtype: :obj:`str` - """ - return json.dumps(self.to_python()) - - @classmethod - def create(cls: Type[T], *args: typing.Any, **kwargs: typing.Any) -> T: - raise NotImplemented - - def __str__(self) -> str: - """ - Return object as string. Alias for '.as_json()' - - :return: str - """ - return self.as_json() - - def __getitem__(self, item: typing.Union[str, int]) -> typing.Any: - """ - Item getter (by key) - - :param item: - :return: - """ - if item in self.props: - return self.props[item].get_value(self) - raise KeyError(item) - - def __setitem__(self, key: str, value: typing.Any) -> None: - """ - Item setter (by key) - - :param key: - :param value: - :return: - """ - if key in self.props: - return self.props[key].set_value(self, value, self.conf.get('parent', None)) - raise KeyError(key) - - def __contains__(self, item: typing.Dict[str, typing.Any]) -> bool: - """ - Check key contains in that object - - :param item: - :return: - """ - self.clean() - return item in self.values - - def __iter__(self) -> typing.Iterator[str]: - """ - Iterate over items - - :return: - """ - for item in self.to_python().items(): - yield item - - def iter_keys(self) -> typing.Generator[typing.Any, None, None]: - """ - Iterate over keys - - :return: - """ - for key, _ in self: - yield key - - def iter_values(self) -> typing.Generator[typing.Any, None, None]: - """ - Iterate over values - - :return: - """ - for _, value in self: - yield value - - def __hash__(self) -> int: - def _hash(obj)-> int: - buf: int = 0 - if isinstance(obj, list): - for item in obj: - buf += _hash(item) - elif isinstance(obj, dict): - for dict_key, dict_value in obj.items(): - buf += hash(dict_key) + _hash(dict_value) - else: - try: - buf += hash(obj) - except TypeError: # Skip unhashable objects - pass - return buf - - result = 0 - for key, value in sorted(self.values.items()): - result += hash(key) + _hash(value) - - return result - - def __eq__(self, other: TelegramObject) -> bool: - return isinstance(other, self.__class__) and hash(other) == hash(self) diff --git a/aiogram/types/callback_game.py b/aiogram/types/callback_game.py deleted file mode 100644 index 254df324..00000000 --- a/aiogram/types/callback_game.py +++ /dev/null @@ -1,11 +0,0 @@ -from . import base - - -class CallbackGame(base.TelegramObject): - """ - A placeholder, currently holds no information. Use BotFather to set up your game. - - https://core.telegram.org/bots/api#callbackgame - """ - - pass diff --git a/aiogram/types/callback_query.py b/aiogram/types/callback_query.py deleted file mode 100644 index 19dcb373..00000000 --- a/aiogram/types/callback_query.py +++ /dev/null @@ -1,70 +0,0 @@ -import typing - -from . import base -from . import fields -from .message import Message -from .user import User - - -class CallbackQuery(base.TelegramObject): - """ - This object represents an incoming callback query from a callback button in an inline keyboard. - - If the button that originated the query was attached to a message sent by the bot, - the field message will be present. - - If the button was attached to a message sent via the bot (in inline mode), - the field inline_message_id will be present. - - Exactly one of the fields data or game_short_name will be present. - - https://core.telegram.org/bots/api#callbackquery - """ - - id: base.String = fields.Field() - from_user: User = fields.Field(alias="from", base=User) - message: Message = fields.Field(base=Message) - inline_message_id: base.String = fields.Field() - chat_instance: base.String = fields.Field() - data: base.String = fields.Field() - game_short_name: base.String = fields.Field() - - async def answer( - self, - text: typing.Union[base.String, None] = None, - show_alert: typing.Union[base.Boolean, None] = None, - url: typing.Union[base.String, None] = None, - cache_time: typing.Union[base.Integer, None] = None, - ): - """ - Use this method to send answers to callback queries sent from inline keyboards. - The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. - - 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 and accept the terms. - Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. - - Source: https://core.telegram.org/bots/api#answercallbackquery - - :param text: Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters - :type text: :obj:`typing.Union[base.String, None]` - :param show_alert: If true, an alert will be shown by the client instead of a notification - at the top of the chat screen. Defaults to false. - :type show_alert: :obj:`typing.Union[base.Boolean, None]` - :param url: URL that will be opened by the user's client. - :type url: :obj:`typing.Union[base.String, None]` - :param cache_time: The maximum amount of time in seconds that the - result of the callback query may be cached client-side. - :type cache_time: :obj:`typing.Union[base.Integer, None]` - :return: On success, True is returned. - :rtype: :obj:`base.Boolean`""" - await self.bot.answer_callback_query( - callback_query_id=self.id, - text=text, - show_alert=show_alert, - url=url, - cache_time=cache_time, - ) - - def __hash__(self): - return hash(self.id) diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py deleted file mode 100644 index 66b8fe4d..00000000 --- a/aiogram/types/chat.py +++ /dev/null @@ -1,637 +0,0 @@ -from __future__ import annotations - -import asyncio -import datetime -import typing - -from . import base -from . import fields -from .chat_permissions import ChatPermissions -from .chat_photo import ChatPhoto -from ..utils import helper -from ..utils import markdown - - -class Chat(base.TelegramObject): - """ - This object represents a chat. - - https://core.telegram.org/bots/api#chat - """ - id: base.Integer = fields.Field() - type: base.String = fields.Field() - title: base.String = fields.Field() - username: base.String = fields.Field() - first_name: base.String = fields.Field() - last_name: base.String = fields.Field() - all_members_are_administrators: base.Boolean = fields.Field() - photo: ChatPhoto = fields.Field(base=ChatPhoto) - description: base.String = fields.Field() - invite_link: base.String = fields.Field() - pinned_message: 'Message' = fields.Field(base='Message') - permissions: ChatPermissions = fields.Field(base=ChatPermissions) - sticker_set_name: base.String = fields.Field() - can_set_sticker_set: base.Boolean = fields.Field() - - def __hash__(self): - return self.id - - @property - def full_name(self): - if self.type == ChatType.PRIVATE: - full_name = self.first_name - if self.last_name: - full_name += ' ' + self.last_name - return full_name - return self.title - - @property - def mention(self): - """ - Get mention if a Chat has a username, or get full name if this is a Private Chat, otherwise None is returned - """ - if self.username: - return '@' + self.username - if self.type == ChatType.PRIVATE: - return self.full_name - return None - - @property - def user_url(self): - if self.type != ChatType.PRIVATE: - raise TypeError('`user_url` property is only available in private chats!') - - return f"tg://user?id={self.id}" - - def get_mention(self, name=None, as_html=False): - if name is None: - name = self.mention - if as_html: - return markdown.hlink(name, self.user_url) - return markdown.link(name, self.user_url) - - async def get_url(self): - """ - Use this method to get chat link. - Private chat returns user link. - Other chat types return either username link (if they are public) or invite link (if they are private). - :return: link - :rtype: :obj:`base.String` - """ - if self.type == ChatType.PRIVATE: - return f"tg://user?id={self.id}" - - if self.username: - return f'https://t.me/{self.username}' - - if self.invite_link: - return self.invite_link - - await self.update_chat() - return self.invite_link - - async def update_chat(self): - """ - User this method to update Chat data - - :return: None - """ - other = await self.bot.get_chat(self.id) - - for key, value in other: - self[key] = value - - async def set_photo(self, photo): - """ - Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ - setting is off in the target group. - - Source: https://core.telegram.org/bots/api#setchatphoto - - :param photo: New chat photo, uploaded using multipart/form-data - :type photo: :obj:`base.InputFile` - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.set_chat_photo(self.id, photo) - - async def delete_photo(self): - """ - Use this method to delete a chat photo. Photos can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ - setting is off in the target group. - - Source: https://core.telegram.org/bots/api#deletechatphoto - - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.delete_chat_photo(self.id) - - async def set_title(self, title): - """ - Use this method to change the title of a chat. Titles can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ - setting is off in the target group. - - Source: https://core.telegram.org/bots/api#setchattitle - - :param title: New chat title, 1-255 characters - :type title: :obj:`base.String` - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.set_chat_title(self.id, title) - - async def set_description(self, description): - """ - Use this method to change the description of a supergroup or a channel. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Source: https://core.telegram.org/bots/api#setchatdescription - - :param description: New chat description, 0-255 characters - :type description: :obj:`typing.Union[base.String, None]` - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.delete_chat_description(self.id, description) - - async def kick(self, user_id: base.Integer, - until_date: typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None] = None): - """ - Use this method to kick a user from a group, a supergroup or a channel. - In the case of supergroups and channels, the user will not be able to return to the group - on their own using invite links, etc., unless unbanned first. - - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ setting - is off in the target group. - Otherwise members may only be removed by the group's creator or by the member that added them. - - Source: https://core.telegram.org/bots/api#kickchatmember - - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :param until_date: Date when the user will be unbanned, unix time. - :type until_date: :obj:`typing.Union[base.Integer, None]` - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.kick_chat_member(self.id, user_id=user_id, until_date=until_date) - - async def unban(self, user_id: base.Integer): - """ - Use this method to unban a previously kicked user in a supergroup or channel. ` - The user will not return to the group or channel automatically, but will be able to join via link, etc. - - The bot must be an administrator for this to work. - - Source: https://core.telegram.org/bots/api#unbanchatmember - - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.unban_chat_member(self.id, user_id=user_id) - - async def restrict(self, user_id: base.Integer, - permissions: typing.Optional[ChatPermissions] = None, - until_date: typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None] = None, - can_send_messages: typing.Union[base.Boolean, None] = None, - can_send_media_messages: typing.Union[base.Boolean, None] = None, - can_send_other_messages: typing.Union[base.Boolean, None] = None, - can_add_web_page_previews: typing.Union[base.Boolean, None] = None) -> base.Boolean: - """ - Use this method to restrict a user in a supergroup. - The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. - Pass True for all boolean parameters to lift restrictions from a user. - - Source: https://core.telegram.org/bots/api#restrictchatmember - - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :param permissions: New user permissions - :type permissions: :obj:`ChatPermissions` - :param until_date: Date when restrictions will be lifted for the user, unix time. - :type until_date: :obj:`typing.Union[base.Integer, None]` - :param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues - :type can_send_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_send_media_messages: Pass True, if the user can send audios, documents, photos, videos, - video notes and voice notes, implies can_send_messages - :type can_send_media_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_send_other_messages: Pass True, if the user can send animations, games, stickers and - use inline bots, implies can_send_media_messages - :type can_send_other_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_add_web_page_previews: Pass True, if the user may add web page previews to their messages, - implies can_send_media_messages - :type can_add_web_page_previews: :obj:`typing.Union[base.Boolean, None]` - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.restrict_chat_member(self.id, user_id=user_id, - permissions=permissions, - until_date=until_date, - can_send_messages=can_send_messages, - can_send_media_messages=can_send_media_messages, - can_send_other_messages=can_send_other_messages, - can_add_web_page_previews=can_add_web_page_previews) - - async def promote(self, user_id: base.Integer, - can_change_info: typing.Union[base.Boolean, None] = None, - can_post_messages: typing.Union[base.Boolean, None] = None, - can_edit_messages: typing.Union[base.Boolean, None] = None, - can_delete_messages: typing.Union[base.Boolean, None] = None, - can_invite_users: typing.Union[base.Boolean, None] = None, - can_restrict_members: typing.Union[base.Boolean, None] = None, - can_pin_messages: typing.Union[base.Boolean, None] = None, - can_promote_members: typing.Union[base.Boolean, None] = None) -> base.Boolean: - """ - Use this method to promote or demote a user in a supergroup or a channel. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Pass False for all boolean parameters to demote a user. - - Source: https://core.telegram.org/bots/api#promotechatmember - - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :param can_change_info: Pass True, if the administrator can change chat title, photo and other settings - :type can_change_info: :obj:`typing.Union[base.Boolean, None]` - :param can_post_messages: Pass True, if the administrator can create channel posts, channels only - :type can_post_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_edit_messages: Pass True, if the administrator can edit messages of other users, channels only - :type can_edit_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_delete_messages: Pass True, if the administrator can delete messages of other users - :type can_delete_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_invite_users: Pass True, if the administrator can invite new users to the chat - :type can_invite_users: :obj:`typing.Union[base.Boolean, None]` - :param can_restrict_members: Pass True, if the administrator can restrict, ban or unban chat members - :type can_restrict_members: :obj:`typing.Union[base.Boolean, None]` - :param can_pin_messages: Pass True, if the administrator can pin messages, supergroups only - :type can_pin_messages: :obj:`typing.Union[base.Boolean, None]` - :param can_promote_members: Pass True, if the administrator can add new administrators - with a subset of his own privileges or demote administrators that he has promoted, - directly or indirectly (promoted by administrators that were appointed by him) - :type can_promote_members: :obj:`typing.Union[base.Boolean, None]` - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.promote_chat_member(self.id, - user_id=user_id, - can_change_info=can_change_info, - can_post_messages=can_post_messages, - can_edit_messages=can_edit_messages, - can_delete_messages=can_delete_messages, - can_invite_users=can_invite_users, - can_restrict_members=can_restrict_members, - can_pin_messages=can_pin_messages, - can_promote_members=can_promote_members) - - async def pin_message(self, message_id: int, disable_notification: bool = False): - """ - Use this method to pin a message in a supergroup. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Source: https://core.telegram.org/bots/api#pinchatmessage - - :param message_id: Identifier of a message to pin - :type message_id: :obj:`base.Integer` - :param disable_notification: Pass True, if it is not necessary to send a notification to - all group members about the new pinned message - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.pin_chat_message(self.id, message_id, disable_notification) - - async def unpin_message(self): - """ - Use this method to unpin a message in a supergroup chat. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Source: https://core.telegram.org/bots/api#unpinchatmessage - - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.unpin_chat_message(self.id) - - async def leave(self): - """ - Use this method for your bot to leave a group, supergroup or channel. - - Source: https://core.telegram.org/bots/api#leavechat - - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.leave_chat(self.id) - - async def get_administrators(self): - """ - Use this method to get a list of administrators in a chat. - - Source: https://core.telegram.org/bots/api#getchatadministrators - - :return: On success, returns an Array of ChatMember objects that contains information about all - chat administrators except other bots. - If the chat is a group or a supergroup and no administrators were appointed, - only the creator will be returned. - :rtype: :obj:`typing.List[types.ChatMember]` - """ - return await self.bot.get_chat_administrators(self.id) - - async def get_members_count(self): - """ - Use this method to get the number of members in a chat. - - Source: https://core.telegram.org/bots/api#getchatmemberscount - - :return: Returns Int on success. - :rtype: :obj:`base.Integer` - """ - return await self.bot.get_chat_members_count(self.id) - - async def get_member(self, user_id): - """ - Use this method to get information about a member of a chat. - - Source: https://core.telegram.org/bots/api#getchatmember - - :param user_id: Unique identifier of the target user - :type user_id: :obj:`base.Integer` - :return: Returns a ChatMember object on success. - :rtype: :obj:`types.ChatMember` - """ - return await self.bot.get_chat_member(self.id, user_id) - - async def do(self, action): - """ - Use this method when you need to tell the user that something is happening on the bot's side. - The status is set for 5 seconds or less - (when a message arrives from your bot, Telegram clients clear its typing status). - - We only recommend using this method when a response from the bot will take - a noticeable amount of time to arrive. - - Source: https://core.telegram.org/bots/api#sendchataction - - :param action: Type of action to broadcast. - :type action: :obj:`base.String` - :return: Returns True on success. - :rtype: :obj:`base.Boolean` - """ - return await self.bot.send_chat_action(self.id, action) - - async def export_invite_link(self): - """ - Use this method to export an invite link to a supergroup or a channel. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Source: https://core.telegram.org/bots/api#exportchatinvitelink - - :return: Returns exported invite link as String on success. - :rtype: :obj:`base.String` - """ - if not self.invite_link: - self.invite_link = await self.bot.export_chat_invite_link(self.id) - - return self.invite_link - - def __int__(self): - return self.id - - -class ChatType(helper.Helper): - """ - List of chat types - - :key: PRIVATE - :key: GROUP - :key: SUPER_GROUP - :key: CHANNEL - """ - - mode = helper.HelperMode.lowercase - - PRIVATE = helper.Item() # private - GROUP = helper.Item() # group - SUPER_GROUP = helper.Item() # supergroup - CHANNEL = helper.Item() # channel - - @staticmethod - def _check(obj, chat_types) -> bool: - if hasattr(obj, 'chat'): - obj = obj.chat - if not hasattr(obj, 'type'): - return False - return obj.type in chat_types - - @classmethod - def is_private(cls, obj) -> bool: - """ - Check chat is private - - :param obj: - :return: - """ - return cls._check(obj, [cls.PRIVATE]) - - @classmethod - def is_group(cls, obj) -> bool: - """ - Check chat is group - - :param obj: - :return: - """ - return cls._check(obj, [cls.GROUP]) - - @classmethod - def is_super_group(cls, obj) -> bool: - """ - Check chat is super-group - - :param obj: - :return: - """ - return cls._check(obj, [cls.SUPER_GROUP]) - - @classmethod - def is_group_or_super_group(cls, obj) -> bool: - """ - Check chat is group or super-group - - :param obj: - :return: - """ - return cls._check(obj, [cls.GROUP, cls.SUPER_GROUP]) - - @classmethod - def is_channel(cls, obj) -> bool: - """ - Check chat is channel - - :param obj: - :return: - """ - return cls._check(obj, [cls.CHANNEL]) - - -class ChatActions(helper.Helper): - """ - List of chat actions - - :key: TYPING - :key: UPLOAD_PHOTO - :key: RECORD_VIDEO - :key: UPLOAD_VIDEO - :key: RECORD_AUDIO - :key: UPLOAD_AUDIO - :key: UPLOAD_DOCUMENT - :key: FIND_LOCATION - :key: RECORD_VIDEO_NOTE - :key: UPLOAD_VIDEO_NOTE - """ - - mode = helper.HelperMode.snake_case - - TYPING: str = helper.Item() # typing - UPLOAD_PHOTO: str = helper.Item() # upload_photo - RECORD_VIDEO: str = helper.Item() # record_video - UPLOAD_VIDEO: str = helper.Item() # upload_video - RECORD_AUDIO: str = helper.Item() # record_audio - UPLOAD_AUDIO: str = helper.Item() # upload_audio - UPLOAD_DOCUMENT: str = helper.Item() # upload_document - FIND_LOCATION: str = helper.Item() # find_location - RECORD_VIDEO_NOTE: str = helper.Item() # record_video_note - UPLOAD_VIDEO_NOTE: str = helper.Item() # upload_video_note - - @classmethod - async def _do(cls, action: str, sleep=None): - from aiogram import Bot - await Bot.get_current().send_chat_action(Chat.get_current().id, action) - if sleep: - await asyncio.sleep(sleep) - - @classmethod - def calc_timeout(cls, text, timeout=.8): - """ - Calculate timeout for text - - :param text: - :param timeout: - :return: - """ - return min((len(str(text)) * timeout, 5.0)) - - @classmethod - async def typing(cls, sleep=None): - """ - Do typing - - :param sleep: sleep timeout - :return: - """ - if isinstance(sleep, str): - sleep = cls.calc_timeout(sleep) - await cls._do(cls.TYPING, sleep) - - @classmethod - async def upload_photo(cls, sleep=None): - """ - Do upload_photo - - :param sleep: sleep timeout - :return: - """ - await cls._do(cls.UPLOAD_PHOTO, sleep) - - @classmethod - async def record_video(cls, sleep=None): - """ - Do record video - - :param sleep: sleep timeout - :return: - """ - await cls._do(cls.RECORD_VIDEO, sleep) - - @classmethod - async def upload_video(cls, sleep=None): - """ - Do upload video - - :param sleep: sleep timeout - :return: - """ - await cls._do(cls.UPLOAD_VIDEO, sleep) - - @classmethod - async def record_audio(cls, sleep=None): - """ - Do record audio - - :param sleep: sleep timeout - :return: - """ - await cls._do(cls.RECORD_AUDIO, sleep) - - @classmethod - async def upload_audio(cls, sleep=None): - """ - Do upload audio - - :param sleep: sleep timeout - :return: - """ - await cls._do(cls.UPLOAD_AUDIO, sleep) - - @classmethod - async def upload_document(cls, sleep=None): - """ - Do upload document - - :param sleep: sleep timeout - :return: - """ - await cls._do(cls.UPLOAD_DOCUMENT, sleep) - - @classmethod - async def find_location(cls, sleep=None): - """ - Do find location - - :param sleep: sleep timeout - :return: - """ - await cls._do(cls.FIND_LOCATION, sleep) - - @classmethod - async def record_video_note(cls, sleep=None): - """ - Do record video note - - :param sleep: sleep timeout - :return: - """ - await cls._do(cls.RECORD_VIDEO_NOTE, sleep) - - @classmethod - async def upload_video_note(cls, sleep=None): - """ - Do upload video note - - :param sleep: sleep timeout - :return: - """ - await cls._do(cls.UPLOAD_VIDEO_NOTE, sleep) diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py deleted file mode 100644 index 7e05a33f..00000000 --- a/aiogram/types/chat_member.py +++ /dev/null @@ -1,65 +0,0 @@ -import datetime -import warnings -from typing import Optional - -from . import base -from . import fields -from .user import User -from ..utils import helper - - -class ChatMember(base.TelegramObject): - """ - This object contains information about one member of a chat. - - https://core.telegram.org/bots/api#chatmember - """ - user: User = fields.Field(base=User) - status: base.String = fields.Field() - until_date: datetime.datetime = fields.DateTimeField() - can_be_edited: base.Boolean = fields.Field() - can_change_info: base.Boolean = fields.Field() - can_post_messages: base.Boolean = fields.Field() - can_edit_messages: base.Boolean = fields.Field() - can_delete_messages: base.Boolean = fields.Field() - can_invite_users: base.Boolean = fields.Field() - can_restrict_members: base.Boolean = fields.Field() - can_pin_messages: base.Boolean = fields.Field() - can_promote_members: base.Boolean = fields.Field() - is_member: base.Boolean = fields.Field() - can_send_messages: base.Boolean = fields.Field() - can_send_media_messages: base.Boolean = fields.Field() - can_send_polls: base.Boolean = fields.Field() - can_send_other_messages: base.Boolean = fields.Field() - can_add_web_page_previews: base.Boolean = fields.Field() - - def is_chat_admin(self) -> bool: - return ChatMemberStatus.is_chat_admin(self.status) - - def is_chat_member(self) -> bool: - return ChatMemberStatus.is_chat_member(self.status) - - def __int__(self) -> int: - return self.user.id - - -class ChatMemberStatus(helper.Helper): - """ - Chat member status - """ - mode = helper.HelperMode.lowercase - - CREATOR = helper.Item() # creator - ADMINISTRATOR = helper.Item() # administrator - MEMBER = helper.Item() # member - RESTRICTED = helper.Item() # restricted - LEFT = helper.Item() # left - KICKED = helper.Item() # kicked - - @classmethod - def is_chat_admin(cls, role: str) -> bool: - return role in [cls.ADMINISTRATOR, cls.CREATOR] - - @classmethod - def is_chat_member(cls, role: str) -> bool: - return role in [cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR, cls.RESTRICTED] diff --git a/aiogram/types/chat_permissions.py b/aiogram/types/chat_permissions.py deleted file mode 100644 index 9d44653e..00000000 --- a/aiogram/types/chat_permissions.py +++ /dev/null @@ -1,39 +0,0 @@ -from . import base -from . import fields - - -class ChatPermissions(base.TelegramObject): - """ - Describes actions that a non-administrator user is allowed to take in a chat. - - https://core.telegram.org/bots/api#chatpermissions - """ - can_send_messages: base.Boolean = fields.Field() - can_send_media_messages: base.Boolean = fields.Field() - can_send_polls: base.Boolean = fields.Field() - can_send_other_messages: base.Boolean = fields.Field() - can_add_web_page_previews: base.Boolean = fields.Field() - can_change_info: base.Boolean = fields.Field() - can_invite_users: base.Boolean = fields.Field() - can_pin_messages: base.Boolean = fields.Field() - - def __init__(self, - can_send_messages: base.Boolean = None, - can_send_media_messages: base.Boolean = None, - can_send_polls: base.Boolean = None, - can_send_other_messages: base.Boolean = None, - can_add_web_page_previews: base.Boolean = None, - can_change_info: base.Boolean = None, - can_invite_users: base.Boolean = None, - can_pin_messages: base.Boolean = None, - **kwargs): - super(ChatPermissions, self).__init__( - can_send_messages=can_send_messages, - can_send_media_messages=can_send_media_messages, - can_send_polls=can_send_polls, - can_send_other_messages=can_send_other_messages, - can_add_web_page_previews=can_add_web_page_previews, - can_change_info=can_change_info, - can_invite_users=can_invite_users, - can_pin_messages=can_pin_messages, - ) diff --git a/aiogram/types/chat_photo.py b/aiogram/types/chat_photo.py deleted file mode 100644 index 00c1cedf..00000000 --- a/aiogram/types/chat_photo.py +++ /dev/null @@ -1,93 +0,0 @@ -import os -import pathlib - -from . import base -from . import fields - - -class ChatPhoto(base.TelegramObject): - """ - This object represents a chat photo. - - https://core.telegram.org/bots/api#chatphoto - """ - - small_file_id: base.String = fields.Field() - big_file_id: base.String = fields.Field() - - async def download_small( - self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True - ): - """ - Download file - - :param destination: filename or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO` - :param timeout: Integer - :param chunk_size: Integer - :param seek: Boolean - go to start of file when downloading is finished. - :param make_dirs: Make dirs if not exist - :return: destination - """ - file = await self.get_small_file() - - is_path = True - if destination is None: - destination = file.file_path - elif isinstance(destination, (str, pathlib.Path)) and os.path.isdir(destination): - os.path.join(destination, file.file_path) - else: - is_path = False - - if is_path and make_dirs: - os.makedirs(os.path.dirname(destination), exist_ok=True) - - return await self.bot.download_file( - file_path=file.file_path, - destination=destination, - timeout=timeout, - chunk_size=chunk_size, - seek=seek, - ) - - async def download_big( - self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True - ): - """ - Download file - - :param destination: filename or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO` - :param timeout: Integer - :param chunk_size: Integer - :param seek: Boolean - go to start of file when downloading is finished. - :param make_dirs: Make dirs if not exist - :return: destination - """ - file = await self.get_big_file() - - is_path = True - if destination is None: - destination = file.file_path - elif isinstance(destination, (str, pathlib.Path)) and os.path.isdir(destination): - os.path.join(destination, file.file_path) - else: - is_path = False - - if is_path and make_dirs: - os.makedirs(os.path.dirname(destination), exist_ok=True) - - return await self.bot.download_file( - file_path=file.file_path, - destination=destination, - timeout=timeout, - chunk_size=chunk_size, - seek=seek, - ) - - async def get_small_file(self): - return await self.bot.get_file(self.small_file_id) - - async def get_big_file(self): - return await self.bot.get_file(self.big_file_id) - - def __hash__(self): - return hash(self.small_file_id) + hash(self.big_file_id) diff --git a/aiogram/types/chosen_inline_result.py b/aiogram/types/chosen_inline_result.py deleted file mode 100644 index 24c59cb5..00000000 --- a/aiogram/types/chosen_inline_result.py +++ /dev/null @@ -1,23 +0,0 @@ -from . import base -from . import fields -from .location import Location -from .user import User - - -class ChosenInlineResult(base.TelegramObject): - """ - Represents a result of an inline query that was chosen by the user and sent to their chat partner. - - Note: It is necessary to enable inline feedback via @Botfather in order to receive these objects in updates. - Your bot can accept payments from Telegram users. - Please see the introduction to payments for more details on the process and how to set up payments for your bot. - Please note that users will need Telegram v.4.0 or higher to use payments (released on May 18, 2017). - - https://core.telegram.org/bots/api#choseninlineresult - """ - - result_id: base.String = fields.Field() - from_user: User = fields.Field(alias="from", base=User) - location: Location = fields.Field(base=Location) - inline_message_id: base.String = fields.Field() - query: base.String = fields.Field() diff --git a/aiogram/types/contact.py b/aiogram/types/contact.py deleted file mode 100644 index a3cfc15b..00000000 --- a/aiogram/types/contact.py +++ /dev/null @@ -1,26 +0,0 @@ -from . import base -from . import fields - - -class Contact(base.TelegramObject): - """ - This object represents a phone contact. - - https://core.telegram.org/bots/api#contact - """ - - phone_number: base.String = fields.Field() - first_name: base.String = fields.Field() - last_name: base.String = fields.Field() - user_id: base.Integer = fields.Field() - vcard: base.String = fields.Field() - - @property - def full_name(self): - name = self.first_name - if self.last_name is not None: - name += " " + self.last_name - return name - - def __hash__(self): - return hash(self.phone_number) diff --git a/aiogram/types/document.py b/aiogram/types/document.py deleted file mode 100644 index f640bc27..00000000 --- a/aiogram/types/document.py +++ /dev/null @@ -1,18 +0,0 @@ -from . import base -from . import fields -from . import mixins -from .photo_size import PhotoSize - - -class Document(base.TelegramObject, mixins.Downloadable): - """ - This object represents a general file (as opposed to photos, voice messages and audio files). - - https://core.telegram.org/bots/api#document - """ - - file_id: base.String = fields.Field() - thumb: PhotoSize = fields.Field(base=PhotoSize) - file_name: base.String = fields.Field() - mime_type: base.String = fields.Field() - file_size: base.Integer = fields.Field() diff --git a/aiogram/types/encrypted_credentials.py b/aiogram/types/encrypted_credentials.py deleted file mode 100644 index d649c8d9..00000000 --- a/aiogram/types/encrypted_credentials.py +++ /dev/null @@ -1,16 +0,0 @@ -from . import base -from . import fields - - -class EncryptedCredentials(base.TelegramObject): - """ - Contains data required for decrypting and authenticating EncryptedPassportElement. - See the Telegram Passport Documentation for a complete description of the data decryption - and authentication processes. - - https://core.telegram.org/bots/api#encryptedcredentials - """ - - data: base.String = fields.Field() - hash: base.String = fields.Field() - secret: base.String = fields.Field() diff --git a/aiogram/types/encrypted_passport_element.py b/aiogram/types/encrypted_passport_element.py deleted file mode 100644 index 76e02ec1..00000000 --- a/aiogram/types/encrypted_passport_element.py +++ /dev/null @@ -1,22 +0,0 @@ -import typing - -from . import base -from . import fields -from .passport_file import PassportFile - - -class EncryptedPassportElement(base.TelegramObject): - """ - Contains information about documents or other Telegram Passport elements shared with the bot by the user. - - https://core.telegram.org/bots/api#encryptedpassportelement - """ - - type: base.String = fields.Field() - data: base.String = fields.Field() - phone_number: base.String = fields.Field() - email: base.String = fields.Field() - files: typing.List[PassportFile] = fields.ListField(base=PassportFile) - front_side: PassportFile = fields.Field(base=PassportFile) - reverse_side: PassportFile = fields.Field(base=PassportFile) - selfie: PassportFile = fields.Field(base=PassportFile) diff --git a/aiogram/types/fields.py b/aiogram/types/fields.py deleted file mode 100644 index 37b562f5..00000000 --- a/aiogram/types/fields.py +++ /dev/null @@ -1,201 +0,0 @@ -import abc -import datetime - -__all__ = ("BaseField", "Field", "ListField", "DateTimeField", "TextField", "ListOfLists") - - -class BaseField(metaclass=abc.ABCMeta): - """ - Base field (prop) - """ - - def __init__(self, *, base=None, default=None, alias=None, on_change=None): - """ - Init prop - - :param base: class for child element - :param default: default value - :param alias: alias name (for e.g. field 'from' has to be named 'from_user' - as 'from' is a builtin Python keyword - :param on_change: callback will be called when value is changed - """ - self.base_object = base - self.default = default - self.alias = alias - self.on_change = on_change - - def __set_name__(self, owner, name): - if self.alias is None: - self.alias = name - - def resolve_base(self, instance): - if self.base_object is None or hasattr(self.base_object, "telegram_types"): - return - elif isinstance(self.base_object, str): - self.base_object = instance.telegram_types.get(self.base_object) - - def get_value(self, instance): - """ - Get value for the current object instance - - :param instance: - :return: - """ - return instance.values.get(self.alias, self.default) - - def set_value(self, instance, value, parent=None): - """ - Set prop value - - :param instance: - :param value: - :param parent: - :return: - """ - self.resolve_base(instance) - value = self.deserialize(value, parent) - instance.values[self.alias] = value - self._trigger_changed(instance, value) - - def _trigger_changed(self, instance, value): - if not self.on_change and instance is not None: - return - callback = getattr(instance, self.on_change) - callback(value) - - def __get__(self, instance, owner): - return self.get_value(instance) - - def __set__(self, instance, value): - self.set_value(instance, value) - - @abc.abstractmethod - def serialize(self, value): - """ - Serialize value to python - - :param value: - :return: - """ - pass - - @abc.abstractmethod - def deserialize(self, value, parent=None): - """Deserialize python object value to TelegramObject value""" - pass - - def export(self, instance): - """ - Alias for `serialize` but for current Object instance - - :param instance: - :return: - """ - return self.serialize(self.get_value(instance)) - - -class Field(BaseField): - """ - Simple field - """ - - def serialize(self, value): - if self.base_object is not None and hasattr(value, "to_python"): - return value.to_python() - return value - - def deserialize(self, value, parent=None): - if ( - isinstance(value, dict) - and self.base_object is not None - and not hasattr(value, "base_object") - and not hasattr(value, "to_python") - ): - return self.base_object(conf={"parent": parent}, **value) - return value - - -class ListField(Field): - """ - Field contains list ob objects - """ - - def __init__(self, *args, **kwargs): - default = kwargs.pop("default", None) - if default is None: - default = [] - - super(ListField, self).__init__(*args, default=default, **kwargs) - - def serialize(self, value): - result = [] - serialize = super(ListField, self).serialize - for item in value: - result.append(serialize(item)) - return result - - def deserialize(self, value, parent=None): - result = [] - deserialize = super(ListField, self).deserialize - for item in value: - result.append(deserialize(item, parent=parent)) - return result - - -class ListOfLists(Field): - def serialize(self, value): - result = [] - serialize = super(ListOfLists, self).serialize - for row in value: - row_result = [] - for item in row: - row_result.append(serialize(item)) - result.append(row_result) - return result - - def deserialize(self, value, parent=None): - result = [] - deserialize = super(ListOfLists, self).deserialize - if hasattr(value, "__iter__"): - for row in value: - row_result = [] - for item in row: - row_result.append(deserialize(item, parent=parent)) - result.append(row_result) - return result - - -class DateTimeField(Field): - """ - In this field st_ored datetime - - in: unixtime - out: datetime - """ - - def serialize(self, value: datetime.datetime): - return round(value.timestamp()) - - def deserialize(self, value, parent=None): - return datetime.datetime.fromtimestamp(value) - - -class TextField(Field): - def __init__(self, *, prefix=None, suffix=None, default=None, alias=None): - super(TextField, self).__init__(default=default, alias=alias) - self.prefix = prefix - self.suffix = suffix - - def serialize(self, value): - if value is None: - return value - if self.prefix: - value = self.prefix + value - if self.suffix: - value += self.suffix - return value - - def deserialize(self, value, parent=None): - if value is not None and not isinstance(value, str): - raise TypeError(f"Field '{self.alias}' should be str not {type(value).__name__}") - return value diff --git a/aiogram/types/file.py b/aiogram/types/file.py deleted file mode 100644 index 0bcd1f63..00000000 --- a/aiogram/types/file.py +++ /dev/null @@ -1,22 +0,0 @@ -from . import base -from . import fields -from . import mixins - - -class File(base.TelegramObject, mixins.Downloadable): - """ - This object represents a file ready to be downloaded. - - The file can be downloaded via the link https://api.telegram.org/file/bot/. - - 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 getFile. - - Maximum file size to download is 20 MB - - https://core.telegram.org/bots/api#file - """ - - file_id: base.String = fields.Field() - file_size: base.Integer = fields.Field() - file_path: base.String = fields.Field() diff --git a/aiogram/types/force_reply.py b/aiogram/types/force_reply.py deleted file mode 100644 index cafb6432..00000000 --- a/aiogram/types/force_reply.py +++ /dev/null @@ -1,37 +0,0 @@ -import typing - -from . import base -from . import fields - - -class ForceReply(base.TelegramObject): - """ - Upon receiving a message with this object, - Telegram clients will display a reply interface to the user - (act as if the user has selected the bot‘s message and tapped ’Reply'). - This can be extremely useful if you want to create user-friendly step-by-step - interfaces without having to sacrifice privacy mode. - - Example: A poll bot for groups runs in privacy mode - (only receives commands, replies to its messages and mentions). - There could be two ways to create a new poll - - The last option is definitely more attractive. - And if you use ForceReply in your bot‘s questions, it will receive the user’s answers even - if it only receives replies, commands and mentions — without any extra work for the user. - - https://core.telegram.org/bots/api#forcereply - """ - - force_reply: base.Boolean = fields.Field(default=True) - selective: base.Boolean = fields.Field() - - @classmethod - def create(cls, selective: typing.Optional[base.Boolean] = None): - """ - Create new force reply - - :param selective: - :return: - """ - return cls(selective=selective) diff --git a/aiogram/types/game.py b/aiogram/types/game.py deleted file mode 100644 index b6845464..00000000 --- a/aiogram/types/game.py +++ /dev/null @@ -1,24 +0,0 @@ -import typing - -from . import base -from . import fields -from .animation import Animation -from .message_entity import MessageEntity -from .photo_size import PhotoSize - - -class Game(base.TelegramObject): - """ - This object represents a game. - - Use BotFather to create and edit games, their short names will act as unique identifiers. - - https://core.telegram.org/bots/api#game - """ - - title: base.String = fields.Field() - description: base.String = fields.Field() - photo: typing.List[PhotoSize] = fields.ListField(base=PhotoSize) - text: base.String = fields.Field() - text_entities: typing.List[MessageEntity] = fields.ListField(base=MessageEntity) - animation: Animation = fields.Field(base=Animation) diff --git a/aiogram/types/game_high_score.py b/aiogram/types/game_high_score.py deleted file mode 100644 index 5244c9ea..00000000 --- a/aiogram/types/game_high_score.py +++ /dev/null @@ -1,17 +0,0 @@ -from . import base -from . import fields -from .user import User - - -class GameHighScore(base.TelegramObject): - """ - This object represents one row of the high scores table for a game. - And that‘s about all we’ve got for now. - If you've got any questions, please check out our Bot FAQ - - https://core.telegram.org/bots/api#gamehighscore - """ - - position: base.Integer = fields.Field() - user: User = fields.Field(base=User) - score: base.Integer = fields.Field() diff --git a/aiogram/types/inline_keyboard.py b/aiogram/types/inline_keyboard.py deleted file mode 100644 index 5f1652fe..00000000 --- a/aiogram/types/inline_keyboard.py +++ /dev/null @@ -1,127 +0,0 @@ -import typing - -from . import base -from . import fields -from .callback_game import CallbackGame -from .login_url import LoginUrl - - -class InlineKeyboardMarkup(base.TelegramObject): - """ - This object represents an inline keyboard that appears right next to the message it belongs to. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will display unsupported message. - - https://core.telegram.org/bots/api#inlinekeyboardmarkup - """ - - inline_keyboard: "typing.List[typing.List[InlineKeyboardButton]]" = fields.ListOfLists( - base="InlineKeyboardButton" - ) - - def __init__(self, row_width=3, inline_keyboard=None, **kwargs): - if inline_keyboard is None: - inline_keyboard = [] - - conf = kwargs.pop("conf", {}) or {} - conf["row_width"] = row_width - - super(InlineKeyboardMarkup, self).__init__( - **kwargs, conf=conf, inline_keyboard=inline_keyboard - ) - - @property - def row_width(self): - return self.conf.get("row_width", 3) - - @row_width.setter - def row_width(self, value): - self.conf["row_width"] = value - - def add(self, *args): - """ - Add buttons - - :param args: - :return: self - :rtype: :obj:`types.InlineKeyboardMarkup` - """ - row = [] - for index, button in enumerate(args, start=1): - row.append(button) - if index % self.row_width == 0: - self.inline_keyboard.append(row) - row = [] - if len(row) > 0: - self.inline_keyboard.append(row) - return self - - def row(self, *args): - """ - Add row - - :param args: - :return: self - :rtype: :obj:`types.InlineKeyboardMarkup` - """ - btn_array = [] - for button in args: - btn_array.append(button) - self.inline_keyboard.append(btn_array) - return self - - def insert(self, button): - """ - Insert button to last row - - :param button: - :return: self - :rtype: :obj:`types.InlineKeyboardMarkup` - """ - if self.inline_keyboard and len(self.inline_keyboard[-1]) < self.row_width: - self.inline_keyboard[-1].append(button) - else: - self.add(button) - return self - - -class InlineKeyboardButton(base.TelegramObject): - """ - This object represents one button of an inline keyboard. You must use exactly one of the optional fields. - - https://core.telegram.org/bots/api#inlinekeyboardbutton - """ - - text: base.String = fields.Field() - url: base.String = fields.Field() - login_url: LoginUrl = fields.Field(base=LoginUrl) - callback_data: base.String = fields.Field() - switch_inline_query: base.String = fields.Field() - switch_inline_query_current_chat: base.String = fields.Field() - callback_game: CallbackGame = fields.Field(base=CallbackGame) - pay: base.Boolean = fields.Field() - - def __init__( - self, - text: base.String, - url: base.String = None, - login_url: LoginUrl = None, - callback_data: base.String = None, - switch_inline_query: base.String = None, - switch_inline_query_current_chat: base.String = None, - callback_game: CallbackGame = None, - pay: base.Boolean = None, - **kwargs, - ): - super(InlineKeyboardButton, self).__init__( - text=text, - url=url, - login_url=login_url, - callback_data=callback_data, - switch_inline_query=switch_inline_query, - switch_inline_query_current_chat=switch_inline_query_current_chat, - callback_game=callback_game, - pay=pay, - **kwargs, - ) diff --git a/aiogram/types/inline_query.py b/aiogram/types/inline_query.py deleted file mode 100644 index 611da173..00000000 --- a/aiogram/types/inline_query.py +++ /dev/null @@ -1,71 +0,0 @@ -import typing - -from . import base -from . import fields -from .inline_query_result import InlineQueryResult -from .location import Location -from .user import User - - -class InlineQuery(base.TelegramObject): - """ - This object represents an incoming inline query. - - When the user sends an empty query, your bot could return some default or trending results. - - https://core.telegram.org/bots/api#inlinequery - """ - - id: base.String = fields.Field() - from_user: User = fields.Field(alias="from", base=User) - location: Location = fields.Field(base=Location) - query: base.String = fields.Field() - offset: base.String = fields.Field() - - async def answer( - self, - results: typing.List[InlineQueryResult], - cache_time: typing.Union[base.Integer, None] = None, - is_personal: typing.Union[base.Boolean, None] = None, - next_offset: typing.Union[base.String, None] = None, - switch_pm_text: typing.Union[base.String, None] = None, - switch_pm_parameter: typing.Union[base.String, None] = None, - ): - """ - Use this method to send answers to an inline query. - No more than 50 results per query are allowed. - - Source: https://core.telegram.org/bots/api#answerinlinequery - - :param results: A JSON-serialized array of results for the inline query - :type results: :obj:`typing.List[types.InlineQueryResult]` - :param cache_time: The maximum amount of time in seconds that the result of the - inline query may be cached on the server. Defaults to 300. - :type cache_time: :obj:`typing.Union[base.Integer, None]` - :param is_personal: Pass True, if results may be cached on the server side only - for the user that sent the query. By default, results may be returned to any user who sends the same query - :type is_personal: :obj:`typing.Union[base.Boolean, None]` - :param next_offset: Pass the offset that a client should send in the - next query with the same text to receive more results. - Pass an empty string if there are no more results or if you don‘t support pagination. - Offset length can’t exceed 64 bytes. - :type next_offset: :obj:`typing.Union[base.String, None]` - :param switch_pm_text: If passed, clients will display a button with specified text that - switches the user to a private chat with the bot and sends the bot a start message - with the parameter switch_pm_parameter - :type switch_pm_text: :obj:`typing.Union[base.String, None]` - :param switch_pm_parameter: Deep-linking parameter for the /start message sent to the bot when - user presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed. - :type switch_pm_parameter: :obj:`typing.Union[base.String, None]` - :return: On success, True is returned - :rtype: :obj:`base.Boolean` - """ - return await self.bot.answer_inline_query( - self.id, - results=results, - cache_time=cache_time, - is_personal=is_personal, - next_offset=next_offset, - switch_pm_text=switch_pm_text, - switch_pm_parameter=switch_pm_parameter, - ) diff --git a/aiogram/types/inline_query_result.py b/aiogram/types/inline_query_result.py deleted file mode 100644 index 19746bd4..00000000 --- a/aiogram/types/inline_query_result.py +++ /dev/null @@ -1,939 +0,0 @@ -import typing - -from . import base -from . import fields -from .inline_keyboard import InlineKeyboardMarkup -from .input_message_content import InputMessageContent - - -class InlineQueryResult(base.TelegramObject): - """ - This object represents one result of an inline query. - - Telegram clients currently support results of the following 20 types - - https://core.telegram.org/bots/api#inlinequeryresult - """ - - id: base.String = fields.Field() - reply_markup: InlineKeyboardMarkup = fields.Field(base=InlineKeyboardMarkup) - - def safe_get_parse_mode(self): - try: - return self.bot.parse_mode - except RuntimeError: - pass - - def __init__(self, **kwargs): - if "parse_mode" in kwargs and kwargs["parse_mode"] is None: - kwargs["parse_mode"] = self.safe_get_parse_mode() - super(InlineQueryResult, self).__init__(**kwargs) - - -class InlineQueryResultArticle(InlineQueryResult): - """ - Represents a link to an article or web page. - - https://core.telegram.org/bots/api#inlinequeryresultarticle - """ - - type: base.String = fields.Field(alias="type", default="article") - title: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - url: base.String = fields.Field() - hide_url: base.Boolean = fields.Field() - description: base.String = fields.Field() - thumb_url: base.String = fields.Field() - thumb_width: base.Integer = fields.Field() - thumb_height: base.Integer = fields.Field() - - def __init__( - self, - *, - id: base.String, - title: base.String, - input_message_content: InputMessageContent, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - url: typing.Optional[base.String] = None, - hide_url: typing.Optional[base.Boolean] = None, - description: typing.Optional[base.String] = None, - thumb_url: typing.Optional[base.String] = None, - thumb_width: typing.Optional[base.Integer] = None, - thumb_height: typing.Optional[base.Integer] = None, - ): - super(InlineQueryResultArticle, self).__init__( - id=id, - title=title, - input_message_content=input_message_content, - reply_markup=reply_markup, - url=url, - hide_url=hide_url, - description=description, - thumb_url=thumb_url, - thumb_width=thumb_width, - thumb_height=thumb_height, - ) - - -class InlineQueryResultPhoto(InlineQueryResult): - """ - Represents a link to a photo. - - By default, this photo will be sent by the user with optional caption. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the photo. - - https://core.telegram.org/bots/api#inlinequeryresultphoto - """ - - type: base.String = fields.Field(alias="type", default="photo") - photo_url: base.String = fields.Field() - thumb_url: base.String = fields.Field() - photo_width: base.Integer = fields.Field() - photo_height: base.Integer = fields.Field() - title: base.String = fields.Field() - description: base.String = fields.Field() - caption: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - photo_url: base.String, - thumb_url: base.String, - photo_width: typing.Optional[base.Integer] = None, - photo_height: typing.Optional[base.Integer] = None, - title: typing.Optional[base.String] = None, - description: typing.Optional[base.String] = None, - caption: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultPhoto, self).__init__( - id=id, - photo_url=photo_url, - thumb_url=thumb_url, - photo_width=photo_width, - photo_height=photo_height, - title=title, - description=description, - caption=caption, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultGif(InlineQueryResult): - """ - Represents a link to an animated GIF file. - - By default, this animated GIF file will be sent by the user with optional caption. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the animation. - - https://core.telegram.org/bots/api#inlinequeryresultgif - """ - - type: base.String = fields.Field(alias="type", default="gif") - gif_url: base.String = fields.Field() - gif_width: base.Integer = fields.Field() - gif_height: base.Integer = fields.Field() - gif_duration: base.Integer = fields.Field() - thumb_url: base.String = fields.Field() - title: base.String = fields.Field() - caption: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - gif_url: base.String, - gif_width: typing.Optional[base.Integer] = None, - gif_height: typing.Optional[base.Integer] = None, - gif_duration: typing.Optional[base.Integer] = None, - thumb_url: typing.Optional[base.String] = None, - title: typing.Optional[base.String] = None, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultGif, self).__init__( - id=id, - gif_url=gif_url, - gif_width=gif_width, - gif_height=gif_height, - gif_duration=gif_duration, - thumb_url=thumb_url, - title=title, - caption=caption, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultMpeg4Gif(InlineQueryResult): - """ - Represents a link to a video animation (H.264/MPEG-4 AVC video without sound). - - By default, this animated MPEG-4 file will be sent by the user with optional caption. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the animation. - - https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif - """ - - type: base.String = fields.Field(alias="type", default="mpeg4_gif") - mpeg4_url: base.String = fields.Field() - mpeg4_width: base.Integer = fields.Field() - mpeg4_height: base.Integer = fields.Field() - mpeg4_duration: base.Integer = fields.Field() - thumb_url: base.String = fields.Field() - title: base.String = fields.Field() - caption: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - mpeg4_url: base.String, - thumb_url: base.String, - mpeg4_width: typing.Optional[base.Integer] = None, - mpeg4_height: typing.Optional[base.Integer] = None, - mpeg4_duration: typing.Optional[base.Integer] = None, - title: typing.Optional[base.String] = None, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultMpeg4Gif, self).__init__( - id=id, - mpeg4_url=mpeg4_url, - mpeg4_width=mpeg4_width, - mpeg4_height=mpeg4_height, - mpeg4_duration=mpeg4_duration, - thumb_url=thumb_url, - title=title, - caption=caption, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultVideo(InlineQueryResult): - """ - Represents a link to a page containing an embedded video player or a video file. - - By default, this video file will be sent by the user with an optional caption. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the video. - - If an InlineQueryResultVideo message contains an embedded video (e.g., YouTube), - you must replace its content using input_message_content. - - https://core.telegram.org/bots/api#inlinequeryresultvideo - """ - - type: base.String = fields.Field(alias="type", default="video") - video_url: base.String = fields.Field() - mime_type: base.String = fields.Field() - thumb_url: base.String = fields.Field() - title: base.String = fields.Field() - caption: base.String = fields.Field() - video_width: base.Integer = fields.Field() - video_height: base.Integer = fields.Field() - video_duration: base.Integer = fields.Field() - description: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - video_url: base.String, - mime_type: base.String, - thumb_url: base.String, - title: base.String, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - video_width: typing.Optional[base.Integer] = None, - video_height: typing.Optional[base.Integer] = None, - video_duration: typing.Optional[base.Integer] = None, - description: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultVideo, self).__init__( - id=id, - video_url=video_url, - mime_type=mime_type, - thumb_url=thumb_url, - title=title, - caption=caption, - video_width=video_width, - video_height=video_height, - video_duration=video_duration, - description=description, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultAudio(InlineQueryResult): - """ - Represents a link to an mp3 audio file. By default, this audio file will be sent by the user. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the audio. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will ignore them. - - https://core.telegram.org/bots/api#inlinequeryresultaudio - """ - - type: base.String = fields.Field(alias="type", default="audio") - audio_url: base.String = fields.Field() - title: base.String = fields.Field() - caption: base.String = fields.Field() - performer: base.String = fields.Field() - audio_duration: base.Integer = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - audio_url: base.String, - title: base.String, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - performer: typing.Optional[base.String] = None, - audio_duration: typing.Optional[base.Integer] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultAudio, self).__init__( - id=id, - audio_url=audio_url, - title=title, - caption=caption, - parse_mode=parse_mode, - performer=performer, - audio_duration=audio_duration, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultVoice(InlineQueryResult): - """ - Represents a link to a voice recording in an .ogg container encoded with OPUS. - - By default, this voice recording will be sent by the user. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the the voice message. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will ignore them. - - https://core.telegram.org/bots/api#inlinequeryresultvoice - """ - - type: base.String = fields.Field(alias="type", default="voice") - voice_url: base.String = fields.Field() - title: base.String = fields.Field() - caption: base.String = fields.Field() - voice_duration: base.Integer = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - voice_url: base.String, - title: base.String, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - voice_duration: typing.Optional[base.Integer] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultVoice, self).__init__( - id=id, - voice_url=voice_url, - title=title, - caption=caption, - voice_duration=voice_duration, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultDocument(InlineQueryResult): - """ - Represents a link to a file. - - By default, this file will be sent by the user with an optional caption. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the file. Currently, only .PDF and .ZIP files can be sent using this method. - - Note: This will only work in Telegram versions released after 9 April, 2016. Older clients will ignore them. - - https://core.telegram.org/bots/api#inlinequeryresultdocument - """ - - type: base.String = fields.Field(alias="type", default="document") - title: base.String = fields.Field() - caption: base.String = fields.Field() - document_url: base.String = fields.Field() - mime_type: base.String = fields.Field() - description: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - thumb_url: base.String = fields.Field() - thumb_width: base.Integer = fields.Field() - thumb_height: base.Integer = fields.Field() - - def __init__( - self, - *, - id: base.String, - title: base.String, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - document_url: typing.Optional[base.String] = None, - mime_type: typing.Optional[base.String] = None, - description: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - thumb_url: typing.Optional[base.String] = None, - thumb_width: typing.Optional[base.Integer] = None, - thumb_height: typing.Optional[base.Integer] = None, - ): - super(InlineQueryResultDocument, self).__init__( - id=id, - title=title, - caption=caption, - document_url=document_url, - mime_type=mime_type, - description=description, - reply_markup=reply_markup, - input_message_content=input_message_content, - thumb_url=thumb_url, - thumb_width=thumb_width, - thumb_height=thumb_height, - parse_mode=parse_mode, - ) - - -class InlineQueryResultLocation(InlineQueryResult): - """ - Represents a location on a map. - - By default, the location will be sent by the user. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the location. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will ignore them. - - https://core.telegram.org/bots/api#inlinequeryresultlocation - """ - - type: base.String = fields.Field(alias="type", default="location") - latitude: base.Float = fields.Field() - longitude: base.Float = fields.Field() - title: base.String = fields.Field() - live_period: base.Integer = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - thumb_url: base.String = fields.Field() - thumb_width: base.Integer = fields.Field() - thumb_height: base.Integer = fields.Field() - - def __init__( - self, - *, - id: base.String, - latitude: base.Float, - longitude: base.Float, - title: base.String, - live_period: typing.Optional[base.Integer] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - thumb_url: typing.Optional[base.String] = None, - thumb_width: typing.Optional[base.Integer] = None, - thumb_height: typing.Optional[base.Integer] = None, - ): - super(InlineQueryResultLocation, self).__init__( - id=id, - latitude=latitude, - longitude=longitude, - title=title, - live_period=live_period, - reply_markup=reply_markup, - input_message_content=input_message_content, - thumb_url=thumb_url, - thumb_width=thumb_width, - thumb_height=thumb_height, - ) - - -class InlineQueryResultVenue(InlineQueryResult): - """ - Represents a venue. By default, the venue will be sent by the user. - - Alternatively, you can use input_message_content to send a message with the specified content - instead of the venue. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will ignore them. - - https://core.telegram.org/bots/api#inlinequeryresultvenue - """ - - type: base.String = fields.Field(alias="type", default="venue") - latitude: base.Float = fields.Field() - longitude: base.Float = fields.Field() - title: base.String = fields.Field() - address: base.String = fields.Field() - foursquare_id: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - thumb_url: base.String = fields.Field() - thumb_width: base.Integer = fields.Field() - thumb_height: base.Integer = fields.Field() - foursquare_type: base.String = fields.Field() - - def __init__( - self, - *, - id: base.String, - latitude: base.Float, - longitude: base.Float, - title: base.String, - address: base.String, - foursquare_id: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - thumb_url: typing.Optional[base.String] = None, - thumb_width: typing.Optional[base.Integer] = None, - thumb_height: typing.Optional[base.Integer] = None, - foursquare_type: typing.Optional[base.String] = None, - ): - super(InlineQueryResultVenue, self).__init__( - id=id, - latitude=latitude, - longitude=longitude, - title=title, - address=address, - foursquare_id=foursquare_id, - reply_markup=reply_markup, - input_message_content=input_message_content, - thumb_url=thumb_url, - thumb_width=thumb_width, - thumb_height=thumb_height, - foursquare_type=foursquare_type, - ) - - -class InlineQueryResultContact(InlineQueryResult): - """ - Represents a contact with a phone number. - - By default, this contact will be sent by the user. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the contact. - - Note: This will only work in Telegram versions released after 9 April, 2016. Older clients will ignore them. - - https://core.telegram.org/bots/api#inlinequeryresultcontact - """ - - type: base.String = fields.Field(alias="type", default="contact") - phone_number: base.String = fields.Field() - first_name: base.String = fields.Field() - last_name: base.String = fields.Field() - vcard: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - thumb_url: base.String = fields.Field() - thumb_width: base.Integer = fields.Field() - thumb_height: base.Integer = fields.Field() - foursquare_type: base.String = fields.Field() - - def __init__( - self, - *, - id: base.String, - phone_number: base.String, - first_name: base.String, - last_name: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - thumb_url: typing.Optional[base.String] = None, - thumb_width: typing.Optional[base.Integer] = None, - thumb_height: typing.Optional[base.Integer] = None, - foursquare_type: typing.Optional[base.String] = None, - ): - super(InlineQueryResultContact, self).__init__( - id=id, - phone_number=phone_number, - first_name=first_name, - last_name=last_name, - reply_markup=reply_markup, - input_message_content=input_message_content, - thumb_url=thumb_url, - thumb_width=thumb_width, - thumb_height=thumb_height, - foursquare_type=foursquare_type, - ) - - -class InlineQueryResultGame(InlineQueryResult): - """ - Represents a Game. - - Note: This will only work in Telegram versions released after October 1, 2016. - Older clients will not display any inline results if a game result is among them. - - https://core.telegram.org/bots/api#inlinequeryresultgame - """ - - type: base.String = fields.Field(alias="type", default="game") - game_short_name: base.String = fields.Field() - - def __init__( - self, - *, - id: base.String, - game_short_name: base.String, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - ): - super(InlineQueryResultGame, self).__init__( - id=id, game_short_name=game_short_name, reply_markup=reply_markup - ) - - -class InlineQueryResultCachedPhoto(InlineQueryResult): - """ - Represents a link to a photo stored on the Telegram servers. - - By default, this photo will be sent by the user with an optional caption. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the photo. - - https://core.telegram.org/bots/api#inlinequeryresultcachedphoto - """ - - type: base.String = fields.Field(alias="type", default="photo") - photo_file_id: base.String = fields.Field() - title: base.String = fields.Field() - description: base.String = fields.Field() - caption: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - photo_file_id: base.String, - title: typing.Optional[base.String] = None, - description: typing.Optional[base.String] = None, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultCachedPhoto, self).__init__( - id=id, - photo_file_id=photo_file_id, - title=title, - description=description, - caption=caption, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultCachedGif(InlineQueryResult): - """ - Represents a link to an animated GIF file stored on the Telegram servers. - - By default, this animated GIF file will be sent by the user with an optional caption. - Alternatively, you can use input_message_content to send a message with specified content - instead of the animation. - - https://core.telegram.org/bots/api#inlinequeryresultcachedgif - """ - - type: base.String = fields.Field(alias="type", default="gif") - gif_file_id: base.String = fields.Field() - title: base.String = fields.Field() - caption: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - gif_file_id: base.String, - title: typing.Optional[base.String] = None, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultCachedGif, self).__init__( - id=id, - gif_file_id=gif_file_id, - title=title, - caption=caption, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): - """ - Represents a link to a video animation (H.264/MPEG-4 AVC video without sound) stored on the Telegram servers. - - By default, this animated MPEG-4 file will be sent by the user with an optional caption. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the animation. - - https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif - """ - - type: base.String = fields.Field(alias="type", default="mpeg4_gif") - mpeg4_file_id: base.String = fields.Field() - title: base.String = fields.Field() - caption: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - mpeg4_file_id: base.String, - title: typing.Optional[base.String] = None, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultCachedMpeg4Gif, self).__init__( - id=id, - mpeg4_file_id=mpeg4_file_id, - title=title, - caption=caption, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultCachedSticker(InlineQueryResult): - """ - Represents a link to a sticker stored on the Telegram servers. - - By default, this sticker will be sent by the user. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the sticker. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will ignore them. - - https://core.telegram.org/bots/api#inlinequeryresultcachedsticker - """ - - type: base.String = fields.Field(alias="type", default="sticker") - sticker_file_id: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - sticker_file_id: base.String, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultCachedSticker, self).__init__( - id=id, - sticker_file_id=sticker_file_id, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultCachedDocument(InlineQueryResult): - """ - Represents a link to a file stored on the Telegram servers. - By default, this file will be sent by the user with an optional caption. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the file. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will ignore them. - - https://core.telegram.org/bots/api#inlinequeryresultcacheddocument - """ - - type: base.String = fields.Field(alias="type", default="document") - title: base.String = fields.Field() - document_file_id: base.String = fields.Field() - description: base.String = fields.Field() - caption: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - title: base.String, - document_file_id: base.String, - description: typing.Optional[base.String] = None, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultCachedDocument, self).__init__( - id=id, - title=title, - document_file_id=document_file_id, - description=description, - caption=caption, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultCachedVideo(InlineQueryResult): - """ - Represents a link to a video file stored on the Telegram servers. - - By default, this video file will be sent by the user with an optional caption. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the video. - - https://core.telegram.org/bots/api#inlinequeryresultcachedvideo - """ - - type: base.String = fields.Field(alias="type", default="video") - video_file_id: base.String = fields.Field() - title: base.String = fields.Field() - description: base.String = fields.Field() - caption: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - video_file_id: base.String, - title: base.String, - description: typing.Optional[base.String] = None, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultCachedVideo, self).__init__( - id=id, - video_file_id=video_file_id, - title=title, - description=description, - caption=caption, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultCachedVoice(InlineQueryResult): - """ - Represents a link to a voice message stored on the Telegram servers. - - By default, this voice message will be sent by the user. - Alternatively, you can use input_message_content to send a message with the specified content - instead of the voice message. - - Note: This will only work in Telegram versions released after 9 April, 2016. Older clients will ignore them. - - https://core.telegram.org/bots/api#inlinequeryresultcachedvoice - """ - - type: base.String = fields.Field(alias="type", default="voice") - voice_file_id: base.String = fields.Field() - title: base.String = fields.Field() - caption: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - voice_file_id: base.String, - title: base.String, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultCachedVoice, self).__init__( - id=id, - voice_file_id=voice_file_id, - title=title, - caption=caption, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) - - -class InlineQueryResultCachedAudio(InlineQueryResult): - """ - Represents a link to an mp3 audio file stored on the Telegram servers. - - By default, this audio file will be sent by the user. - Alternatively, you can use input_message_content to send a message with - the specified content instead of the audio. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will ignore them. - - https://core.telegram.org/bots/api#inlinequeryresultcachedaudio - """ - - type: base.String = fields.Field(alias="type", default="audio") - audio_file_id: base.String = fields.Field() - caption: base.String = fields.Field() - input_message_content: InputMessageContent = fields.Field(base=InputMessageContent) - - def __init__( - self, - *, - id: base.String, - audio_file_id: base.String, - caption: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - reply_markup: typing.Optional[InlineKeyboardMarkup] = None, - input_message_content: typing.Optional[InputMessageContent] = None, - ): - super(InlineQueryResultCachedAudio, self).__init__( - id=id, - audio_file_id=audio_file_id, - caption=caption, - parse_mode=parse_mode, - reply_markup=reply_markup, - input_message_content=input_message_content, - ) diff --git a/aiogram/types/input_file.py b/aiogram/types/input_file.py deleted file mode 100644 index a6959295..00000000 --- a/aiogram/types/input_file.py +++ /dev/null @@ -1,219 +0,0 @@ -import asyncio -import inspect -import io -import logging -import os -import secrets - -import aiohttp - -from . import base -from ..bot import api - -CHUNK_SIZE = 65536 - -log = logging.getLogger("aiogram") - - -class InputFile(base.TelegramObject): - """ - This object represents the contents of a file to be uploaded. - Must be posted using multipart/form-data in the usual way that files are uploaded via the browser. - - Also that is not typical TelegramObject! - - https://core.telegram.org/bots/api#inputfile - """ - - def __init__(self, path_or_bytesio, filename=None, conf=None): - """ - - :param path_or_bytesio: - :param filename: - :param conf: - """ - super(InputFile, self).__init__(conf=conf) - if isinstance(path_or_bytesio, str): - # As path - self._file = open(path_or_bytesio, "rb") - self._path = path_or_bytesio - if filename is None: - filename = os.path.split(path_or_bytesio)[-1] - elif isinstance(path_or_bytesio, io.IOBase): - self._path = None - self._file = path_or_bytesio - elif isinstance(path_or_bytesio, _WebPipe): - self._path = None - self._file = path_or_bytesio - else: - raise TypeError("Not supported file type.") - - self._filename = filename - - self.attachment_key = secrets.token_urlsafe(16) - - def __del__(self): - """ - Close file descriptor - """ - if not hasattr(self, "_file"): - return - - if inspect.iscoroutinefunction(self._file.close): - return asyncio.ensure_future(self._file.close()) - self._file.close() - - @property - def filename(self): - if self._filename is None: - self._filename = api.guess_filename(self._file) - return self._filename - - @filename.setter - def filename(self, value): - self._filename = value - - @property - def attach(self): - return f"attach://{self.attachment_key}" - - def get_filename(self) -> str: - """ - Get file name - - :return: name - """ - return self.filename - - @property - def file(self): - return self._file - - def get_file(self): - """ - Get file object - - :return: - """ - return self.file - - @classmethod - def from_url(cls, url, filename=None, chunk_size=CHUNK_SIZE): - """ - Download file from URL - - Manually is not required action. You can send urls instead! - - :param url: target URL - :param filename: optional. set custom file name - :param chunk_size: - - :return: InputFile - """ - pipe = _WebPipe(url, chunk_size=chunk_size) - if filename is None: - filename = pipe.name - - return cls(pipe, filename, chunk_size) - - def save(self, filename, chunk_size=CHUNK_SIZE): - """ - Write file to disk - - :param filename: - :param chunk_size: - """ - with open(filename, "wb") as fp: - while True: - # Chunk writer - data = self.file.read(chunk_size) - if not data: - break - fp.write(data) - # Flush all data - fp.flush() - - # Go to start of file. - if self.file.seekable(): - self.file.seek(0) - - def __str__(self): - return f"" - - __repr__ = __str__ - - def to_python(self): - raise TypeError("Object of this type is not exportable!") - - @classmethod - def to_object(cls, data): - raise TypeError("Object of this type is not importable!") - - -class _WebPipe: - def __init__(self, url, chunk_size=-1): - self.url = url - self.chunk_size = chunk_size - - self._session: aiohttp.ClientSession = None - self._response: aiohttp.ClientResponse = None - self._reader = None - self._name = None - - self._lock = asyncio.Lock() - - @property - def name(self): - if not self._name: - *_, part = self.url.rpartition("/") - if part: - self._name = part - else: - self._name = secrets.token_urlsafe(24) - return self._name - - async def open(self): - session = self._session = aiohttp.ClientSession() - self._response = await session.get(self.url) # type: aiohttp.ClientResponse - await self._lock.acquire() - - return self - - async def close(self): - if self._response and not self._response.closed: - await self._response.close() - if self._session and not self._session.closed: - await self._session.close() - if self._lock.locked(): - self._lock.release() - - @property - def closed(self): - return not self._session or self._session.closed - - def __aiter__(self): - return self - - async def __anext__(self): - if self.closed: - await self.open() - - chunk = await self.read(self.chunk_size) - if not chunk: - await self.close() - raise StopAsyncIteration - return chunk - - async def read(self, chunk_size=-1): - if not self._response: - raise LookupError("I/O operation on closed stream") - response: aiohttp.ClientResponse = self._response - reader: aiohttp.StreamReader = response.content - - return await reader.read(chunk_size) - - def __str__(self): - result = f"WebPipe url='{self.url}', name='{self.name}'" - return "<" + result + ">" - - __repr__ = __str__ diff --git a/aiogram/types/input_media.py b/aiogram/types/input_media.py deleted file mode 100644 index 95ca75ae..00000000 --- a/aiogram/types/input_media.py +++ /dev/null @@ -1,364 +0,0 @@ -import io -import secrets -import typing - -from . import base -from . import fields -from .input_file import InputFile - -ATTACHMENT_PREFIX = 'attach://' - - -class InputMedia(base.TelegramObject): - """ - This object represents the content of a media message to be sent. It should be one of - - InputMediaAnimation - - InputMediaDocument - - InputMediaAudio - - InputMediaPhoto - - InputMediaVideo - - That is only base class. - - https://core.telegram.org/bots/api#inputmedia - """ - type: base.String = fields.Field(default='photo') - media: base.String = fields.Field(alias='media', on_change='_media_changed') - thumb: typing.Union[base.InputFile, base.String] = fields.Field(alias='thumb', on_change='_thumb_changed') - caption: base.String = fields.Field() - parse_mode: base.String = fields.Field() - - def __init__(self, *args, **kwargs): - self._thumb_file = None - self._media_file = None - - media = kwargs.pop('media', None) - if isinstance(media, (io.IOBase, InputFile)): - self.file = media - elif media is not None: - self.media = media - - thumb = kwargs.pop('thumb', None) - if isinstance(thumb, (io.IOBase, InputFile)): - self.thumb_file = thumb - elif thumb is not None: - self.thumb = thumb - - super(InputMedia, self).__init__(*args, **kwargs) - - try: - if self.parse_mode is None and self.bot and self.bot.parse_mode: - self.parse_mode = self.bot.parse_mode - except RuntimeError: - pass - - @property - def file(self): - return self._media_file - - @file.setter - def file(self, file: io.IOBase): - self.media = 'attach://' + secrets.token_urlsafe(16) - self._media_file = file - - @file.deleter - def file(self): - self.media = None - self._media_file = None - - def _media_changed(self, value): - if value is None or isinstance(value, str) and not value.startswith('attach://'): - self._media_file = None - - @property - def thumb_file(self): - return self._thumb_file - - @thumb_file.setter - def thumb_file(self, file: io.IOBase): - self.thumb = 'attach://' + secrets.token_urlsafe(16) - self._thumb_file = file - - @thumb_file.deleter - def thumb_file(self): - self.thumb = None - self._thumb_file = None - - def _thumb_changed(self, value): - if value is None or isinstance(value, str) and not value.startswith('attach://'): - self._thumb_file = None - - def get_files(self): - if self._media_file: - yield self.media[9:], self._media_file - if self._thumb_file: - yield self.thumb[9:], self._thumb_file - - -class InputMediaAnimation(InputMedia): - """ - Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. - - https://core.telegram.org/bots/api#inputmediaanimation - """ - - width: base.Integer = fields.Field() - height: base.Integer = fields.Field() - duration: base.Integer = fields.Field() - - def __init__(self, media: base.InputFile, - thumb: typing.Union[base.InputFile, base.String] = None, - caption: base.String = None, - width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None, - parse_mode: base.String = None, **kwargs): - super(InputMediaAnimation, self).__init__(type='animation', media=media, thumb=thumb, caption=caption, - width=width, height=height, duration=duration, - parse_mode=parse_mode, conf=kwargs) - - -class InputMediaDocument(InputMedia): - """ - Represents a photo to be sent. - - https://core.telegram.org/bots/api#inputmediadocument - """ - - def __init__(self, media: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None, - caption: base.String = None, parse_mode: base.String = None, **kwargs): - super(InputMediaDocument, self).__init__(type='document', media=media, thumb=thumb, - caption=caption, parse_mode=parse_mode, - conf=kwargs) - - -class InputMediaAudio(InputMedia): - """ - Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. - - https://core.telegram.org/bots/api#inputmediaanimation - """ - - width: base.Integer = fields.Field() - height: base.Integer = fields.Field() - duration: base.Integer = fields.Field() - performer: base.String = fields.Field() - title: base.String = fields.Field() - - def __init__(self, media: base.InputFile, - thumb: typing.Union[base.InputFile, base.String] = None, - caption: base.String = None, - width: base.Integer = None, height: base.Integer = None, - duration: base.Integer = None, - performer: base.String = None, - title: base.String = None, - parse_mode: base.String = None, **kwargs): - super(InputMediaAudio, self).__init__(type='audio', media=media, thumb=thumb, caption=caption, - width=width, height=height, duration=duration, - performer=performer, title=title, - parse_mode=parse_mode, conf=kwargs) - - -class InputMediaPhoto(InputMedia): - """ - Represents a photo to be sent. - - https://core.telegram.org/bots/api#inputmediaphoto - """ - - def __init__(self, media: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None, - caption: base.String = None, parse_mode: base.String = None, **kwargs): - super(InputMediaPhoto, self).__init__(type='photo', media=media, thumb=thumb, - caption=caption, parse_mode=parse_mode, - conf=kwargs) - - -class InputMediaVideo(InputMedia): - """ - Represents a video to be sent. - - https://core.telegram.org/bots/api#inputmediavideo - """ - width: base.Integer = fields.Field() - height: base.Integer = fields.Field() - duration: base.Integer = fields.Field() - supports_streaming: base.Boolean = fields.Field() - - def __init__(self, media: base.InputFile, - thumb: typing.Union[base.InputFile, base.String] = None, - caption: base.String = None, - width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None, - parse_mode: base.String = None, - supports_streaming: base.Boolean = None, **kwargs): - super(InputMediaVideo, self).__init__(type='video', media=media, thumb=thumb, caption=caption, - width=width, height=height, duration=duration, - parse_mode=parse_mode, - supports_streaming=supports_streaming, conf=kwargs) - - -class MediaGroup(base.TelegramObject): - """ - Helper for sending media group - """ - - def __init__(self, medias: typing.Optional[typing.List[typing.Union[InputMedia, typing.Dict]]] = None): - super(MediaGroup, self).__init__() - self.media = [] - - if medias: - self.attach_many(*medias) - - def attach_many(self, *medias: typing.Union[InputMedia, typing.Dict]): - """ - Attach list of media - - :param medias: - """ - for media in medias: - self.attach(media) - - def attach(self, media: typing.Union[InputMedia, typing.Dict]): - """ - Attach media - - :param media: - """ - if isinstance(media, dict): - if 'type' not in media: - raise ValueError(f"Invalid media!") - - media_type = media['type'] - if media_type == 'photo': - media = InputMediaPhoto(**media) - elif media_type == 'video': - media = InputMediaVideo(**media) - # elif media_type == 'document': - # media = InputMediaDocument(**media) - # elif media_type == 'audio': - # media = InputMediaAudio(**media) - # elif media_type == 'animation': - # media = InputMediaAnimation(**media) - else: - raise TypeError(f"Invalid media type '{media_type}'!") - - elif not isinstance(media, InputMedia): - raise TypeError(f"Media must be an instance of InputMedia or dict, not {type(media).__name__}") - - elif media.type in ['document', 'audio', 'animation']: - raise ValueError(f"This type of media is not supported by media groups!") - - self.media.append(media) - - ''' - def attach_animation(self, animation: base.InputFile, - thumb: typing.Union[base.InputFile, base.String] = None, - caption: base.String = None, - width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None, - parse_mode: base.Boolean = None): - """ - Attach animation - - :param animation: - :param thumb: - :param caption: - :param width: - :param height: - :param duration: - :param parse_mode: - """ - if not isinstance(animation, InputMedia): - animation = InputMediaAnimation(media=animation, thumb=thumb, caption=caption, - width=width, height=height, duration=duration, - parse_mode=parse_mode) - self.attach(animation) - - def attach_audio(self, audio: base.InputFile, - thumb: typing.Union[base.InputFile, base.String] = None, - caption: base.String = None, - width: base.Integer = None, height: base.Integer = None, - duration: base.Integer = None, - performer: base.String = None, - title: base.String = None, - parse_mode: base.String = None): - """ - Attach animation - - :param audio: - :param thumb: - :param caption: - :param width: - :param height: - :param duration: - :param performer: - :param title: - :param parse_mode: - """ - if not isinstance(audio, InputMedia): - audio = InputMediaAudio(media=audio, thumb=thumb, caption=caption, - width=width, height=height, duration=duration, - performer=performer, title=title, - parse_mode=parse_mode) - self.attach(audio) - - def attach_document(self, document: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None, - caption: base.String = None, parse_mode: base.String = None): - """ - Attach document - - :param parse_mode: - :param caption: - :param thumb: - :param document: - """ - if not isinstance(document, InputMedia): - document = InputMediaDocument(media=document, thumb=thumb, caption=caption, parse_mode=parse_mode) - self.attach(document) - ''' - - def attach_photo(self, photo: typing.Union[InputMediaPhoto, base.InputFile], - caption: base.String = None): - """ - Attach photo - - :param photo: - :param caption: - """ - if not isinstance(photo, InputMedia): - photo = InputMediaPhoto(media=photo, caption=caption) - self.attach(photo) - - def attach_video(self, video: typing.Union[InputMediaVideo, base.InputFile], - thumb: typing.Union[base.InputFile, base.String] = None, - caption: base.String = None, - width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None): - """ - Attach video - - :param video: - :param caption: - :param width: - :param height: - :param duration: - """ - if not isinstance(video, InputMedia): - video = InputMediaVideo(media=video, thumb=thumb, caption=caption, - width=width, height=height, duration=duration) - self.attach(video) - - def to_python(self) -> typing.List: - """ - Get object as JSON serializable - - :return: - """ - self.clean() - result = [] - for obj in self.media: - if isinstance(obj, base.TelegramObject): - obj = obj.to_python() - result.append(obj) - return result - - def get_files(self): - for inputmedia in self.media: - if not isinstance(inputmedia, InputMedia) or not inputmedia.file: - continue - yield from inputmedia.get_files() diff --git a/aiogram/types/input_message_content.py b/aiogram/types/input_message_content.py deleted file mode 100644 index cea1c1af..00000000 --- a/aiogram/types/input_message_content.py +++ /dev/null @@ -1,125 +0,0 @@ -import typing - -from . import base -from . import fields - - -class InputMessageContent(base.TelegramObject): - """ - This object represents the content of a message to be sent as a result of an inline query. - - Telegram clients currently support the following 4 types - - https://core.telegram.org/bots/api#inputmessagecontent - """ - - pass - - -class InputContactMessageContent(InputMessageContent): - """ - Represents the content of a contact message to be sent as the result of an inline query. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will ignore them. - - https://core.telegram.org/bots/api#inputcontactmessagecontent - """ - - phone_number: base.String = fields.Field() - first_name: base.String = fields.Field() - last_name: base.String = fields.Field() - vcard: base.String = fields.Field() - - def __init__( - self, - phone_number: base.String, - first_name: typing.Optional[base.String] = None, - last_name: typing.Optional[base.String] = None, - ): - super(InputContactMessageContent, self).__init__( - phone_number=phone_number, first_name=first_name, last_name=last_name - ) - - -class InputLocationMessageContent(InputMessageContent): - """ - Represents the content of a location message to be sent as the result of an inline query. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will ignore them. - - https://core.telegram.org/bots/api#inputlocationmessagecontent - """ - - latitude: base.Float = fields.Field() - longitude: base.Float = fields.Field() - - def __init__(self, latitude: base.Float, longitude: base.Float): - super(InputLocationMessageContent, self).__init__(latitude=latitude, longitude=longitude) - - -class InputTextMessageContent(InputMessageContent): - """ - Represents the content of a text message to be sent as the result of an inline query. - - https://core.telegram.org/bots/api#inputtextmessagecontent - """ - - message_text: base.String = fields.Field() - parse_mode: base.String = fields.Field() - disable_web_page_preview: base.Boolean = fields.Field() - - def safe_get_parse_mode(self): - try: - return self.bot.parse_mode - except RuntimeError: - pass - - def __init__( - self, - message_text: typing.Optional[base.String] = None, - parse_mode: typing.Optional[base.String] = None, - disable_web_page_preview: typing.Optional[base.Boolean] = None, - ): - if parse_mode is None: - parse_mode = self.safe_get_parse_mode() - - super(InputTextMessageContent, self).__init__( - message_text=message_text, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - ) - - -class InputVenueMessageContent(InputMessageContent): - """ - Represents the content of a venue message to be sent as the result of an inline query. - - Note: This will only work in Telegram versions released after 9 April, 2016. - Older clients will ignore them. - - https://core.telegram.org/bots/api#inputvenuemessagecontent - """ - - latitude: base.Float = fields.Field() - longitude: base.Float = fields.Field() - title: base.String = fields.Field() - address: base.String = fields.Field() - foursquare_id: base.String = fields.Field() - - def __init__( - self, - latitude: typing.Optional[base.Float] = None, - longitude: typing.Optional[base.Float] = None, - title: typing.Optional[base.String] = None, - address: typing.Optional[base.String] = None, - foursquare_id: typing.Optional[base.String] = None, - ): - super(InputVenueMessageContent, self).__init__( - latitude=latitude, - longitude=longitude, - title=title, - address=address, - foursquare_id=foursquare_id, - ) diff --git a/aiogram/types/invoice.py b/aiogram/types/invoice.py deleted file mode 100644 index 4112e1a3..00000000 --- a/aiogram/types/invoice.py +++ /dev/null @@ -1,16 +0,0 @@ -from . import base -from . import fields - - -class Invoice(base.TelegramObject): - """ - This object contains basic information about an invoice. - - https://core.telegram.org/bots/api#invoice - """ - - title: base.String = fields.Field() - description: base.String = fields.Field() - start_parameter: base.String = fields.Field() - currency: base.String = fields.Field() - total_amount: base.Integer = fields.Field() diff --git a/aiogram/types/labeled_price.py b/aiogram/types/labeled_price.py deleted file mode 100644 index c1cbd48e..00000000 --- a/aiogram/types/labeled_price.py +++ /dev/null @@ -1,16 +0,0 @@ -from . import base -from . import fields - - -class LabeledPrice(base.TelegramObject): - """ - This object represents a portion of the price for goods or services. - - https://core.telegram.org/bots/api#labeledprice - """ - - label: base.String = fields.Field() - amount: base.Integer = fields.Field() - - def __init__(self, label: base.String, amount: base.Integer): - super(LabeledPrice, self).__init__(label=label, amount=amount) diff --git a/aiogram/types/location.py b/aiogram/types/location.py deleted file mode 100644 index a1df1829..00000000 --- a/aiogram/types/location.py +++ /dev/null @@ -1,13 +0,0 @@ -from . import base -from . import fields - - -class Location(base.TelegramObject): - """ - This object represents a point on the map. - - https://core.telegram.org/bots/api#location - """ - - longitude: base.Float = fields.Field() - latitude: base.Float = fields.Field() diff --git a/aiogram/types/login_url.py b/aiogram/types/login_url.py deleted file mode 100644 index 1c9468e6..00000000 --- a/aiogram/types/login_url.py +++ /dev/null @@ -1,33 +0,0 @@ -from . import base -from . import fields - - -class LoginUrl(base.TelegramObject): - """ - This object represents a parameter of the inline keyboard button used to automatically authorize a user. - Serves as a great replacement for the Telegram Login Widget when the user is coming from Telegram. - All the user needs to do is tap/click a button and confirm that they want to log in. - - https://core.telegram.org/bots/api#loginurl - """ - - url: base.String = fields.Field() - forward_text: base.String = fields.Field() - bot_username: base.String = fields.Field() - request_write_access: base.Boolean = fields.Field() - - def __init__( - self, - url: base.String, - forward_text: base.String = None, - bot_username: base.String = None, - request_write_access: base.Boolean = None, - **kwargs, - ): - super(LoginUrl, self).__init__( - url=url, - forward_text=forward_text, - bot_username=bot_username, - request_write_access=request_write_access, - **kwargs, - ) diff --git a/aiogram/types/mask_position.py b/aiogram/types/mask_position.py deleted file mode 100644 index 57003798..00000000 --- a/aiogram/types/mask_position.py +++ /dev/null @@ -1,15 +0,0 @@ -from . import base -from . import fields - - -class MaskPosition(base.TelegramObject): - """ - This object describes the position on faces where a mask should be placed by default. - - https://core.telegram.org/bots/api#maskposition - """ - - point: base.String = fields.Field() - x_shift: base.Float = fields.Field() - y_shift: base.Float = fields.Field() - scale: base.Float = fields.Field() diff --git a/aiogram/types/message.py b/aiogram/types/message.py deleted file mode 100644 index 8fdece2b..00000000 --- a/aiogram/types/message.py +++ /dev/null @@ -1,1801 +0,0 @@ -from __future__ import annotations - -import datetime -import functools -import sys -import typing - -from . import base -from . import fields -from .animation import Animation -from .audio import Audio -from .chat import Chat, ChatType -from .contact import Contact -from .document import Document -from .force_reply import ForceReply -from .game import Game -from .inline_keyboard import InlineKeyboardMarkup -from .input_media import MediaGroup, InputMedia -from .invoice import Invoice -from .location import Location -from .message_entity import MessageEntity -from .passport_data import PassportData -from .photo_size import PhotoSize -from .poll import Poll -from .reply_keyboard import ReplyKeyboardRemove, ReplyKeyboardMarkup -from .sticker import Sticker -from .successful_payment import SuccessfulPayment -from .user import User -from .venue import Venue -from .video import Video -from .video_note import VideoNote -from .voice import Voice -from ..utils import helper -from ..utils import markdown as md - - -class Message(base.TelegramObject): - """ - This object represents a message. - - https://core.telegram.org/bots/api#message - """ - message_id: base.Integer = fields.Field() - from_user: User = fields.Field(alias='from', base=User) - date: datetime.datetime = fields.DateTimeField() - chat: Chat = fields.Field(base=Chat) - forward_from: User = fields.Field(base=User) - forward_from_chat: Chat = fields.Field(base=Chat) - forward_from_message_id: base.Integer = fields.Field() - forward_signature: base.String = fields.Field() - forward_date: datetime.datetime = fields.DateTimeField() - reply_to_message: Message = fields.Field(base='Message') - edit_date: datetime.datetime = fields.DateTimeField() - media_group_id: base.String = fields.Field() - author_signature: base.String = fields.Field() - forward_sender_name: base.String = fields.Field() - text: base.String = fields.Field() - entities: typing.List[MessageEntity] = fields.ListField(base=MessageEntity) - caption_entities: typing.List[MessageEntity] = fields.ListField(base=MessageEntity) - audio: Audio = fields.Field(base=Audio) - document: Document = fields.Field(base=Document) - animation: Animation = fields.Field(base=Animation) - game: Game = fields.Field(base=Game) - photo: typing.List[PhotoSize] = fields.ListField(base=PhotoSize) - sticker: Sticker = fields.Field(base=Sticker) - video: Video = fields.Field(base=Video) - voice: Voice = fields.Field(base=Voice) - video_note: VideoNote = fields.Field(base=VideoNote) - caption: base.String = fields.Field() - contact: Contact = fields.Field(base=Contact) - location: Location = fields.Field(base=Location) - venue: Venue = fields.Field(base=Venue) - new_chat_members: typing.List[User] = fields.ListField(base=User) - left_chat_member: User = fields.Field(base=User) - new_chat_title: base.String = fields.Field() - new_chat_photo: typing.List[PhotoSize] = fields.ListField(base=PhotoSize) - delete_chat_photo: base.Boolean = fields.Field() - group_chat_created: base.Boolean = fields.Field() - supergroup_chat_created: base.Boolean = fields.Field() - channel_chat_created: base.Boolean = fields.Field() - migrate_to_chat_id: base.Integer = fields.Field() - migrate_from_chat_id: base.Integer = fields.Field() - pinned_message: Message = fields.Field(base='Message') - invoice: Invoice = fields.Field(base=Invoice) - successful_payment: SuccessfulPayment = fields.Field(base=SuccessfulPayment) - connected_website: base.String = fields.Field() - passport_data: PassportData = fields.Field(base=PassportData) - poll: Poll = fields.Field(base=Poll) - reply_markup: InlineKeyboardMarkup = fields.Field(base=InlineKeyboardMarkup) - - @property - @functools.lru_cache() - def content_type(self): - if self.text: - return ContentType.TEXT - if self.audio: - return ContentType.AUDIO - if self.animation: - return ContentType.ANIMATION - if self.document: - return ContentType.DOCUMENT - if self.game: - return ContentType.GAME - if self.photo: - return ContentType.PHOTO - if self.sticker: - return ContentType.STICKER - if self.video: - return ContentType.VIDEO - if self.video_note: - return ContentType.VIDEO_NOTE - if self.voice: - return ContentType.VOICE - if self.contact: - return ContentType.CONTACT - if self.venue: - return ContentType.VENUE - if self.location: - return ContentType.LOCATION - if self.new_chat_members: - return ContentType.NEW_CHAT_MEMBERS - if self.left_chat_member: - return ContentType.LEFT_CHAT_MEMBER - if self.invoice: - return ContentType.INVOICE - if self.successful_payment: - return ContentType.SUCCESSFUL_PAYMENT - if self.connected_website: - return ContentType.CONNECTED_WEBSITE - if self.migrate_from_chat_id: - return ContentType.MIGRATE_FROM_CHAT_ID - if self.migrate_to_chat_id: - return ContentType.MIGRATE_TO_CHAT_ID - if self.pinned_message: - return ContentType.PINNED_MESSAGE - if self.new_chat_title: - return ContentType.NEW_CHAT_TITLE - if self.new_chat_photo: - return ContentType.NEW_CHAT_PHOTO - if self.delete_chat_photo: - return ContentType.DELETE_CHAT_PHOTO - if self.group_chat_created: - return ContentType.GROUP_CHAT_CREATED - if self.passport_data: - return ContentType.PASSPORT_DATA - if self.poll: - return ContentType.POLL - - return ContentType.UNKNOWN - - def is_command(self): - """ - Check message text is command - - :return: bool - """ - return self.text and self.text.startswith('/') - - def get_full_command(self): - """ - Split command and args - - :return: tuple of (command, args) - """ - if self.is_command(): - command, _, args = self.text.partition(' ') - return command, args - - def get_command(self, pure=False): - """ - Get command from message - - :return: - """ - command = self.get_full_command() - if command: - command = command[0] - if pure: - command, _, _ = command[1:].partition('@') - return command - - def get_args(self): - """ - Get arguments - - :return: - """ - command = self.get_full_command() - if command: - return command[1].strip() - - def parse_entities(self, as_html=True): - """ - Text or caption formatted as HTML or Markdown. - - :return: str - """ - - text = self.text or self.caption - if text is None: - raise TypeError("This message doesn't have any text.") - - quote_fn = md.quote_html if as_html else md.escape_md - - entities = self.entities or self.caption_entities - if not entities: - return quote_fn(text) - - if not sys.maxunicode == 0xffff: - text = text.encode('utf-16-le') - - result = '' - offset = 0 - - for entity in sorted(entities, key=lambda item: item.offset): - entity_text = entity.parse(text, as_html=as_html) - - if sys.maxunicode == 0xffff: - part = text[offset:entity.offset] - result += quote_fn(part) + entity_text - else: - part = text[offset * 2:entity.offset * 2] - result += quote_fn(part.decode('utf-16-le')) + entity_text - - offset = entity.offset + entity.length - - if sys.maxunicode == 0xffff: - part = text[offset:] - result += quote_fn(part) - else: - part = text[offset * 2:] - result += quote_fn(part.decode('utf-16-le')) - - return result - - @property - def md_text(self) -> str: - """ - Text or caption formatted as markdown. - - :return: str - """ - return self.parse_entities(False) - - @property - def html_text(self) -> str: - """ - Text or caption formatted as HTML - - :return: str - """ - return self.parse_entities() - - @property - def url(self) -> str: - """ - Get URL for the message - - :return: str - """ - if self.chat.type not in [ChatType.SUPER_GROUP, ChatType.CHANNEL]: - raise TypeError('Invalid chat type!') - elif not self.chat.username: - raise TypeError('This chat does not have @username') - - return f"https://t.me/{self.chat.username}/{self.message_id}" - - def link(self, text, as_html=True) -> str: - """ - Generate URL for using in text messages with HTML or MD parse mode - - :param text: link label - :param as_html: generate as HTML - :return: str - """ - try: - url = self.url - except TypeError: # URL is not accessible - if as_html: - return md.quote_html(text) - return md.escape_md(text) - - if as_html: - return md.hlink(text, url) - return md.link(text, url) - - async def answer(self, text: base.String, - parse_mode: typing.Union[base.String, None] = None, - disable_web_page_preview: typing.Union[base.Boolean, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Answer to this message - - :param text: Text of the message to be sent - :type text: :obj:`base.String` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_web_page_preview: Disables link previews for links in this message - :type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :type reply: :obj:`base.Boolean` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - return await self.bot.send_message(chat_id=self.chat.id, - text=text, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_photo(self, photo: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Use this method to send photos. - - Source: https://core.telegram.org/bots/api#sendphoto - - :param photo: Photo to send - :type photo: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Photo caption (may also be used when resending photos by file_id), 0-1024 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :type reply: :obj:`base.Boolean` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - return await self.bot.send_photo(chat_id=self.chat.id, - photo=photo, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_audio(self, audio: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - duration: typing.Union[base.Integer, None] = None, - performer: typing.Union[base.String, None] = None, - title: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Use this method to send audio files, if you want Telegram clients to display them in the music player. - Your audio must be in the .mp3 format. - - For sending voice messages, use the sendVoice method instead. - - Source: https://core.telegram.org/bots/api#sendaudio - - :param audio: Audio file to send. - :type audio: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Audio caption, 0-200 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param duration: Duration of the audio in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param performer: Performer - :type performer: :obj:`typing.Union[base.String, None]` - :param title: Track name - :type title: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_audio(chat_id=self.chat.id, - audio=audio, - caption=caption, - parse_mode=parse_mode, - duration=duration, - performer=performer, - title=title, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_animation(self, animation: typing.Union[base.InputFile, base.String], - duration: typing.Union[base.Integer, None] = None, - width: typing.Union[base.Integer, None] = None, - height: typing.Union[base.Integer, None] = None, - thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None, - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). - - On success, the sent Message is returned. - Bots can currently send animation files of up to 50 MB in size, this limit may be changed in the future. - - Source https://core.telegram.org/bots/api#sendanimation - - :param animation: Animation to send. Pass a file_id as String to send an animation that exists - on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation - from the Internet, or upload a new animation using multipart/form-data - :type animation: :obj:`typing.Union[base.InputFile, base.String]` - :param duration: Duration of sent animation in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param width: Animation width - :type width: :obj:`typing.Union[base.Integer, None]` - :param height: Animation height - :type height: :obj:`typing.Union[base.Integer, None]` - :param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 90. - :type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]` - :param caption: Animation caption (may also be used when resending animation by file_id), 0-1024 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in the media caption - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, types.ForceReply], None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - return await self.bot.send_animation(self.chat.id, - animation=animation, - duration=duration, - width=width, - height=height, - thumb=thumb, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_document(self, document: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Use this method to send general files. - - Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future. - - Source: https://core.telegram.org/bots/api#senddocument - - :param document: File to send. - :type document: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Document caption (may also be used when resending documents by file_id), 0-200 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in the media caption - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply], None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_document(chat_id=self.chat.id, - document=document, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_video(self, video: typing.Union[base.InputFile, base.String], - duration: typing.Union[base.Integer, None] = None, - width: typing.Union[base.Integer, None] = None, - height: typing.Union[base.Integer, None] = None, - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Use this method to send video files, Telegram clients support mp4 videos - (other formats may be sent as Document). - - Source: https://core.telegram.org/bots/api#sendvideo - - :param video: Video to send. - :type video: :obj:`typing.Union[base.InputFile, base.String]` - :param duration: Duration of sent video in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param width: Video width - :type width: :obj:`typing.Union[base.Integer, None]` - :param height: Video height - :type height: :obj:`typing.Union[base.Integer, None]` - :param caption: Video caption (may also be used when resending videos by file_id), 0-200 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in the media caption - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_video(chat_id=self.chat.id, - video=video, - duration=duration, - width=width, - height=height, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_voice(self, voice: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - duration: typing.Union[base.Integer, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Use this method to send audio files, if you want Telegram clients to display the file - as a playable voice message. - - For this to work, your audio must be in an .ogg file encoded with OPUS - (other formats may be sent as Audio or Document). - - Source: https://core.telegram.org/bots/api#sendvoice - - :param voice: Audio file to send. - :type voice: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Voice message caption, 0-200 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in the media caption - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param duration: Duration of the voice message in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_voice(chat_id=self.chat.id, - voice=voice, - caption=caption, - parse_mode=parse_mode, - duration=duration, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_video_note(self, video_note: typing.Union[base.InputFile, base.String], - duration: typing.Union[base.Integer, None] = None, - length: typing.Union[base.Integer, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long. - Use this method to send video messages. - - Source: https://core.telegram.org/bots/api#sendvideonote - - :param video_note: Video note to send. - :type video_note: :obj:`typing.Union[base.InputFile, base.String]` - :param duration: Duration of sent video in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param length: Video width and height - :type length: :obj:`typing.Union[base.Integer, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_video_note(chat_id=self.chat.id, - video_note=video_note, - duration=duration, - length=length, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_media_group(self, media: typing.Union[MediaGroup, typing.List], - disable_notification: typing.Union[base.Boolean, None] = None, - reply: base.Boolean = False) -> typing.List[Message]: - """ - Use this method to send a group of photos or videos as an album. - - Source: https://core.telegram.org/bots/api#sendmediagroup - - :param media: A JSON-serialized array describing photos and videos to be sent - :type media: :obj:`typing.Union[types.MediaGroup, typing.List]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, an array of the sent Messages is returned. - :rtype: typing.List[types.Message] - """ - return await self.bot.send_media_group(self.chat.id, - media=media, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None) - - async def answer_location(self, - latitude: base.Float, longitude: base.Float, - live_period: typing.Union[base.Integer, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Use this method to send point on the map. - - Source: https://core.telegram.org/bots/api#sendlocation - - :param latitude: Latitude of the location - :type latitude: :obj:`base.Float` - :param longitude: Longitude of the location - :type longitude: :obj:`base.Float` - :param live_period: Period in seconds for which the location will be updated - :type live_period: :obj:`typing.Union[base.Integer, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_location(chat_id=self.chat.id, - latitude=latitude, - longitude=longitude, - live_period=live_period, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_venue(self, - latitude: base.Float, longitude: base.Float, - title: base.String, address: base.String, - foursquare_id: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Use this method to send information about a venue. - - Source: https://core.telegram.org/bots/api#sendvenue - - :param latitude: Latitude of the venue - :type latitude: :obj:`base.Float` - :param longitude: Longitude of the venue - :type longitude: :obj:`base.Float` - :param title: Name of the venue - :type title: :obj:`base.String` - :param address: Address of the venue - :type address: :obj:`base.String` - :param foursquare_id: Foursquare identifier of the venue - :type foursquare_id: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_venue(chat_id=self.chat.id, - latitude=latitude, - longitude=longitude, - title=title, - address=address, - foursquare_id=foursquare_id, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_contact(self, phone_number: base.String, - first_name: base.String, last_name: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Use this method to send phone contacts. - - Source: https://core.telegram.org/bots/api#sendcontact - - :param phone_number: Contact's phone number - :type phone_number: :obj:`base.String` - :param first_name: Contact's first name - :type first_name: :obj:`base.String` - :param last_name: Contact's last name - :type last_name: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_contact(chat_id=self.chat.id, - phone_number=phone_number, - first_name=first_name, last_name=last_name, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def answer_sticker(self, sticker: typing.Union[base.InputFile, base.String], - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = False) -> Message: - """ - Use this method to send .webp stickers. - - Source: https://core.telegram.org/bots/api#sendsticker - - :param sticker: Sticker to send. - :type sticker: :obj:`typing.Union[base.InputFile, base.String]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_sticker(chat_id=self.chat.id, - sticker=sticker, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply(self, text: base.String, - parse_mode: typing.Union[base.String, None] = None, - disable_web_page_preview: typing.Union[base.Boolean, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Reply to this message - - :param text: Text of the message to be sent - :type text: :obj:`base.String` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_web_page_preview: Disables link previews for links in this message - :type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :type reply: :obj:`base.Boolean` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - return await self.bot.send_message(chat_id=self.chat.id, - text=text, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_photo(self, photo: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Use this method to send photos. - - Source: https://core.telegram.org/bots/api#sendphoto - - :param photo: Photo to send - :type photo: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Photo caption (may also be used when resending photos by file_id), 0-1024 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :type reply: :obj:`base.Boolean` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - return await self.bot.send_photo(chat_id=self.chat.id, - photo=photo, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_audio(self, audio: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - duration: typing.Union[base.Integer, None] = None, - performer: typing.Union[base.String, None] = None, - title: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Use this method to send audio files, if you want Telegram clients to display them in the music player. - Your audio must be in the .mp3 format. - - For sending voice messages, use the sendVoice method instead. - - Source: https://core.telegram.org/bots/api#sendaudio - - :param audio: Audio file to send. - :type audio: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Audio caption, 0-200 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param duration: Duration of the audio in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param performer: Performer - :type performer: :obj:`typing.Union[base.String, None]` - :param title: Track name - :type title: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_audio(chat_id=self.chat.id, - audio=audio, - caption=caption, - parse_mode=parse_mode, - duration=duration, - performer=performer, - title=title, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_animation(self, animation: typing.Union[base.InputFile, base.String], - duration: typing.Union[base.Integer, None] = None, - width: typing.Union[base.Integer, None] = None, - height: typing.Union[base.Integer, None] = None, - thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None, - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). - - On success, the sent Message is returned. - Bots can currently send animation files of up to 50 MB in size, this limit may be changed in the future. - - Source https://core.telegram.org/bots/api#sendanimation - - :param animation: Animation to send. Pass a file_id as String to send an animation that exists - on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation - from the Internet, or upload a new animation using multipart/form-data - :type animation: :obj:`typing.Union[base.InputFile, base.String]` - :param duration: Duration of sent animation in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param width: Animation width - :type width: :obj:`typing.Union[base.Integer, None]` - :param height: Animation height - :type height: :obj:`typing.Union[base.Integer, None]` - :param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 90. - :type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]` - :param caption: Animation caption (may also be used when resending animation by file_id), 0-1024 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in the media caption - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, types.ForceReply], None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - return await self.bot.send_animation(self.chat.id, - animation=animation, - duration=duration, - width=width, - height=height, - thumb=thumb, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_document(self, document: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Use this method to send general files. - - Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future. - - Source: https://core.telegram.org/bots/api#senddocument - - :param document: File to send. - :type document: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Document caption (may also be used when resending documents by file_id), 0-200 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in the media caption - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply], None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_document(chat_id=self.chat.id, - document=document, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_video(self, video: typing.Union[base.InputFile, base.String], - duration: typing.Union[base.Integer, None] = None, - width: typing.Union[base.Integer, None] = None, - height: typing.Union[base.Integer, None] = None, - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Use this method to send video files, Telegram clients support mp4 videos - (other formats may be sent as Document). - - Source: https://core.telegram.org/bots/api#sendvideo - - :param video: Video to send. - :type video: :obj:`typing.Union[base.InputFile, base.String]` - :param duration: Duration of sent video in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param width: Video width - :type width: :obj:`typing.Union[base.Integer, None]` - :param height: Video height - :type height: :obj:`typing.Union[base.Integer, None]` - :param caption: Video caption (may also be used when resending videos by file_id), 0-200 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in the media caption - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_video(chat_id=self.chat.id, - video=video, - duration=duration, - width=width, - height=height, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_voice(self, voice: typing.Union[base.InputFile, base.String], - caption: typing.Union[base.String, None] = None, - parse_mode: typing.Union[base.String, None] = None, - duration: typing.Union[base.Integer, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Use this method to send audio files, if you want Telegram clients to display the file - as a playable voice message. - - For this to work, your audio must be in an .ogg file encoded with OPUS - (other formats may be sent as Audio or Document). - - Source: https://core.telegram.org/bots/api#sendvoice - - :param voice: Audio file to send. - :type voice: :obj:`typing.Union[base.InputFile, base.String]` - :param caption: Voice message caption, 0-200 characters - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in the media caption - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param duration: Duration of the voice message in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_voice(chat_id=self.chat.id, - voice=voice, - caption=caption, - parse_mode=parse_mode, - duration=duration, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_video_note(self, video_note: typing.Union[base.InputFile, base.String], - duration: typing.Union[base.Integer, None] = None, - length: typing.Union[base.Integer, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long. - Use this method to send video messages. - - Source: https://core.telegram.org/bots/api#sendvideonote - - :param video_note: Video note to send. - :type video_note: :obj:`typing.Union[base.InputFile, base.String]` - :param duration: Duration of sent video in seconds - :type duration: :obj:`typing.Union[base.Integer, None]` - :param length: Video width and height - :type length: :obj:`typing.Union[base.Integer, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_video_note(chat_id=self.chat.id, - video_note=video_note, - duration=duration, - length=length, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_media_group(self, media: typing.Union[MediaGroup, typing.List], - disable_notification: typing.Union[base.Boolean, None] = None, - reply: base.Boolean = True) -> typing.List[Message]: - """ - Use this method to send a group of photos or videos as an album. - - Source: https://core.telegram.org/bots/api#sendmediagroup - - :param media: A JSON-serialized array describing photos and videos to be sent - :type media: :obj:`typing.Union[types.MediaGroup, typing.List]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, an array of the sent Messages is returned. - :rtype: typing.List[types.Message] - """ - return await self.bot.send_media_group(self.chat.id, - media=media, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None) - - async def reply_location(self, - latitude: base.Float, longitude: base.Float, - live_period: typing.Union[base.Integer, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Use this method to send point on the map. - - Source: https://core.telegram.org/bots/api#sendlocation - - :param latitude: Latitude of the location - :type latitude: :obj:`base.Float` - :param longitude: Longitude of the location - :type longitude: :obj:`base.Float` - :param live_period: Period in seconds for which the location will be updated - :type live_period: :obj:`typing.Union[base.Integer, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_location(chat_id=self.chat.id, - latitude=latitude, - longitude=longitude, - live_period=live_period, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_venue(self, - latitude: base.Float, longitude: base.Float, - title: base.String, address: base.String, - foursquare_id: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Use this method to send information about a venue. - - Source: https://core.telegram.org/bots/api#sendvenue - - :param latitude: Latitude of the venue - :type latitude: :obj:`base.Float` - :param longitude: Longitude of the venue - :type longitude: :obj:`base.Float` - :param title: Name of the venue - :type title: :obj:`base.String` - :param address: Address of the venue - :type address: :obj:`base.String` - :param foursquare_id: Foursquare identifier of the venue - :type foursquare_id: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_venue(chat_id=self.chat.id, - latitude=latitude, - longitude=longitude, - title=title, - address=address, - foursquare_id=foursquare_id, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_contact(self, phone_number: base.String, - first_name: base.String, last_name: typing.Union[base.String, None] = None, - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Use this method to send phone contacts. - - Source: https://core.telegram.org/bots/api#sendcontact - - :param phone_number: Contact's phone number - :type phone_number: :obj:`base.String` - :param first_name: Contact's first name - :type first_name: :obj:`base.String` - :param last_name: Contact's last name - :type last_name: :obj:`typing.Union[base.String, None]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_contact(chat_id=self.chat.id, - phone_number=phone_number, - first_name=first_name, last_name=last_name, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def reply_sticker(self, sticker: typing.Union[base.InputFile, base.String], - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ReplyKeyboardRemove, - ForceReply, None] = None, - reply: base.Boolean = True) -> Message: - """ - Use this method to send .webp stickers. - - Source: https://core.telegram.org/bots/api#sendsticker - - :param sticker: Sticker to send. - :type sticker: :obj:`typing.Union[base.InputFile, base.String]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, - custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, - types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` - :param reply: fill 'reply_to_message_id' - :return: On success, the sent Message is returned. - :rtype: :obj:`types.Message` - """ - return await self.bot.send_sticker(chat_id=self.chat.id, - sticker=sticker, - disable_notification=disable_notification, - reply_to_message_id=self.message_id if reply else None, - reply_markup=reply_markup) - - async def forward(self, chat_id: typing.Union[base.Integer, base.String], - disable_notification: typing.Union[base.Boolean, None] = None) -> Message: - """ - Forward this message - - Source: https://core.telegram.org/bots/api#forwardmessage - - :param chat_id: Unique identifier for the target chat or username of the target channel - :type chat_id: :obj:`typing.Union[base.Integer, base.String]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :return: On success, the sent Message is returned - :rtype: :obj:`types.Message` - """ - return await self.bot.forward_message(chat_id, self.chat.id, self.message_id, disable_notification) - - async def edit_text(self, text: base.String, - parse_mode: typing.Union[base.String, None] = None, - disable_web_page_preview: typing.Union[base.Boolean, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - None] = None) -> typing.Union[Message, base.Boolean]: - """ - Use this method to edit text and game messages sent by the bot or via the bot (for inline bots). - - Source: https://core.telegram.org/bots/api#editmessagetext - - :param text: New text of the message - :type text: :obj:`base.String` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param disable_web_page_preview: Disables link previews for links in this message - :type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: A JSON-serialized object for an inline keyboard. - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if edited message is sent by the bot, - the edited Message is returned, otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - return await self.bot.edit_message_text(text=text, - chat_id=self.chat.id, message_id=self.message_id, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - reply_markup=reply_markup) - - async def edit_caption(self, caption: base.String, - parse_mode: typing.Union[base.String, None] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, - None] = None) -> typing.Union[Message, base.Boolean]: - """ - Use this method to edit captions of messages sent by the bot or via the bot (for inline bots). - - Source: https://core.telegram.org/bots/api#editmessagecaption - - :param caption: New caption of the message - :type caption: :obj:`typing.Union[base.String, None]` - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, - fixed-width text or inline URLs in your bot's message. - :type parse_mode: :obj:`typing.Union[base.String, None]` - :param reply_markup: A JSON-serialized object for an inline keyboard - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if edited message is sent by the bot, the edited Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - return await self.bot.edit_message_caption(chat_id=self.chat.id, message_id=self.message_id, caption=caption, - parse_mode=parse_mode, reply_markup=reply_markup) - - async def edit_media(self, media: InputMedia, - reply_markup: typing.Union[InlineKeyboardMarkup, - None] = None) -> typing.Union[Message, base.Boolean]: - """ - Use this method to edit audio, document, photo, or video messages. - If a message is a part of a message album, then it can be edited only to a photo or a video. - Otherwise, message type can be changed arbitrarily. - When inline message is edited, new file can't be uploaded. - Use previously uploaded file via its file_id or specify a URL. - - On success, if the edited message was sent by the bot, - the edited Message is returned, otherwise True is returned. - - Source https://core.telegram.org/bots/api#editmessagemedia - - :param media: A JSON-serialized object for a new media content of the message - :type media: :obj:`types.InputMedia` - :param reply_markup: A JSON-serialized object for a new inline keyboard - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if the edited message was sent by the bot, the edited Message is returned, - otherwise True is returned - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - return await self.bot.edit_message_media(media=media, chat_id=self.chat.id, message_id=self.message_id, - reply_markup=reply_markup) - - async def edit_reply_markup(self, - reply_markup: typing.Union[InlineKeyboardMarkup, - None] = None) -> typing.Union[Message, base.Boolean]: - """ - Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots). - - Source: https://core.telegram.org/bots/api#editmessagereplymarkup - - :param reply_markup: A JSON-serialized object for an inline keyboard - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if edited message is sent by the bot, the edited Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - return await self.bot.edit_message_reply_markup(chat_id=self.chat.id, message_id=self.message_id, - reply_markup=reply_markup) - - async def delete_reply_markup(self) -> typing.Union[Message, base.Boolean]: - """ - Use this method to delete reply markup of messages sent by the bot or via the bot (for inline bots). - - :return: On success, if edited message is sent by the bot, the edited Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - return await self.bot.edit_message_reply_markup(chat_id=self.chat.id, message_id=self.message_id) - - async def edit_live_location(self, latitude: base.Float, - longitude: base.Float, - reply_markup: typing.Union[InlineKeyboardMarkup, - None] = None) -> typing.Union[Message, base.Boolean]: - """ - Use this method to edit live location messages sent by the bot or via the bot (for inline bots). - A location can be edited until its live_period expires or editing is explicitly disabled by a call - to stopMessageLiveLocation. - - Source: https://core.telegram.org/bots/api#editmessagelivelocation - - :param latitude: Latitude of new location - :type latitude: :obj:`base.Float` - :param longitude: Longitude of new location - :type longitude: :obj:`base.Float` - :param reply_markup: A JSON-serialized object for a new inline keyboard. - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if the edited message was sent by the bot, the edited Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - return await self.bot.edit_message_live_location(latitude=latitude, longitude=longitude, - chat_id=self.chat.id, message_id=self.message_id, - reply_markup=reply_markup) - - async def stop_live_location(self, - reply_markup: typing.Union[InlineKeyboardMarkup, - None] = None) -> typing.Union[Message, base.Boolean]: - """ - Use this method to stop updating a live location message sent by the bot or via the bot - (for inline bots) before live_period expires. - - Source: https://core.telegram.org/bots/api#stopmessagelivelocation - - :param reply_markup: A JSON-serialized object for a new inline keyboard. - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if the message was sent by the bot, the sent Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - return await self.bot.stop_message_live_location(chat_id=self.chat.id, message_id=self.message_id, - reply_markup=reply_markup) - - async def delete(self) -> base.Boolean: - """ - Use this method to delete a message, including service messages, with the following limitations: - - A message can only be deleted if it was sent less than 48 hours ago. - - Bots can delete outgoing messages in private chats, groups, and supergroups. - - Bots can delete incoming messages in private chats. - - Bots granted can_post_messages permissions can delete outgoing messages in channels. - - If the bot is an administrator of a group, it can delete any message there. - - If the bot has can_delete_messages permission in a supergroup or a channel, it can delete any message there. - - Source: https://core.telegram.org/bots/api#deletemessage - - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - return await self.bot.delete_message(self.chat.id, self.message_id) - - async def pin(self, disable_notification: typing.Union[base.Boolean, None] = None) -> base.Boolean: - """ - Use this method to pin a message in a supergroup. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Source: https://core.telegram.org/bots/api#pinchatmessage - - :param disable_notification: Pass True, if it is not necessary to send a notification to - all group members about the new pinned message - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :return: Returns True on success - :rtype: :obj:`base.Boolean` - """ - return await self.chat.pin_message(self.message_id, disable_notification) - - async def send_copy( - self: Message, - chat_id: typing.Union[str, int], - disable_notification: typing.Optional[bool] = None, - reply_to_message_id: typing.Optional[int] = None, - reply_markup: typing.Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, None] = None, - ) -> Message: - """ - Send copy of current message - - :param chat_id: - :param disable_notification: - :param reply_to_message_id: - :param reply_markup: - :return: - """ - kwargs = { - "chat_id": chat_id, - "reply_markup": reply_markup or self.reply_markup, - "parse_mode": ParseMode.HTML, - "disable_notification": disable_notification, - "reply_to_message_id": reply_to_message_id, - } - text = self.html_text if (self.text or self.caption) else None - - if self.text: - return await self.bot.send_message(text=text, **kwargs) - elif self.audio: - return await self.bot.send_audio( - audio=self.audio.file_id, - caption=text, - title=self.audio.title, - performer=self.audio.performer, - duration=self.audio.duration, - **kwargs - ) - elif self.animation: - return await self.bot.send_animation( - animation=self.animation.file_id, caption=text, **kwargs - ) - elif self.document: - return await self.bot.send_document( - document=self.document.file_id, caption=text, **kwargs - ) - elif self.photo: - return await self.bot.send_photo( - photo=self.photo[-1].file_id, caption=text, **kwargs - ) - elif self.sticker: - kwargs.pop("parse_mode") - return await self.bot.send_sticker(sticker=self.sticker.file_id, **kwargs) - elif self.video: - return await self.bot.send_video( - video=self.video.file_id, caption=text, **kwargs - ) - elif self.video_note: - kwargs.pop("parse_mode") - return await self.bot.send_video_note( - video_note=self.video_note.file_id, **kwargs - ) - elif self.voice: - return await self.bot.send_voice(voice=self.voice.file_id, **kwargs) - elif self.contact: - kwargs.pop("parse_mode") - return await self.bot.send_contact( - phone_number=self.contact.phone_number, - first_name=self.contact.first_name, - last_name=self.contact.last_name, - vcard=self.contact.vcard, - **kwargs - ) - elif self.venue: - kwargs.pop("parse_mode") - return await self.bot.send_venue( - latitude=self.venue.location.latitude, - longitude=self.venue.location.longitude, - title=self.venue.title, - address=self.venue.address, - foursquare_id=self.venue.foursquare_id, - foursquare_type=self.venue.foursquare_type, - **kwargs - ) - elif self.location: - kwargs.pop("parse_mode") - return await self.bot.send_location( - latitude=self.location.latitude, longitude=self.location.longitude, **kwargs - ) - elif self.poll: - kwargs.pop("parse_mode") - return await self.bot.send_poll( - question=self.poll.question, options=self.poll.options, **kwargs - ) - else: - raise TypeError("This type of message can't be copied.") - - def __int__(self): - return self.message_id - - -class ContentType(helper.Helper): - """ - List of message content types - - WARNING: Single elements - - :key: TEXT - :key: AUDIO - :key: DOCUMENT - :key: GAME - :key: PHOTO - :key: STICKER - :key: VIDEO - :key: VIDEO_NOTE - :key: VOICE - :key: CONTACT - :key: LOCATION - :key: VENUE - :key: NEW_CHAT_MEMBERS - :key: LEFT_CHAT_MEMBER - :key: INVOICE - :key: SUCCESSFUL_PAYMENT - :key: CONNECTED_WEBSITE - :key: MIGRATE_TO_CHAT_ID - :key: MIGRATE_FROM_CHAT_ID - :key: UNKNOWN - :key: ANY - """ - mode = helper.HelperMode.snake_case - - TEXT = helper.Item() # text - AUDIO = helper.Item() # audio - DOCUMENT = helper.Item() # document - ANIMATION = helper.Item() # animation - GAME = helper.Item() # game - PHOTO = helper.Item() # photo - STICKER = helper.Item() # sticker - VIDEO = helper.Item() # video - VIDEO_NOTE = helper.Item() # video_note - VOICE = helper.Item() # voice - CONTACT = helper.Item() # contact - LOCATION = helper.Item() # location - VENUE = helper.Item() # venue - NEW_CHAT_MEMBERS = helper.Item() # new_chat_member - LEFT_CHAT_MEMBER = helper.Item() # left_chat_member - INVOICE = helper.Item() # invoice - SUCCESSFUL_PAYMENT = helper.Item() # successful_payment - CONNECTED_WEBSITE = helper.Item() # connected_website - MIGRATE_TO_CHAT_ID = helper.Item() # migrate_to_chat_id - MIGRATE_FROM_CHAT_ID = helper.Item() # migrate_from_chat_id - PINNED_MESSAGE = helper.Item() # pinned_message - NEW_CHAT_TITLE = helper.Item() # new_chat_title - NEW_CHAT_PHOTO = helper.Item() # new_chat_photo - DELETE_CHAT_PHOTO = helper.Item() # delete_chat_photo - GROUP_CHAT_CREATED = helper.Item() # group_chat_created - PASSPORT_DATA = helper.Item() # passport_data - POLL = helper.Item() - - UNKNOWN = helper.Item() # unknown - ANY = helper.Item() # any - - -class ContentTypes(helper.Helper): - """ - List of message content types - - WARNING: List elements. - - :key: TEXT - :key: AUDIO - :key: DOCUMENT - :key: GAME - :key: PHOTO - :key: STICKER - :key: VIDEO - :key: VIDEO_NOTE - :key: VOICE - :key: CONTACT - :key: LOCATION - :key: VENUE - :key: NEW_CHAT_MEMBERS - :key: LEFT_CHAT_MEMBER - :key: INVOICE - :key: SUCCESSFUL_PAYMENT - :key: CONNECTED_WEBSITE - :key: MIGRATE_TO_CHAT_ID - :key: MIGRATE_FROM_CHAT_ID - :key: UNKNOWN - :key: ANY - """ - mode = helper.HelperMode.snake_case - - TEXT = helper.ListItem() # text - AUDIO = helper.ListItem() # audio - DOCUMENT = helper.ListItem() # document - ANIMATION = helper.ListItem() # animation - GAME = helper.ListItem() # game - PHOTO = helper.ListItem() # photo - STICKER = helper.ListItem() # sticker - VIDEO = helper.ListItem() # video - VIDEO_NOTE = helper.ListItem() # video_note - VOICE = helper.ListItem() # voice - CONTACT = helper.ListItem() # contact - LOCATION = helper.ListItem() # location - VENUE = helper.ListItem() # venue - NEW_CHAT_MEMBERS = helper.ListItem() # new_chat_member - LEFT_CHAT_MEMBER = helper.ListItem() # left_chat_member - INVOICE = helper.ListItem() # invoice - SUCCESSFUL_PAYMENT = helper.ListItem() # successful_payment - CONNECTED_WEBSITE = helper.ListItem() # connected_website - MIGRATE_TO_CHAT_ID = helper.ListItem() # migrate_to_chat_id - MIGRATE_FROM_CHAT_ID = helper.ListItem() # migrate_from_chat_id - PINNED_MESSAGE = helper.ListItem() # pinned_message - NEW_CHAT_TITLE = helper.ListItem() # new_chat_title - NEW_CHAT_PHOTO = helper.ListItem() # new_chat_photo - DELETE_CHAT_PHOTO = helper.ListItem() # delete_chat_photo - GROUP_CHAT_CREATED = helper.ListItem() # group_chat_created - PASSPORT_DATA = helper.ListItem() # passport_data - POLL = helper.ListItem() - - UNKNOWN = helper.ListItem() # unknown - ANY = helper.ListItem() # any - - -class ParseMode(helper.Helper): - """ - Parse modes - - :key: MARKDOWN - :key: HTML - """ - - mode = helper.HelperMode.lowercase - - MARKDOWN = helper.Item() - HTML = helper.Item() diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py deleted file mode 100644 index ad53da65..00000000 --- a/aiogram/types/message_entity.py +++ /dev/null @@ -1,109 +0,0 @@ -import sys - -from . import base -from . import fields -from .user import User -from ..utils import helper, markdown - - -class MessageEntity(base.TelegramObject): - """ - This object represents one special entity in a text message. For example, hashtags, usernames, URLs, etc. - - https://core.telegram.org/bots/api#messageentity - """ - - type: base.String = fields.Field() - offset: base.Integer = fields.Field() - length: base.Integer = fields.Field() - url: base.String = fields.Field() - user: User = fields.Field(base=User) - - def get_text(self, text): - """ - Get value of entity - - :param text: full text - :return: part of text - """ - if sys.maxunicode == 0xFFFF: - return text[self.offset : self.offset + self.length] - - if not isinstance(text, bytes): - entity_text = text.encode("utf-16-le") - else: - entity_text = text - - entity_text = entity_text[self.offset * 2 : (self.offset + self.length) * 2] - return entity_text.decode("utf-16-le") - - def parse(self, text, as_html=True): - """ - Get entity value with markup - - :param text: original text - :param as_html: as html? - :return: entity text with markup - """ - if not text: - return text - entity_text = self.get_text(text) - - if self.type == MessageEntityType.BOLD: - method = markdown.hbold if as_html else markdown.bold - return method(entity_text) - if self.type == MessageEntityType.ITALIC: - method = markdown.hitalic if as_html else markdown.italic - return method(entity_text) - if self.type == MessageEntityType.PRE: - method = markdown.hpre if as_html else markdown.pre - return method(entity_text) - if self.type == MessageEntityType.CODE: - method = markdown.hcode if as_html else markdown.code - return method(entity_text) - if self.type == MessageEntityType.URL: - method = markdown.hlink if as_html else markdown.link - return method(entity_text, entity_text) - if self.type == MessageEntityType.TEXT_LINK: - method = markdown.hlink if as_html else markdown.link - return method(entity_text, self.url) - if self.type == MessageEntityType.TEXT_MENTION and self.user: - return self.user.get_mention(entity_text, as_html=as_html) - - return entity_text - - -class MessageEntityType(helper.Helper): - """ - List of entity types - - :key: MENTION - :key: HASHTAG - :key: CASHTAG - :key: BOT_COMMAND - :key: URL - :key: EMAIL - :key: PHONE_NUMBER - :key: BOLD - :key: ITALIC - :key: CODE - :key: PRE - :key: TEXT_LINK - :key: TEXT_MENTION - """ - - mode = helper.HelperMode.snake_case - - MENTION = helper.Item() # mention - @username - HASHTAG = helper.Item() # hashtag - CASHTAG = helper.Item() # cashtag - BOT_COMMAND = helper.Item() # bot_command - URL = helper.Item() # url - EMAIL = helper.Item() # email - PHONE_NUMBER = helper.Item() # phone_number - BOLD = helper.Item() # bold - bold text - ITALIC = helper.Item() # italic - italic text - CODE = helper.Item() # code - monowidth string - PRE = helper.Item() # pre - monowidth block - TEXT_LINK = helper.Item() # text_link - for clickable text URLs - TEXT_MENTION = helper.Item() # text_mention - for users without usernames diff --git a/aiogram/types/mixins.py b/aiogram/types/mixins.py deleted file mode 100644 index 024cad72..00000000 --- a/aiogram/types/mixins.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -import pathlib - - -class Downloadable: - """ - Mixin for files - """ - - async def download( - self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True - ): - """ - Download file - - :param destination: filename or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO` - :param timeout: Integer - :param chunk_size: Integer - :param seek: Boolean - go to start of file when downloading is finished. - :param make_dirs: Make dirs if not exist - :return: destination - """ - file = await self.get_file() - - is_path = True - if destination is None: - destination = file.file_path - elif isinstance(destination, (str, pathlib.Path)) and os.path.isdir(destination): - destination = os.path.join(destination, file.file_path) - else: - is_path = False - - if is_path and make_dirs: - os.makedirs(os.path.dirname(destination), exist_ok=True) - - return await self.bot.download_file( - file_path=file.file_path, - destination=destination, - timeout=timeout, - chunk_size=chunk_size, - seek=seek, - ) - - async def get_file(self): - """ - Get file information - - :return: :obj:`aiogram.types.File` - """ - if hasattr(self, "file_path"): - return self - else: - return await self.bot.get_file(self.file_id) - - async def get_url(self): - """ - Get file url. - - Attention!! - This method has security vulnerabilities for the reason that result - contains bot's *access token* in open form. Use at your own risk! - - :return: url - """ - file = await self.get_file() - return self.bot.get_file_url(file.file_path) - - def __hash__(self): - return hash(self.file_id) diff --git a/aiogram/types/order_info.py b/aiogram/types/order_info.py deleted file mode 100644 index 535cdd0f..00000000 --- a/aiogram/types/order_info.py +++ /dev/null @@ -1,16 +0,0 @@ -from . import base -from . import fields -from .shipping_address import ShippingAddress - - -class OrderInfo(base.TelegramObject): - """ - This object represents information about an order. - - https://core.telegram.org/bots/api#orderinfo - """ - - name: base.String = fields.Field() - phone_number: base.String = fields.Field() - email: base.String = fields.Field() - shipping_address: ShippingAddress = fields.Field(base=ShippingAddress) diff --git a/aiogram/types/passport_data.py b/aiogram/types/passport_data.py deleted file mode 100644 index 2fed9fae..00000000 --- a/aiogram/types/passport_data.py +++ /dev/null @@ -1,17 +0,0 @@ -import typing - -from . import base -from . import fields -from .encrypted_credentials import EncryptedCredentials -from .encrypted_passport_element import EncryptedPassportElement - - -class PassportData(base.TelegramObject): - """ - Contains information about Telegram Passport data shared with the bot by the user. - - https://core.telegram.org/bots/api#passportdata - """ - - data: typing.List[EncryptedPassportElement] = fields.ListField(base=EncryptedPassportElement) - credentials: EncryptedCredentials = fields.Field(base=EncryptedCredentials) diff --git a/aiogram/types/passport_element_error.py b/aiogram/types/passport_element_error.py deleted file mode 100644 index aff60775..00000000 --- a/aiogram/types/passport_element_error.py +++ /dev/null @@ -1,135 +0,0 @@ -import typing - -from . import base -from . import fields - - -class PassportElementError(base.TelegramObject): - """ - This object represents an error in the Telegram Passport element which was submitted that - should be resolved by the user. - - https://core.telegram.org/bots/api#passportelementerror - """ - - source: base.String = fields.Field() - type: base.String = fields.Field() - message: base.String = fields.Field() - - -class PassportElementErrorDataField(PassportElementError): - """ - Represents an issue in one of the data fields that was provided by the user. - The error is considered resolved when the field's value changes. - - https://core.telegram.org/bots/api#passportelementerrordatafield - """ - - field_name: base.String = fields.Field() - data_hash: base.String = fields.Field() - - def __init__( - self, - source: base.String, - type: base.String, - field_name: base.String, - data_hash: base.String, - message: base.String, - ): - super(PassportElementErrorDataField, self).__init__( - source=source, type=type, field_name=field_name, data_hash=data_hash, message=message - ) - - -class PassportElementErrorFile(PassportElementError): - """ - Represents an issue with a document scan. - The error is considered resolved when the file with the document scan changes. - - https://core.telegram.org/bots/api#passportelementerrorfile - """ - - file_hash: base.String = fields.Field() - - def __init__( - self, source: base.String, type: base.String, file_hash: base.String, message: base.String - ): - super(PassportElementErrorFile, self).__init__( - source=source, type=type, file_hash=file_hash, message=message - ) - - -class PassportElementErrorFiles(PassportElementError): - """ - Represents an issue with a list of scans. - The error is considered resolved when the list of files containing the scans changes. - - https://core.telegram.org/bots/api#passportelementerrorfiles - """ - - file_hashes: typing.List[base.String] = fields.ListField() - - def __init__( - self, - source: base.String, - type: base.String, - file_hashes: typing.List[base.String], - message: base.String, - ): - super(PassportElementErrorFiles, self).__init__( - source=source, type=type, file_hashes=file_hashes, message=message - ) - - -class PassportElementErrorFrontSide(PassportElementError): - """ - Represents an issue with the front side of a document. - The error is considered resolved when the file with the front side of the document changes. - - https://core.telegram.org/bots/api#passportelementerrorfrontside - """ - - file_hash: base.String = fields.Field() - - def __init__( - self, source: base.String, type: base.String, file_hash: base.String, message: base.String - ): - super(PassportElementErrorFrontSide, self).__init__( - source=source, type=type, file_hash=file_hash, message=message - ) - - -class PassportElementErrorReverseSide(PassportElementError): - """ - Represents an issue with the reverse side of a document. - The error is considered resolved when the file with reverse side of the document changes. - - https://core.telegram.org/bots/api#passportelementerrorreverseside - """ - - file_hash: base.String = fields.Field() - - def __init__( - self, source: base.String, type: base.String, file_hash: base.String, message: base.String - ): - super(PassportElementErrorReverseSide, self).__init__( - source=source, type=type, file_hash=file_hash, message=message - ) - - -class PassportElementErrorSelfie(PassportElementError): - """ - Represents an issue with the selfie with a document. - The error is considered resolved when the file with the selfie changes. - - https://core.telegram.org/bots/api#passportelementerrorselfie - """ - - file_hash: base.String = fields.Field() - - def __init__( - self, source: base.String, type: base.String, file_hash: base.String, message: base.String - ): - super(PassportElementErrorSelfie, self).__init__( - source=source, type=type, file_hash=file_hash, message=message - ) diff --git a/aiogram/types/passport_file.py b/aiogram/types/passport_file.py deleted file mode 100644 index f00e80c7..00000000 --- a/aiogram/types/passport_file.py +++ /dev/null @@ -1,15 +0,0 @@ -from . import base -from . import fields - - -class PassportFile(base.TelegramObject): - """ - This object represents a file uploaded to Telegram Passport. - Currently all Telegram Passport files are in JPEG format when decrypted and don't exceed 10MB. - - https://core.telegram.org/bots/api#passportfile - """ - - file_id: base.String = fields.Field() - file_size: base.Integer = fields.Field() - file_date: base.Integer = fields.Field() diff --git a/aiogram/types/photo_size.py b/aiogram/types/photo_size.py deleted file mode 100644 index 72c8c52b..00000000 --- a/aiogram/types/photo_size.py +++ /dev/null @@ -1,16 +0,0 @@ -from . import base -from . import fields -from . import mixins - - -class PhotoSize(base.TelegramObject, mixins.Downloadable): - """ - This object represents one size of a photo or a file / sticker thumbnail. - - https://core.telegram.org/bots/api#photosize - """ - - file_id: base.String = fields.Field() - width: base.Integer = fields.Field() - height: base.Integer = fields.Field() - file_size: base.Integer = fields.Field() diff --git a/aiogram/types/poll.py b/aiogram/types/poll.py deleted file mode 100644 index 316bca2d..00000000 --- a/aiogram/types/poll.py +++ /dev/null @@ -1,16 +0,0 @@ -import typing - -from . import base -from . import fields - - -class PollOption(base.TelegramObject): - text: base.String = fields.Field() - voter_count: base.Integer = fields.Field() - - -class Poll(base.TelegramObject): - id: base.String = fields.Field() - question: base.String = fields.Field() - options: typing.List[PollOption] = fields.ListField(base=PollOption) - is_closed: base.Boolean = fields.Field() diff --git a/aiogram/types/pre_checkout_query.py b/aiogram/types/pre_checkout_query.py deleted file mode 100644 index 0da1ded8..00000000 --- a/aiogram/types/pre_checkout_query.py +++ /dev/null @@ -1,35 +0,0 @@ -from . import base -from . import fields -from .order_info import OrderInfo -from .user import User - - -class PreCheckoutQuery(base.TelegramObject): - """ - This object contains information about an incoming pre-checkout query. - Your bot can offer users HTML5 games to play solo or to compete against - each other in groups and one-on-one chats. - - Create games via @BotFather using the /newgame command. - - Please note that this kind of power requires responsibility: - you will need to accept the terms for each game that your bots will be offering. - - https://core.telegram.org/bots/api#precheckoutquery - """ - - id: base.String = fields.Field() - from_user: User = fields.Field(alias="from", base=User) - currency: base.String = fields.Field() - total_amount: base.Integer = fields.Field() - invoice_payload: base.String = fields.Field() - shipping_option_id: base.String = fields.Field() - order_info: OrderInfo = fields.Field(base=OrderInfo) - - def __hash__(self): - return self.id - - def __eq__(self, other): - if isinstance(other, type(self)): - return other.id == self.id - return self.id == other diff --git a/aiogram/types/reply_keyboard.py b/aiogram/types/reply_keyboard.py deleted file mode 100644 index 8d6a8578..00000000 --- a/aiogram/types/reply_keyboard.py +++ /dev/null @@ -1,126 +0,0 @@ -import typing - -from . import base -from . import fields - - -class ReplyKeyboardMarkup(base.TelegramObject): - """ - This object represents a custom keyboard with reply options (see Introduction to bots for details and examples). - - https://core.telegram.org/bots/api#replykeyboardmarkup - """ - - keyboard: "typing.List[typing.List[KeyboardButton]]" = fields.ListOfLists( - base="KeyboardButton", default=[] - ) - resize_keyboard: base.Boolean = fields.Field() - one_time_keyboard: base.Boolean = fields.Field() - selective: base.Boolean = fields.Field() - - def __init__( - self, - keyboard: "typing.List[typing.List[KeyboardButton]]" = None, - resize_keyboard: base.Boolean = None, - one_time_keyboard: base.Boolean = None, - selective: base.Boolean = None, - row_width: base.Integer = 3, - ): - super(ReplyKeyboardMarkup, self).__init__( - keyboard=keyboard, - resize_keyboard=resize_keyboard, - one_time_keyboard=one_time_keyboard, - selective=selective, - conf={"row_width": row_width}, - ) - - @property - def row_width(self): - return self.conf.get("row_width", 3) - - @row_width.setter - def row_width(self, value): - self.conf["row_width"] = value - - def add(self, *args): - """ - Add buttons - - :param args: - :return: self - :rtype: :obj:`types.ReplyKeyboardMarkup` - """ - row = [] - for index, button in enumerate(args, start=1): - row.append(button) - if index % self.row_width == 0: - self.keyboard.append(row) - row = [] - if len(row) > 0: - self.keyboard.append(row) - return self - - def row(self, *args): - """ - Add row - - :param args: - :return: self - :rtype: :obj:`types.ReplyKeyboardMarkup` - """ - btn_array = [] - for button in args: - btn_array.append(button) - self.keyboard.append(btn_array) - return self - - def insert(self, button): - """ - Insert button to last row - - :param button: - :return: self - :rtype: :obj:`types.ReplyKeyboardMarkup` - """ - if self.keyboard and len(self.keyboard[-1]) < self.row_width: - self.keyboard[-1].append(button) - else: - self.add(button) - return self - - -class KeyboardButton(base.TelegramObject): - """ - This object represents one button of the reply keyboard. For simple text buttons String can be used instead of this object to specify text of the button. Optional fields are mutually exclusive. - Note: request_contact and request_location options will only work in Telegram versions released after 9 April, 2016. Older clients will ignore them. - - https://core.telegram.org/bots/api#keyboardbutton - """ - - text: base.String = fields.Field() - request_contact: base.Boolean = fields.Field() - request_location: base.Boolean = fields.Field() - - def __init__( - self, - text: base.String, - request_contact: base.Boolean = None, - request_location: base.Boolean = None, - ): - super(KeyboardButton, self).__init__( - text=text, request_contact=request_contact, request_location=request_location - ) - - -class ReplyKeyboardRemove(base.TelegramObject): - """ - Upon receiving a message with this object, Telegram clients will remove the current custom keyboard and display the default letter-keyboard. By default, custom keyboards are displayed until a new keyboard is sent by a bot. An exception is made for one-time keyboards that are hidden immediately after the user presses a button (see ReplyKeyboardMarkup). - - https://core.telegram.org/bots/api#replykeyboardremove - """ - - remove_keyboard: base.Boolean = fields.Field(default=True) - selective: base.Boolean = fields.Field() - - def __init__(self, selective: base.Boolean = None): - super(ReplyKeyboardRemove, self).__init__(remove_keyboard=True, selective=selective) diff --git a/aiogram/types/response_parameters.py b/aiogram/types/response_parameters.py deleted file mode 100644 index 43a9b49d..00000000 --- a/aiogram/types/response_parameters.py +++ /dev/null @@ -1,13 +0,0 @@ -from . import base -from . import fields - - -class ResponseParameters(base.TelegramObject): - """ - Contains information about why a request was unsuccessful. - - https://core.telegram.org/bots/api#responseparameters - """ - - migrate_to_chat_id: base.Integer = fields.Field() - retry_after: base.Integer = fields.Field() diff --git a/aiogram/types/shipping_address.py b/aiogram/types/shipping_address.py deleted file mode 100644 index 48119bcf..00000000 --- a/aiogram/types/shipping_address.py +++ /dev/null @@ -1,17 +0,0 @@ -from . import base -from . import fields - - -class ShippingAddress(base.TelegramObject): - """ - This object represents a shipping address. - - https://core.telegram.org/bots/api#shippingaddress - """ - - country_code: base.String = fields.Field() - state: base.String = fields.Field() - city: base.String = fields.Field() - street_line1: base.String = fields.Field() - street_line2: base.String = fields.Field() - post_code: base.String = fields.Field() diff --git a/aiogram/types/shipping_option.py b/aiogram/types/shipping_option.py deleted file mode 100644 index 243b8163..00000000 --- a/aiogram/types/shipping_option.py +++ /dev/null @@ -1,35 +0,0 @@ -import typing - -from . import base -from . import fields -from .labeled_price import LabeledPrice - - -class ShippingOption(base.TelegramObject): - """ - This object represents one shipping option. - - https://core.telegram.org/bots/api#shippingoption - """ - - id: base.String = fields.Field() - title: base.String = fields.Field() - prices: typing.List[LabeledPrice] = fields.ListField(base=LabeledPrice) - - def __init__( - self, id: base.String, title: base.String, prices: typing.List[LabeledPrice] = None - ): - if prices is None: - prices = [] - - super(ShippingOption, self).__init__(id=id, title=title, prices=prices) - - def add(self, price: LabeledPrice): - """ - Add price - - :param price: - :return: - """ - self.prices.append(price) - return self diff --git a/aiogram/types/shipping_query.py b/aiogram/types/shipping_query.py deleted file mode 100644 index c7935c2b..00000000 --- a/aiogram/types/shipping_query.py +++ /dev/null @@ -1,25 +0,0 @@ -from . import base -from . import fields -from .shipping_address import ShippingAddress -from .user import User - - -class ShippingQuery(base.TelegramObject): - """ - This object contains information about an incoming shipping query. - - https://core.telegram.org/bots/api#shippingquery - """ - - id: base.String = fields.Field() - from_user: User = fields.Field(alias="from", base=User) - invoice_payload: base.String = fields.Field() - shipping_address: ShippingAddress = fields.Field(base=ShippingAddress) - - def __hash__(self): - return self.id - - def __eq__(self, other): - if isinstance(other, type(self)): - return other.id == self.id - return self.id == other diff --git a/aiogram/types/sticker.py b/aiogram/types/sticker.py deleted file mode 100644 index a3f8b80c..00000000 --- a/aiogram/types/sticker.py +++ /dev/null @@ -1,23 +0,0 @@ -from . import base -from . import fields -from . import mixins -from .mask_position import MaskPosition -from .photo_size import PhotoSize - - -class Sticker(base.TelegramObject, mixins.Downloadable): - """ - This object represents a sticker. - - https://core.telegram.org/bots/api#sticker - """ - - file_id: base.String = fields.Field() - width: base.Integer = fields.Field() - height: base.Integer = fields.Field() - is_animated: base.Boolean = fields.Field() - thumb: PhotoSize = fields.Field(base=PhotoSize) - emoji: base.String = fields.Field() - set_name: base.String = fields.Field() - mask_position: MaskPosition = fields.Field(base=MaskPosition) - file_size: base.Integer = fields.Field() diff --git a/aiogram/types/sticker_set.py b/aiogram/types/sticker_set.py deleted file mode 100644 index 05d2cd98..00000000 --- a/aiogram/types/sticker_set.py +++ /dev/null @@ -1,19 +0,0 @@ -import typing - -from . import base -from . import fields -from .sticker import Sticker - - -class StickerSet(base.TelegramObject): - """ - This object represents a sticker set. - - https://core.telegram.org/bots/api#stickerset - """ - - name: base.String = fields.Field() - title: base.String = fields.Field() - is_animated: base.Boolean = fields.Field() - contains_masks: base.Boolean = fields.Field() - stickers: typing.List[Sticker] = fields.ListField(base=Sticker) diff --git a/aiogram/types/successful_payment.py b/aiogram/types/successful_payment.py deleted file mode 100644 index bca329cc..00000000 --- a/aiogram/types/successful_payment.py +++ /dev/null @@ -1,19 +0,0 @@ -from . import base -from . import fields -from .order_info import OrderInfo - - -class SuccessfulPayment(base.TelegramObject): - """ - This object contains basic information about a successful payment. - - https://core.telegram.org/bots/api#successfulpayment - """ - - currency: base.String = fields.Field() - total_amount: base.Integer = fields.Field() - invoice_payload: base.String = fields.Field() - shipping_option_id: base.String = fields.Field() - order_info: OrderInfo = fields.Field(base=OrderInfo) - telegram_payment_charge_id: base.String = fields.Field() - provider_payment_charge_id: base.String = fields.Field() diff --git a/aiogram/types/update.py b/aiogram/types/update.py deleted file mode 100644 index 7f95b597..00000000 --- a/aiogram/types/update.py +++ /dev/null @@ -1,62 +0,0 @@ -from __future__ import annotations - -from . import base -from . import fields -from .callback_query import CallbackQuery -from .chosen_inline_result import ChosenInlineResult -from .inline_query import InlineQuery -from .message import Message -from .poll import Poll -from .pre_checkout_query import PreCheckoutQuery -from .shipping_query import ShippingQuery -from ..utils import helper - - -class Update(base.TelegramObject): - """ - This object represents an incoming update. - At most one of the optional parameters can be present in any given update. - - https://core.telegram.org/bots/api#update - """ - - update_id: base.Integer = fields.Field() - message: Message = fields.Field(base=Message) - edited_message: Message = fields.Field(base=Message) - channel_post: Message = fields.Field(base=Message) - edited_channel_post: Message = fields.Field(base=Message) - inline_query: InlineQuery = fields.Field(base=InlineQuery) - chosen_inline_result: ChosenInlineResult = fields.Field(base=ChosenInlineResult) - callback_query: CallbackQuery = fields.Field(base=CallbackQuery) - shipping_query: ShippingQuery = fields.Field(base=ShippingQuery) - pre_checkout_query: PreCheckoutQuery = fields.Field(base=PreCheckoutQuery) - poll: Poll = fields.Field(base=Poll) - - def __hash__(self): - return self.update_id - - def __int__(self): - return self.update_id - - -class AllowedUpdates(helper.Helper): - """ - Helper for allowed_updates parameter in getUpdates and setWebhook methods. - - You can use &, + or | operators for make combination of allowed updates. - - Example: - >>> bot.get_updates(allowed_updates=AllowedUpdates.MESSAGE + AllowedUpdates.EDITED_MESSAGE) - """ - - mode = helper.HelperMode.snake_case - - MESSAGE = helper.ListItem() # message - EDITED_MESSAGE = helper.ListItem() # edited_message - CHANNEL_POST = helper.ListItem() # channel_post - EDITED_CHANNEL_POST = helper.ListItem() # edited_channel_post - INLINE_QUERY = helper.ListItem() # inline_query - CHOSEN_INLINE_QUERY = helper.ListItem() # chosen_inline_result - CALLBACK_QUERY = helper.ListItem() # callback_query - SHIPPING_QUERY = helper.ListItem() # shipping_query - PRE_CHECKOUT_QUERY = helper.ListItem() # pre_checkout_query diff --git a/aiogram/types/user.py b/aiogram/types/user.py deleted file mode 100644 index 05ff9401..00000000 --- a/aiogram/types/user.py +++ /dev/null @@ -1,84 +0,0 @@ -from __future__ import annotations - -from typing import Optional - -import babel - -from . import base -from . import fields -from ..utils import markdown - - -class User(base.TelegramObject): - """ - This object represents a Telegram user or bot. - - https://core.telegram.org/bots/api#user - """ - - id: base.Integer = fields.Field() - is_bot: base.Boolean = fields.Field() - first_name: base.String = fields.Field() - last_name: base.String = fields.Field() - username: base.String = fields.Field() - language_code: base.String = fields.Field() - - @property - def full_name(self): - """ - You can get full name of user. - - :return: str - """ - full_name = self.first_name - if self.last_name: - full_name += " " + self.last_name - return full_name - - @property - def mention(self): - """ - You can get user's username to mention him - Full name will be returned if user has no username - - :return: str - """ - if self.username: - return "@" + self.username - return self.full_name - - @property - def locale(self) -> Optional[babel.core.Locale]: - """ - Get user's locale - - :return: :class:`babel.core.Locale` - """ - if not self.language_code: - return None - if not hasattr(self, "_locale"): - setattr(self, "_locale", babel.core.Locale.parse(self.language_code, sep="-")) - return getattr(self, "_locale") - - @property - def url(self): - return f"tg://user?id={self.id}" - - def get_mention(self, name=None, as_html=None): - if as_html is None and self.bot.parse_mode and self.bot.parse_mode.lower() == "html": - as_html = True - - if name is None: - name = self.full_name - if as_html: - return markdown.hlink(name, self.url) - return markdown.link(name, self.url) - - async def get_user_profile_photos(self, offset=None, limit=None): - return await self.bot.get_user_profile_photos(self.id, offset, limit) - - def __hash__(self): - return self.id - - def __int__(self): - return self.id diff --git a/aiogram/types/user_profile_photos.py b/aiogram/types/user_profile_photos.py deleted file mode 100644 index 21304da3..00000000 --- a/aiogram/types/user_profile_photos.py +++ /dev/null @@ -1,16 +0,0 @@ -import typing - -from . import base -from . import fields -from .photo_size import PhotoSize - - -class UserProfilePhotos(base.TelegramObject): - """ - This object represent a user's profile pictures. - - https://core.telegram.org/bots/api#userprofilephotos - """ - - total_count: base.Integer = fields.Field() - photos: typing.List[typing.List[PhotoSize]] = fields.ListOfLists(base=PhotoSize) diff --git a/aiogram/types/venue.py b/aiogram/types/venue.py deleted file mode 100644 index 1335f0ad..00000000 --- a/aiogram/types/venue.py +++ /dev/null @@ -1,17 +0,0 @@ -from . import base -from . import fields -from .location import Location - - -class Venue(base.TelegramObject): - """ - This object represents a venue. - - https://core.telegram.org/bots/api#venue - """ - - location: Location = fields.Field(base=Location) - title: base.String = fields.Field() - address: base.String = fields.Field() - foursquare_id: base.String = fields.Field() - foursquare_type: base.String = fields.Field() diff --git a/aiogram/types/video.py b/aiogram/types/video.py deleted file mode 100644 index 996eb55f..00000000 --- a/aiogram/types/video.py +++ /dev/null @@ -1,20 +0,0 @@ -from . import base -from . import fields -from . import mixins -from .photo_size import PhotoSize - - -class Video(base.TelegramObject, mixins.Downloadable): - """ - This object represents a video file. - - https://core.telegram.org/bots/api#video - """ - - file_id: base.String = fields.Field() - width: base.Integer = fields.Field() - height: base.Integer = fields.Field() - duration: base.Integer = fields.Field() - thumb: PhotoSize = fields.Field(base=PhotoSize) - mime_type: base.String = fields.Field() - file_size: base.Integer = fields.Field() diff --git a/aiogram/types/video_note.py b/aiogram/types/video_note.py deleted file mode 100644 index ea7bd5c1..00000000 --- a/aiogram/types/video_note.py +++ /dev/null @@ -1,18 +0,0 @@ -from . import base -from . import fields -from . import mixins -from .photo_size import PhotoSize - - -class VideoNote(base.TelegramObject, mixins.Downloadable): - """ - This object represents a video message (available in Telegram apps as of v.4.0). - - https://core.telegram.org/bots/api#videonote - """ - - file_id: base.String = fields.Field() - length: base.Integer = fields.Field() - duration: base.Integer = fields.Field() - thumb: PhotoSize = fields.Field(base=PhotoSize) - file_size: base.Integer = fields.Field() diff --git a/aiogram/types/voice.py b/aiogram/types/voice.py deleted file mode 100644 index df2a56a4..00000000 --- a/aiogram/types/voice.py +++ /dev/null @@ -1,16 +0,0 @@ -from . import base -from . import fields -from . import mixins - - -class Voice(base.TelegramObject, mixins.Downloadable): - """ - This object represents a voice note. - - https://core.telegram.org/bots/api#voice - """ - - file_id: base.String = fields.Field() - duration: base.Integer = fields.Field() - mime_type: base.String = fields.Field() - file_size: base.Integer = fields.Field() diff --git a/aiogram/types/webhook_info.py b/aiogram/types/webhook_info.py deleted file mode 100644 index 82424358..00000000 --- a/aiogram/types/webhook_info.py +++ /dev/null @@ -1,20 +0,0 @@ -import typing - -from . import base -from . import fields - - -class WebhookInfo(base.TelegramObject): - """ - Contains information about the current status of a webhook. - - https://core.telegram.org/bots/api#webhookinfo - """ - - url: base.String = fields.Field() - has_custom_certificate: base.Boolean = fields.Field() - pending_update_count: base.Integer = fields.Field() - last_error_date: base.Integer = fields.Field() - last_error_message: base.String = fields.Field() - max_connections: base.Integer = fields.Field() - allowed_updates: typing.List[base.String] = fields.ListField() diff --git a/aiogram/utils/__init__.py b/aiogram/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/aiogram/utils/auth_widget.py b/aiogram/utils/auth_widget.py deleted file mode 100644 index e612c2f1..00000000 --- a/aiogram/utils/auth_widget.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Implementation of Telegram site authorization checking mechanism -for more information https://core.telegram.org/widgets/login#checking-authorization - -Source: https://gist.github.com/JrooTJunior/887791de7273c9df5277d2b1ecadc839 -""" -import collections -import hashlib -import hmac - -from aiogram.utils.deprecated import deprecated - - -@deprecated('`generate_hash` is outdated, please use `check_signature` or `check_integrity`', stacklevel=3) -def generate_hash(data: dict, token: str) -> str: - """ - Generate secret hash - - :param data: - :param token: - :return: - """ - secret = hashlib.sha256() - secret.update(token.encode("utf-8")) - sorted_params = collections.OrderedDict(sorted(data.items())) - msg = "\n".join("{}={}".format(k, v) for k, v in sorted_params.items() if k != "hash") - return hmac.new(secret.digest(), msg.encode("utf-8"), digestmod=hashlib.sha256).hexdigest() - - -@deprecated('`check_token` helper was renamed to `check_integrity`', stacklevel=3) -def check_token(data: dict, token: str) -> bool: - """ - Validate auth token - - :param data: - :param token: - :return: - """ - param_hash = data.get("hash", "") or "" - return param_hash == generate_hash(data, token) - - -def check_signature(token: str, hash: str, **kwargs) -> bool: - """ - Generate hexadecimal representation - of the HMAC-SHA-256 signature of the data-check-string - with the SHA256 hash of the bot's token used as a secret key - - :param token: - :param hash: - :param kwargs: all params received on auth - :return: - """ - secret = hashlib.sha256(token.encode('utf-8')) - check_string = '\n'.join(map(lambda k: f'{k}={kwargs[k]}', sorted(kwargs))) - hmac_string = hmac.new(secret.digest(), check_string.encode('utf-8'), digestmod=hashlib.sha256).hexdigest() - return hmac_string == hash - - -def check_integrity(token: str, data: dict) -> bool: - """ - Verify the authentication and the integrity - of the data received on user's auth - - :param token: Bot's token - :param data: all data that came on auth - :return: - """ - return check_signature(token, **data) diff --git a/aiogram/utils/callback_data.py b/aiogram/utils/callback_data.py deleted file mode 100644 index b0162a7e..00000000 --- a/aiogram/utils/callback_data.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -Callback data factory - -Usage: - Create instance of factory with prefix and element names: - >>> posts_query = CallbackData('post', 'post_id', 'action') - - Then you can generate callback data: - >>> posts_query.new('32feff9b-92fa-48d9-9d29-621dc713743a', action='view') - <<< post:32feff9b-92fa-48d9-9d29-621dc713743a:view - - Also you can generate filters: - >>> posts_query.filter(action='delete') - This filter can handle callback data by pattern: post:*:delete -""" -from __future__ import annotations - -import typing - -from aiogram import types -from aiogram.dispatcher.filters import Filter - - -class CallbackData: - """ - Callback data factory - """ - - def __init__(self, prefix, *parts, sep=':'): - if not isinstance(prefix, str): - raise TypeError(f'Prefix must be instance of str not {type(prefix).__name__}') - if not prefix: - raise ValueError("Prefix can't be empty") - if sep in prefix: - raise ValueError(f"Separator {sep!r} can't be used in prefix") - if not parts: - raise TypeError('Parts were not passed!') - - self.prefix = prefix - self.sep = sep - - self._part_names = parts - - def new(self, *args, **kwargs) -> str: - """ - Generate callback data - - :param args: - :param kwargs: - :return: - """ - args = list(args) - - data = [self.prefix] - - for part in self._part_names: - value = kwargs.pop(part, None) - if value is None: - if args: - value = args.pop(0) - else: - raise ValueError(f'Value for {part!r} was not passed!') - - if value is not None and not isinstance(value, str): - value = str(value) - - if not value: - raise ValueError(f"Value for part {part!r} can't be empty!'") - if self.sep in value: - raise ValueError(f"Symbol {self.sep!r} is defined as the separator and can't be used in parts' values") - - data.append(value) - - if args or kwargs: - raise TypeError('Too many arguments were passed!') - - callback_data = self.sep.join(data) - if len(callback_data) > 64: - raise ValueError('Resulted callback data is too long!') - - return callback_data - - def parse(self, callback_data: str) -> typing.Dict[str, str]: - """ - Parse data from the callback data - - :param callback_data: - :return: - """ - prefix, *parts = callback_data.split(self.sep) - if prefix != self.prefix: - raise ValueError("Passed callback data can't be parsed with that prefix.") - elif len(parts) != len(self._part_names): - raise ValueError('Invalid parts count!') - - result = {'@': prefix} - result.update(zip(self._part_names, parts)) - return result - - def filter(self, **config) -> CallbackDataFilter: - """ - Generate filter - - :param config: - :return: - """ - for key in config.keys(): - if key not in self._part_names: - raise ValueError(f'Invalid field name {key!r}') - return CallbackDataFilter(self, config) - - -class CallbackDataFilter(Filter): - - def __init__(self, factory: CallbackData, config: typing.Dict[str, str]): - self.config = config - self.factory = factory - - @classmethod - def validate(cls, full_config: typing.Dict[str, typing.Any]): - raise ValueError("That filter can't be used in filters factory!") - - async def check(self, query: types.CallbackQuery): - try: - data = self.factory.parse(query.data) - except ValueError: - return False - - for key, value in self.config.items(): - if isinstance(value, (list, tuple, set, frozenset)): - if data.get(key) not in value: - return False - else: - if data.get(key) != value: - return False - return {'callback_data': data} diff --git a/aiogram/utils/deprecated.py b/aiogram/utils/deprecated.py index cb22c506..0b35eb99 100644 --- a/aiogram/utils/deprecated.py +++ b/aiogram/utils/deprecated.py @@ -1,7 +1,7 @@ import asyncio +import functools import inspect import warnings -import functools from typing import Callable @@ -33,8 +33,10 @@ def deprecated(reason, stacklevel=2) -> Callable: @functools.wraps(func) def wrapper(*args, **kwargs): - warn_deprecated(msg.format(name=func.__name__, reason=reason), stacklevel=stacklevel) - warnings.simplefilter('default', DeprecationWarning) + warn_deprecated( + msg.format(name=func.__name__, reason=reason), stacklevel=stacklevel + ) + warnings.simplefilter("default", DeprecationWarning) return func(*args, **kwargs) return wrapper @@ -69,9 +71,9 @@ def deprecated(reason, stacklevel=2) -> Callable: def warn_deprecated(message, warning=DeprecationWarning, stacklevel=2): - warnings.simplefilter('always', warning) + warnings.simplefilter("always", warning) warnings.warn(message, category=warning, stacklevel=stacklevel) - warnings.simplefilter('default', warning) + warnings.simplefilter("default", warning) def renamed_argument(old_name: str, new_name: str, until_version: str, stacklevel: int = 3): @@ -100,33 +102,32 @@ def renamed_argument(old_name: str, new_name: str, until_version: str, stackleve def decorator(func): if asyncio.iscoroutinefunction(func): + @functools.wraps(func) async def wrapped(*args, **kwargs): if old_name in kwargs: - warn_deprecated(f"In coroutine '{func.__name__}' argument '{old_name}' " - f"is renamed to '{new_name}' " - f"and will be removed in aiogram {until_version}", - stacklevel=stacklevel) - kwargs.update( - { - new_name: kwargs[old_name], - } + warn_deprecated( + f"In coroutine '{func.__name__}' argument '{old_name}' " + f"is renamed to '{new_name}' " + f"and will be removed in aiogram {until_version}", + stacklevel=stacklevel, ) + kwargs.update({new_name: kwargs[old_name]}) kwargs.pop(old_name) return await func(*args, **kwargs) + else: + @functools.wraps(func) def wrapped(*args, **kwargs): if old_name in kwargs: - warn_deprecated(f"In function `{func.__name__}` argument `{old_name}` " - f"is renamed to `{new_name}` " - f"and will be removed in aiogram {until_version}", - stacklevel=stacklevel) - kwargs.update( - { - new_name: kwargs[old_name], - } + warn_deprecated( + f"In function `{func.__name__}` argument `{old_name}` " + f"is renamed to `{new_name}` " + f"and will be removed in aiogram {until_version}", + stacklevel=stacklevel, ) + kwargs.update({new_name: kwargs[old_name]}) kwargs.pop(old_name) return func(*args, **kwargs) diff --git a/aiogram/utils/emoji.py b/aiogram/utils/emoji.py deleted file mode 100644 index 07faff56..00000000 --- a/aiogram/utils/emoji.py +++ /dev/null @@ -1,12 +0,0 @@ -try: - import emoji -except ImportError: - raise ImportError('Need install "emoji" module.') - - -def emojize(text): - return emoji.emojize(text, use_aliases=True) - - -def demojize(text): - return emoji.demojize(text) diff --git a/aiogram/utils/exceptions.py b/aiogram/utils/exceptions.py index f77fe257..b164be4d 100644 --- a/aiogram/utils/exceptions.py +++ b/aiogram/utils/exceptions.py @@ -88,13 +88,13 @@ import time # TODO: aiogram.utils.exceptions.BadRequest: Bad request: can't parse entities: unsupported start tag "function" at byte offset 0 # TODO: aiogram.utils.exceptions.TelegramAPIError: Gateway Timeout -_PREFIXES = ['error: ', '[error]: ', 'bad request: ', 'conflict: ', 'not found: '] +_PREFIXES = ["error: ", "[error]: ", "bad request: ", "conflict: ", "not found: "] def _clean_message(text): for prefix in _PREFIXES: if text.startswith(prefix): - text = text[len(prefix):] + text = text[len(prefix) :] return (text[0].upper() + text[1:]).strip() @@ -104,7 +104,7 @@ class TelegramAPIError(Exception): class _MatchErrorMixin: - match = '' + match = "" text = None __subclasses = [] @@ -164,67 +164,72 @@ class MessageNotModified(MessageError): """ Will be raised when you try to set new text is equals to current text. """ - match = 'message is not modified' + + match = "message is not modified" class MessageToForwardNotFound(MessageError): """ Will be raised when you try to forward very old or deleted or unknown message. """ - match = 'message to forward not found' + + match = "message to forward not found" class MessageToDeleteNotFound(MessageError): """ Will be raised when you try to delete very old or deleted or unknown message. """ - match = 'message to delete not found' + + match = "message to delete not found" class MessageToReplyNotFound(MessageError): """ Will be raised when you try to reply to very old or deleted or unknown message. """ - match = 'message to reply not found' + + match = "message to reply not found" class MessageIdentifierNotSpecified(MessageError): - match = 'message identifier is not specified' + match = "message identifier is not specified" class MessageTextIsEmpty(MessageError): - match = 'Message text is empty' + match = "Message text is empty" class MessageCantBeEdited(MessageError): - match = 'message can\'t be edited' + match = "message can't be edited" class MessageCantBeDeleted(MessageError): - match = 'message can\'t be deleted' + match = "message can't be deleted" class MessageToEditNotFound(MessageError): - match = 'message to edit not found' + match = "message to edit not found" class MessageIsTooLong(MessageError): - match = 'message is too long' + match = "message is too long" class ToMuchMessages(MessageError): """ Will be raised when you try to send media group with more than 10 items. """ - match = 'Too much messages to send as an album' + + match = "Too much messages to send as an album" class ObjectExpectedAsReplyMarkup(BadRequest): - match = 'object expected as reply markup' + match = "object expected as reply markup" class InlineKeyboardExpected(BadRequest): - match = 'inline keyboard expected' + match = "inline keyboard expected" class PollError(BadRequest): @@ -236,7 +241,7 @@ class PollCantBeStopped(PollError): class PollHasAlreadyBeenClosed(PollError): - match = 'poll has already been closed' + match = "poll has already been closed" class PollsCantBeSentToPrivateChats(PollError): @@ -275,109 +280,112 @@ class MessageWithPollNotFound(PollError, MessageError): """ Will be raised when you try to stop poll with message without poll """ - match = 'message with poll to stop not found' + + match = "message with poll to stop not found" class MessageIsNotAPoll(PollError, MessageError): """ Will be raised when you try to stop poll with message without poll """ - match = 'message is not a poll' + + match = "message is not a poll" class ChatNotFound(BadRequest): - match = 'chat not found' + match = "chat not found" class ChatIdIsEmpty(BadRequest): - match = 'chat_id is empty' + match = "chat_id is empty" class InvalidUserId(BadRequest): - match = 'user_id_invalid' - text = 'Invalid user id' + match = "user_id_invalid" + text = "Invalid user id" class ChatDescriptionIsNotModified(BadRequest): - match = 'chat description is not modified' + match = "chat description is not modified" class InvalidQueryID(BadRequest): - match = 'query is too old and response timeout expired or query id is invalid' + match = "query is too old and response timeout expired or query id is invalid" class InvalidPeerID(BadRequest): - match = 'PEER_ID_INVALID' - text = 'Invalid peer ID' + match = "PEER_ID_INVALID" + text = "Invalid peer ID" class InvalidHTTPUrlContent(BadRequest): - match = 'Failed to get HTTP URL content' + match = "Failed to get HTTP URL content" class ButtonURLInvalid(BadRequest): - match = 'BUTTON_URL_INVALID' - text = 'Button URL invalid' + match = "BUTTON_URL_INVALID" + text = "Button URL invalid" class URLHostIsEmpty(BadRequest): - match = 'URL host is empty' + match = "URL host is empty" class StartParamInvalid(BadRequest): - match = 'START_PARAM_INVALID' - text = 'Start param invalid' + match = "START_PARAM_INVALID" + text = "Start param invalid" class ButtonDataInvalid(BadRequest): - match = 'BUTTON_DATA_INVALID' - text = 'Button data invalid' + match = "BUTTON_DATA_INVALID" + text = "Button data invalid" class WrongFileIdentifier(BadRequest): - match = 'wrong file identifier/HTTP URL specified' + match = "wrong file identifier/HTTP URL specified" class GroupDeactivated(BadRequest): - match = 'group is deactivated' + match = "group is deactivated" class PhotoAsInputFileRequired(BadRequest): """ Will be raised when you try to set chat photo from file ID. """ - match = 'Photo should be uploaded as an InputFile' + + match = "Photo should be uploaded as an InputFile" class InvalidStickersSet(BadRequest): - match = 'STICKERSET_INVALID' - text = 'Stickers set is invalid' + match = "STICKERSET_INVALID" + text = "Stickers set is invalid" class NoStickerInRequest(BadRequest): - match = 'there is no sticker in the request' + match = "there is no sticker in the request" class ChatAdminRequired(BadRequest): - match = 'CHAT_ADMIN_REQUIRED' - text = 'Admin permissions is required!' + match = "CHAT_ADMIN_REQUIRED" + text = "Admin permissions is required!" class NeedAdministratorRightsInTheChannel(BadRequest): - match = 'need administrator rights in the channel chat' - text = 'Admin permissions is required!' + match = "need administrator rights in the channel chat" + text = "Admin permissions is required!" class NotEnoughRightsToPinMessage(BadRequest): - match = 'not enough rights to pin a message' + match = "not enough rights to pin a message" class MethodNotAvailableInPrivateChats(BadRequest): - match = 'method is available only for supergroups and channel' + match = "method is available only for supergroups and channel" class CantDemoteChatCreator(BadRequest): - match = 'can\'t demote chat creator' + match = "can't demote chat creator" class CantRestrictSelf(BadRequest): @@ -386,34 +394,34 @@ class CantRestrictSelf(BadRequest): class NotEnoughRightsToRestrict(BadRequest): - match = 'not enough rights to restrict/unrestrict chat member' + match = "not enough rights to restrict/unrestrict chat member" class PhotoDimensions(BadRequest): - match = 'PHOTO_INVALID_DIMENSIONS' - text = 'Invalid photo dimensions' + match = "PHOTO_INVALID_DIMENSIONS" + text = "Invalid photo dimensions" class UnavailableMembers(BadRequest): - match = 'supergroup members are unavailable' + match = "supergroup members are unavailable" class TypeOfFileMismatch(BadRequest): - match = 'type of file mismatch' + match = "type of file mismatch" class WrongRemoteFileIdSpecified(BadRequest): - match = 'wrong remote file id specified' + match = "wrong remote file id specified" class PaymentProviderInvalid(BadRequest): - match = 'PAYMENT_PROVIDER_INVALID' - text = 'payment provider invalid' + match = "PAYMENT_PROVIDER_INVALID" + text = "payment provider invalid" class CurrencyTotalAmountInvalid(BadRequest): - match = 'currency_total_amount_invalid' - text = 'currency total amount invalid' + match = "currency_total_amount_invalid" + text = "currency total amount invalid" class BadWebhook(BadRequest): @@ -421,44 +429,44 @@ class BadWebhook(BadRequest): class WebhookRequireHTTPS(BadWebhook): - match = 'HTTPS url must be provided for webhook' - text = 'bad webhook: ' + match + match = "HTTPS url must be provided for webhook" + text = "bad webhook: " + match class BadWebhookPort(BadWebhook): - match = 'Webhook can be set up only on ports 80, 88, 443 or 8443' - text = 'bad webhook: ' + match + match = "Webhook can be set up only on ports 80, 88, 443 or 8443" + text = "bad webhook: " + match class BadWebhookAddrInfo(BadWebhook): - match = 'getaddrinfo: Temporary failure in name resolution' - text = 'bad webhook: ' + match + match = "getaddrinfo: Temporary failure in name resolution" + text = "bad webhook: " + match class BadWebhookNoAddressAssociatedWithHostname(BadWebhook): - match = 'failed to resolve host: no address associated with hostname' + match = "failed to resolve host: no address associated with hostname" class CantParseUrl(BadRequest): - match = 'can\'t parse URL' + match = "can't parse URL" class UnsupportedUrlProtocol(BadRequest): - match = 'unsupported URL protocol' + match = "unsupported URL protocol" class CantParseEntities(BadRequest): - match = 'can\'t parse entities' + match = "can't parse entities" class ResultIdDuplicate(BadRequest): - match = 'result_id_duplicate' - text = 'Result ID duplicate' + match = "result_id_duplicate" + text = "Result ID duplicate" class BotDomainInvalid(BadRequest): - match = 'bot_domain_invalid' - text = 'Invalid bot domain' + match = "bot_domain_invalid" + text = "Invalid bot domain" class NotFound(TelegramAPIError, _MatchErrorMixin): @@ -466,7 +474,7 @@ class NotFound(TelegramAPIError, _MatchErrorMixin): class MethodNotKnown(NotFound): - match = 'method not found' + match = "method not found" class ConflictError(TelegramAPIError, _MatchErrorMixin): @@ -474,13 +482,15 @@ class ConflictError(TelegramAPIError, _MatchErrorMixin): class TerminatedByOtherGetUpdates(ConflictError): - match = 'terminated by other getUpdates request' - text = 'Terminated by other getUpdates request; ' \ - 'Make sure that only one bot instance is running' + match = "terminated by other getUpdates request" + text = ( + "Terminated by other getUpdates request; " + "Make sure that only one bot instance is running" + ) class CantGetUpdates(ConflictError): - match = 'can\'t use getUpdates method while webhook is active' + match = "can't use getUpdates method while webhook is active" class Unauthorized(TelegramAPIError, _MatchErrorMixin): @@ -488,23 +498,23 @@ class Unauthorized(TelegramAPIError, _MatchErrorMixin): class BotKicked(Unauthorized): - match = 'bot was kicked from a chat' + match = "bot was kicked from a chat" class BotBlocked(Unauthorized): - match = 'bot was blocked by the user' + match = "bot was blocked by the user" class UserDeactivated(Unauthorized): - match = 'user is deactivated' + match = "user is deactivated" class CantInitiateConversation(Unauthorized): - match = 'bot can\'t initiate conversation with a user' + match = "bot can't initiate conversation with a user" class CantTalkWithBots(Unauthorized): - match = 'bot can\'t send messages to bots' + match = "bot can't send messages to bots" class NetworkError(TelegramAPIError): @@ -513,34 +523,43 @@ class NetworkError(TelegramAPIError): class RestartingTelegram(TelegramAPIError): def __init__(self): - super(RestartingTelegram, self).__init__('The Telegram Bot API service is restarting. Wait few second.') + super(RestartingTelegram, self).__init__( + "The Telegram Bot API service is restarting. Wait few second." + ) class RetryAfter(TelegramAPIError): def __init__(self, retry_after): - super(RetryAfter, self).__init__(f"Flood control exceeded. Retry in {retry_after} seconds.") + super(RetryAfter, self).__init__( + f"Flood control exceeded. Retry in {retry_after} seconds." + ) self.timeout = retry_after class MigrateToChat(TelegramAPIError): def __init__(self, chat_id): - super(MigrateToChat, self).__init__(f"The group has been migrated to a supergroup. New id: {chat_id}.") + super(MigrateToChat, self).__init__( + f"The group has been migrated to a supergroup. New id: {chat_id}." + ) self.migrate_to_chat_id = chat_id class Throttled(TelegramAPIError): def __init__(self, **kwargs): from ..dispatcher.storage import DELTA, EXCEEDED_COUNT, KEY, LAST_CALL, RATE_LIMIT, RESULT - self.key = kwargs.pop(KEY, '') + + self.key = kwargs.pop(KEY, "") self.called_at = kwargs.pop(LAST_CALL, time.time()) self.rate = kwargs.pop(RATE_LIMIT, None) self.result = kwargs.pop(RESULT, False) self.exceeded_count = kwargs.pop(EXCEEDED_COUNT, 0) self.delta = kwargs.pop(DELTA, 0) - self.user = kwargs.pop('user', None) - self.chat = kwargs.pop('chat', None) + self.user = kwargs.pop("user", None) + self.chat = kwargs.pop("chat", None) def __str__(self): - return f"Rate limit exceeded! (Limit: {self.rate} s, " \ - f"exceeded: {self.exceeded_count}, " \ + return ( + f"Rate limit exceeded! (Limit: {self.rate} s, " + f"exceeded: {self.exceeded_count}, " f"time delta: {round(self.delta, 3)} s)" + ) diff --git a/aiogram/utils/executor.py b/aiogram/utils/executor.py deleted file mode 100644 index fe3483f6..00000000 --- a/aiogram/utils/executor.py +++ /dev/null @@ -1,384 +0,0 @@ -import asyncio -import datetime -import functools -import secrets -from typing import Callable, Union, Optional, Any -from warnings import warn - -from aiohttp import web -from aiohttp.web_app import Application - -from ..bot.api import log -from ..dispatcher.dispatcher import Dispatcher -from ..dispatcher.webhook import BOT_DISPATCHER_KEY, DEFAULT_ROUTE_NAME, WebhookRequestHandler - -APP_EXECUTOR_KEY = 'APP_EXECUTOR' - - -def _setup_callbacks(executor: 'Executor', on_startup=None, on_shutdown=None): - if on_startup is not None: - executor.on_startup(on_startup) - if on_shutdown is not None: - executor.on_shutdown(on_shutdown) - - -def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=True, - on_startup=None, on_shutdown=None, timeout=20, relax=0.1, fast=True): - """ - Start bot in long-polling mode - - :param dispatcher: - :param loop: - :param skip_updates: - :param reset_webhook: - :param on_startup: - :param on_shutdown: - :param timeout: - """ - executor = Executor(dispatcher, skip_updates=skip_updates, loop=loop) - _setup_callbacks(executor, on_startup, on_shutdown) - - executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, relax=relax, fast=fast) - - -def set_webhook(dispatcher: Dispatcher, webhook_path: str, *, loop: Optional[asyncio.AbstractEventLoop] = None, - skip_updates: bool = None, on_startup: Optional[Callable] = None, - on_shutdown: Optional[Callable] = None, check_ip: bool = False, - retry_after: Optional[Union[str, int]] = None, route_name: str = DEFAULT_ROUTE_NAME, - web_app: Optional[Application] = None): - """ - Set webhook for bot - - :param dispatcher: Dispatcher - :param webhook_path: str - :param loop: Optional[asyncio.AbstractEventLoop] (default: None) - :param skip_updates: bool (default: None) - :param on_startup: Optional[Callable] (default: None) - :param on_shutdown: Optional[Callable] (default: None) - :param check_ip: bool (default: False) - :param retry_after: Optional[Union[str, int]] See https://tools.ietf.org/html/rfc7231#section-7.1.3 (default: None) - :param route_name: str (default: 'webhook_handler') - :param web_app: Optional[Application] (default: None) - :return: - """ - executor = Executor(dispatcher, skip_updates=skip_updates, check_ip=check_ip, retry_after=retry_after, - loop=loop) - _setup_callbacks(executor, on_startup, on_shutdown) - - executor.set_webhook(webhook_path, route_name=route_name, web_app=web_app) - return executor - - -def start_webhook(dispatcher, webhook_path, *, loop=None, skip_updates=None, - on_startup=None, on_shutdown=None, check_ip=False, retry_after=None, route_name=DEFAULT_ROUTE_NAME, - **kwargs): - """ - Start bot in webhook mode - - :param dispatcher: - :param webhook_path: - :param loop: - :param skip_updates: - :param on_startup: - :param on_shutdown: - :param check_ip: - :param route_name: - :param kwargs: - :return: - """ - executor = set_webhook(dispatcher=dispatcher, - webhook_path=webhook_path, - loop=loop, - skip_updates=skip_updates, - on_startup=on_startup, - on_shutdown=on_shutdown, - check_ip=check_ip, - retry_after=retry_after, - route_name=route_name) - executor.run_app(**kwargs) - - -def start(dispatcher, future, *, loop=None, skip_updates=None, - on_startup=None, on_shutdown=None): - """ - Execute Future. - - :param dispatcher: instance of Dispatcher - :param future: future - :param loop: instance of AbstractEventLoop - :param skip_updates: - :param on_startup: - :param on_shutdown: - :return: - """ - executor = Executor(dispatcher, skip_updates=skip_updates, loop=loop) - _setup_callbacks(executor, on_startup, on_shutdown) - - return executor.start(future) - - -class Executor: - """ - Main executor class - """ - - def __init__(self, dispatcher, skip_updates=None, check_ip=False, retry_after=None, loop=None): - if loop is None: - loop = dispatcher.loop - self.dispatcher = dispatcher - self.skip_updates = skip_updates - self.check_ip = check_ip - self.retry_after = retry_after - self.loop = loop - - self._identity = secrets.token_urlsafe(16) - self._web_app = None - - self._on_startup_webhook = [] - self._on_startup_polling = [] - self._on_shutdown_webhook = [] - self._on_shutdown_polling = [] - - self._freeze = False - - from aiogram import Bot, Dispatcher - Bot.set_current(dispatcher.bot) - Dispatcher.set_current(dispatcher) - - @property - def frozen(self): - return self._freeze - - def set_web_app(self, application: web.Application): - """ - Change instance of aiohttp.web.Applicaton - - :param application: - """ - self._web_app = application - - @property - def web_app(self) -> web.Application: - if self._web_app is None: - raise RuntimeError('web.Application() is not configured!') - return self._web_app - - def on_startup(self, callback: callable, polling=True, webhook=True): - """ - Register a callback for the startup process - - :param callback: - :param polling: use with polling - :param webhook: use with webhook - """ - self._check_frozen() - if not webhook and not polling: - warn('This action has no effect!', UserWarning) - return - - if isinstance(callback, (list, tuple, set)): - for cb in callback: - self.on_startup(cb, polling, webhook) - return - - if polling: - self._on_startup_polling.append(callback) - if webhook: - self._on_startup_webhook.append(callback) - - def on_shutdown(self, callback: callable, polling=True, webhook=True): - """ - Register a callback for the shutdown process - - :param callback: - :param polling: use with polling - :param webhook: use with webhook - """ - self._check_frozen() - if not webhook and not polling: - warn('This action has no effect!', UserWarning) - return - - if isinstance(callback, (list, tuple, set)): - for cb in callback: - self.on_shutdown(cb, polling, webhook) - return - - if polling: - self._on_shutdown_polling.append(callback) - if webhook: - self._on_shutdown_webhook.append(callback) - - def _check_frozen(self): - if self.frozen: - raise RuntimeError('Executor is frozen!') - - def _prepare_polling(self): - self._check_frozen() - self._freeze = True - - # self.loop.set_task_factory(context.task_factory) - - def _prepare_webhook(self, path=None, handler=WebhookRequestHandler, route_name=DEFAULT_ROUTE_NAME, app=None): - self._check_frozen() - self._freeze = True - - # self.loop.set_task_factory(context.task_factory) - - if app is not None: - self._web_app = app - elif self._web_app is None: - self._web_app = app = web.Application() - else: - raise RuntimeError("web.Application() is already configured!") - - if self.retry_after: - app['RETRY_AFTER'] = self.retry_after - - if self._identity == app.get(self._identity): - # App is already configured - return - - if path is not None: - app.router.add_route('*', path, handler, name=route_name) - - async def _wrap_callback(cb, _): - return await cb(self.dispatcher) - - for callback in self._on_startup_webhook: - app.on_startup.append(functools.partial(_wrap_callback, callback)) - - # for callback in self._on_shutdown_webhook: - # app.on_shutdown.append(functools.partial(_wrap_callback, callback)) - - async def _on_shutdown(_): - await self._shutdown_webhook() - - app.on_shutdown.append(_on_shutdown) - app[APP_EXECUTOR_KEY] = self - app[BOT_DISPATCHER_KEY] = self.dispatcher - app[self._identity] = datetime.datetime.now() - app['_check_ip'] = self.check_ip - - def set_webhook(self, webhook_path: Optional[str] = None, request_handler: Any = WebhookRequestHandler, - route_name: str = DEFAULT_ROUTE_NAME, web_app: Optional[Application] = None): - """ - Set webhook for bot - - :param webhook_path: Optional[str] (default: None) - :param request_handler: Any (default: WebhookRequestHandler) - :param route_name: str Name of webhook handler route (default: 'webhook_handler') - :param web_app: Optional[Application] (default: None) - :return: - """ - self._prepare_webhook(webhook_path, request_handler, route_name, web_app) - self.loop.run_until_complete(self._startup_webhook()) - - def run_app(self, **kwargs): - web.run_app(self._web_app, **kwargs) - - def start_webhook(self, webhook_path=None, request_handler=WebhookRequestHandler, route_name=DEFAULT_ROUTE_NAME, - **kwargs): - """ - Start bot in webhook mode - - :param webhook_path: - :param request_handler: - :param route_name: Name of webhook handler route - :param kwargs: - :return: - """ - self.set_webhook(webhook_path=webhook_path, request_handler=request_handler, route_name=route_name) - self.run_app(**kwargs) - - def start_polling(self, reset_webhook=None, timeout=20, relax=0.1, fast=True): - """ - Start bot in long-polling mode - - :param reset_webhook: - :param timeout: - """ - self._prepare_polling() - loop: asyncio.AbstractEventLoop = self.loop - - try: - loop.run_until_complete(self._startup_polling()) - loop.create_task(self.dispatcher.start_polling(reset_webhook=reset_webhook, timeout=timeout, - relax=relax, fast=fast)) - loop.run_forever() - except (KeyboardInterrupt, SystemExit): - # loop.stop() - pass - finally: - loop.run_until_complete(self._shutdown_polling()) - log.warning("Goodbye!") - - def start(self, future): - """ - Execute Future. - - Return the Future's result, or raise its exception. - - :param future: - :return: - """ - self._check_frozen() - self._freeze = True - loop: asyncio.AbstractEventLoop = self.loop - - try: - loop.run_until_complete(self._startup_polling()) - result = loop.run_until_complete(future) - except (KeyboardInterrupt, SystemExit): - result = None - loop.stop() - finally: - loop.run_until_complete(self._shutdown_polling()) - log.warning("Goodbye!") - return result - - async def _skip_updates(self): - await self.dispatcher.reset_webhook(True) - await self.dispatcher.skip_updates() - log.warning(f'Updates were skipped successfully.') - - async def _welcome(self): - user = await self.dispatcher.bot.me - log.info(f"Bot: {user.full_name} [@{user.username}]") - - async def _shutdown(self): - self.dispatcher.stop_polling() - await self.dispatcher.storage.close() - await self.dispatcher.storage.wait_closed() - await self.dispatcher.bot.close() - - async def _startup_polling(self): - await self._welcome() - - if self.skip_updates: - await self._skip_updates() - for callback in self._on_startup_polling: - await callback(self.dispatcher) - - async def _shutdown_polling(self, wait_closed=False): - for callback in self._on_shutdown_polling: - await callback(self.dispatcher) - - await self._shutdown() - - if wait_closed: - await self.dispatcher.wait_closed() - - async def _shutdown_webhook(self, wait_closed=False): - for callback in self._on_shutdown_webhook: - await callback(self.dispatcher) - - await self._shutdown() - - if wait_closed: - await self.dispatcher.wait_closed() - - async def _startup_webhook(self): - await self._welcome() - if self.skip_updates: - await self._skip_updates() diff --git a/aiogram/utils/helper.py b/aiogram/utils/helper.py index 735afe5d..a7725f3e 100644 --- a/aiogram/utils/helper.py +++ b/aiogram/utils/helper.py @@ -15,11 +15,11 @@ Example: """ from typing import List -PROPS_KEYS_ATTR_NAME = '_props_keys' +PROPS_KEYS_ATTR_NAME = "_props_keys" class Helper: - mode = '' + mode = "" @classmethod def all(cls): @@ -40,13 +40,13 @@ class Helper: class HelperMode(Helper): - mode = 'original' + mode = "original" - SCREAMING_SNAKE_CASE = 'SCREAMING_SNAKE_CASE' - lowerCamelCase = 'lowerCamelCase' - CamelCase = 'CamelCase' - snake_case = 'snake_case' - lowercase = 'lowercase' + SCREAMING_SNAKE_CASE = "SCREAMING_SNAKE_CASE" + lowerCamelCase = "lowerCamelCase" + CamelCase = "CamelCase" + snake_case = "snake_case" + lowercase = "lowercase" @classmethod def all(cls): @@ -68,10 +68,10 @@ class HelperMode(Helper): """ if text.isupper(): return text - result = '' + result = "" for pos, symbol in enumerate(text): if symbol.isupper() and pos > 0: - result += '_' + symbol + result += "_" + symbol else: result += symbol.upper() return result @@ -97,10 +97,10 @@ class HelperMode(Helper): :param first_upper: first symbol must be upper? :return: """ - result = '' + result = "" need_upper = False for pos, symbol in enumerate(text): - if symbol == '_' and pos > 0: + if symbol == "_" and pos > 0: need_upper = True else: if need_upper: @@ -126,7 +126,7 @@ class HelperMode(Helper): if mode == cls.snake_case: return cls._snake_case(text) if mode == cls.lowercase: - return cls._snake_case(text).replace('_', '') + return cls._snake_case(text).replace("_", "") if mode == cls.lowerCamelCase: return cls._camel_case(text) if mode == cls.CamelCase: @@ -152,10 +152,10 @@ class Item: def __set_name__(self, owner, name): if not name.isupper(): - raise NameError('Name for helper item must be in uppercase!') + raise NameError("Name for helper item must be in uppercase!") if not self._value: - if hasattr(owner, 'mode'): - self._value = HelperMode.apply(name, getattr(owner, 'mode')) + if hasattr(owner, "mode"): + self._value = HelperMode.apply(name, getattr(owner, "mode")) class ListItem(Item): @@ -197,13 +197,14 @@ class ItemsList(list): class OrderedHelperMeta(type): - def __new__(mcs, name, bases, namespace, **kwargs): cls = super().__new__(mcs, name, bases, namespace) props_keys = [] - for prop_name in (name for name, prop in namespace.items() if isinstance(prop, (Item, ListItem))): + for prop_name in ( + name for name, prop in namespace.items() if isinstance(prop, (Item, ListItem)) + ): props_keys.append(prop_name) setattr(cls, PROPS_KEYS_ATTR_NAME, props_keys) @@ -212,7 +213,7 @@ class OrderedHelperMeta(type): class OrderedHelper(metaclass=OrderedHelperMeta): - mode = '' + mode = "" @classmethod def all(cls) -> List[str]: diff --git a/aiogram/utils/json.py b/aiogram/utils/json.py deleted file mode 100644 index 56f122e4..00000000 --- a/aiogram/utils/json.py +++ /dev/null @@ -1,47 +0,0 @@ -import importlib -import os - -JSON = 'json' -RAPIDJSON = 'rapidjson' -UJSON = 'ujson' - -# Detect mode -mode = JSON -for json_lib in (RAPIDJSON, UJSON): - if 'DISABLE_' + json_lib.upper() in os.environ: - continue - - try: - json = importlib.import_module(json_lib) - except ImportError: - continue - else: - mode = json_lib - break - -if mode == RAPIDJSON: - def dumps(data): - return json.dumps(data, ensure_ascii=False) - - - def loads(data): - return json.loads(data, number_mode=json.NM_NATIVE) - -elif mode == UJSON: - def loads(data): - return json.loads(data) - - - def dumps(data): - return json.dumps(data, ensure_ascii=False) - -else: - import json - - - def dumps(data): - return json.dumps(data, ensure_ascii=False) - - - def loads(data): - return json.loads(data) diff --git a/aiogram/utils/mixins.py b/aiogram/utils/mixins.py index e6857263..de7e9cd0 100644 --- a/aiogram/utils/mixins.py +++ b/aiogram/utils/mixins.py @@ -1,16 +1,16 @@ import contextvars -from typing import TypeVar, Type +from typing import Type, TypeVar -__all__ = ('DataMixin', 'ContextInstanceMixin') +__all__ = ("DataMixin", "ContextInstanceMixin") class DataMixin: @property def data(self): - data = getattr(self, '_data', None) + data = getattr(self, "_data", None) if data is None: data = {} - setattr(self, '_data', data) + setattr(self, "_data", data) return data def __getitem__(self, item): @@ -26,12 +26,12 @@ class DataMixin: return self.data.get(key, default) -T = TypeVar('T') +T = TypeVar("T") class ContextInstanceMixin: def __init_subclass__(cls, **kwargs): - cls.__context_instance = contextvars.ContextVar(f'instance_{cls.__name__}') + cls.__context_instance = contextvars.ContextVar(f"instance_{cls.__name__}") return cls @classmethod @@ -43,5 +43,7 @@ class ContextInstanceMixin: @classmethod def set_current(cls: Type[T], value: T): if not isinstance(value, cls): - raise TypeError(f'Value should be instance of {cls.__name__!r} not {type(value).__name__!r}') + raise TypeError( + f"Value should be instance of {cls.__name__!r} not {type(value).__name__!r}" + ) cls.__context_instance.set(value) diff --git a/aiogram/utils/parts.py b/aiogram/utils/parts.py deleted file mode 100644 index 7c4f51c5..00000000 --- a/aiogram/utils/parts.py +++ /dev/null @@ -1,59 +0,0 @@ -import typing - -MAX_MESSAGE_LENGTH = 4096 - - -def split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]: - """ - Split long text - - :param text: - :param length: - :return: list of parts - :rtype: :obj:`typing.List[str]` - """ - return [text[i : i + length] for i in range(0, len(text), length)] - - -def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]: - """ - Split long text - - :param text: - :param length: - :return: - """ - # TODO: More informative description - - temp_text = text - parts = [] - while temp_text: - if len(temp_text) > length: - try: - split_pos = temp_text[:length].rindex(" ") - except ValueError: - split_pos = length - if split_pos < length // 4 * 3: - split_pos = length - parts.append(temp_text[:split_pos]) - temp_text = temp_text[split_pos:].lstrip() - else: - parts.append(temp_text) - break - return parts - - -def paginate(data: typing.Iterable, page: int = 0, limit: int = 10) -> typing.Iterable: - """ - Slice data over pages - - :param data: any iterable object - :type data: :obj:`typing.Iterable` - :param page: number of page - :type page: :obj:`int` - :param limit: items per page - :type limit: :obj:`int` - :return: sliced object - :rtype: :obj:`typing.Iterable` - """ - return data[page * limit : page * limit + limit] diff --git a/aiogram/utils/payload.py b/aiogram/utils/payload.py deleted file mode 100644 index 0c5e8ae9..00000000 --- a/aiogram/utils/payload.py +++ /dev/null @@ -1,83 +0,0 @@ -import datetime -import secrets - -from babel.support import LazyProxy - -from aiogram import types -from . import json - -DEFAULT_FILTER = ['self', 'cls'] - - -def generate_payload(exclude=None, **kwargs): - """ - Generate payload - - Usage: payload = generate_payload(**locals(), exclude=['foo']) - - :param exclude: - :param kwargs: - :return: dict - """ - if exclude is None: - exclude = [] - return {key: value for key, value in kwargs.items() if - key not in exclude + DEFAULT_FILTER - and value is not None - and not key.startswith('_')} - - -def _normalize(obj): - """ - Normalize dicts and lists - - :param obj: - :return: normalized object - """ - if isinstance(obj, list): - return [_normalize(item) for item in obj] - elif isinstance(obj, dict): - return {k: _normalize(v) for k, v in obj.items() if v is not None} - elif hasattr(obj, 'to_python'): - return obj.to_python() - return obj - - -def prepare_arg(value): - """ - Stringify dicts/lists and convert datetime/timedelta to unix-time - - :param value: - :return: - """ - if value is None: - return value - if isinstance(value, (list, dict)) or hasattr(value, 'to_python'): - return json.dumps(_normalize(value)) - if isinstance(value, datetime.timedelta): - now = datetime.datetime.now() - return int((now + value).timestamp()) - if isinstance(value, datetime.datetime): - return round(value.timestamp()) - if isinstance(value, LazyProxy): - return str(value) - return value - - -def prepare_file(payload, files, key, file): - if isinstance(file, str): - payload[key] = file - elif file is not None: - files[key] = file - - -def prepare_attachment(payload, files, key, file): - if isinstance(file, str): - payload[key] = file - elif isinstance(file, types.InputFile): - payload[key] = file.attach - files[file.attachment_key] = file.file - elif file is not None: - file_attach_name = secrets.token_urlsafe(16) - payload[key] = "attach://" + file_attach_name - files[file_attach_name] = file diff --git a/dev_requirements.txt b/dev_requirements.txt deleted file mode 100644 index 06bc3e9c..00000000 --- a/dev_requirements.txt +++ /dev/null @@ -1,18 +0,0 @@ --r requirements.txt - -ujson>=1.35 -python-rapidjson>=0.7.0 -emoji>=0.5.2 -pytest>=4.4.1,<4.6 -pytest-asyncio>=0.10.0 -tox>=3.9.0 -aresponses>=1.1.1 -uvloop>=0.12.2 -aioredis>=1.2.0 -wheel>=0.31.1 -sphinx>=2.0.1 -sphinx-rtd-theme>=0.4.3 -sphinxcontrib-programoutput>=0.14 -aiohttp-socks>=0.2.2 -rethinkdb>=2.4.1 -coverage==4.5.3 diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 4e50ed99..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = python -msphinx -SPHINXPROJ = aiogram -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/source/static/logo.png b/docs/assets/images/logo.png similarity index 100% rename from docs/source/static/logo.png rename to docs/assets/images/logo.png diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..b4106a9a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,13 @@ +# Overview + +Documentation for version 3.0 + +[![MIT License](https://img.shields.io/pypi/l/aiogram.svg?style=flat-square)](https://opensource.org/licenses/MIT) +[![Supported python versions](https://img.shields.io/pypi/pyversions/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) +[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-4.4-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api) +[![PyPi Package Version](https://img.shields.io/pypi/v/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) +[![PyPi status](https://img.shields.io/pypi/status/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) +[![Downloads](https://img.shields.io/pypi/dm/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) +[![\[Telegram\] aiogram live](https://img.shields.io/badge/telegram-aiogram-blue.svg?style=flat-square)](https://t.me/aiogram_live) + +**aiogram** modern and fully asynchronous framework for [Telegram Bot API](https://core.telegram.org/bots/api) written in Python 3.7 with [asyncio](https://docs.python.org/3/library/asyncio.html) and [aiohttp](https://github.com/aio-libs/aiohttp). It helps you to make your bots faster and simpler. diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 00000000..ed908e2a --- /dev/null +++ b/docs/install.md @@ -0,0 +1,14 @@ +# Installation Guide + +## Using PIP +```bash +pip install -U aiogram +``` + +## Using Pipenv +```bash +pipenv install aiogram +``` + +## Using AUR +*aiogram* is also available in Arch User Repository, so you can install this library on any Arch-based distribution like ArchLinux, Antergos, Manjaro, etc. To do this, use your favorite AUR-helper and install [python-aiogram](https://aur.archlinux.org/packages/python-aiogram/) package. diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index 11a5143b..00000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# aiogram documentation build configuration file, created by -# sphinx-quickstart on Sat Jun 3 11:22:58 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import datetime -import os -import sys - -from pip._vendor.pkg_resources import parse_version - -sys.path.insert(0, os.path.abspath('../..')) - -import aiogram - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.todo', - 'sphinx.ext.viewcode', - 'sphinx.ext.autodoc', - 'sphinx.ext.ifconfig', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'aiogram' -author = 'Illemius / Alex Root Junior' -copyright = f'{datetime.datetime.now().year}, {author}' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# - -parsed_version = parse_version(aiogram.__version__) - -# The short X.Y version. -version = parsed_version.base_version - -# The full version, including alpha/beta/rc tags. -release = aiogram.__version__ - -releaselevel = 'dev' if parsed_version.dev else 'alpha' \ - if 'a' in version else 'beta' \ - if 'b' in version else 'stable' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = [] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -html_logo = 'static/logo.png' -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['static'] - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'aiogramdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'aiogram.tex', 'aiogram Documentation', - 'Illemius / Alex Root Junior', 'manual'), -] - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'aiogram', 'aiogram Documentation', - [author], 1) -] - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'aiogram', 'aiogram Documentation', - author, 'aiogram', 'Asynchonyously Python framework for Telegram Bot API', - 'Miscellaneous'), -] - -autoclass_content = "both" -autodoc_member_order = "bysource" -# autodoc_default_flags = [ -# "members", -# "no-undoc-members", -# "show-inheritance", -# "inherited-members", -# ] diff --git a/docs/source/contribution.rst b/docs/source/contribution.rst deleted file mode 100644 index dba243c9..00000000 --- a/docs/source/contribution.rst +++ /dev/null @@ -1,4 +0,0 @@ -Contribution -============ - -TODO diff --git a/docs/source/dispatcher/filters.rst b/docs/source/dispatcher/filters.rst deleted file mode 100644 index b174f1ef..00000000 --- a/docs/source/dispatcher/filters.rst +++ /dev/null @@ -1,183 +0,0 @@ -======= -Filters -======= - -Basics -====== - -Filter factory greatly simplifies the reuse of filters when registering handlers. - -Filters factory -=============== - -.. autoclass:: aiogram.dispatcher.filters.factory.FiltersFactory - :members: - :show-inheritance: - -Builtin filters -=============== -``aiogram`` has some builtin filters. Here you can see all of them: - -Command -------- - -.. autoclass:: aiogram.dispatcher.filters.builtin.Command - :members: - :show-inheritance: - -CommandStart ------------- - -.. autoclass:: aiogram.dispatcher.filters.builtin.CommandStart - :members: - :show-inheritance: - -CommandHelp ------------ - -.. autoclass:: aiogram.dispatcher.filters.builtin.CommandHelp - :members: - :show-inheritance: - -CommandSettings ---------------- - -.. autoclass:: aiogram.dispatcher.filters.builtin.CommandSettings - :members: - :show-inheritance: - - -CommandPrivacy --------------- - -.. autoclass:: aiogram.dispatcher.filters.builtin.CommandPrivacy - :members: - :show-inheritance: - - -Text ----- - -.. autoclass:: aiogram.dispatcher.filters.builtin.Text - :members: - :show-inheritance: - - -HashTag -------- - -.. autoclass:: aiogram.dispatcher.filters.builtin.HashTag - :members: - :show-inheritance: - - -Regexp ------- - -.. autoclass:: aiogram.dispatcher.filters.builtin.Regexp - :members: - :show-inheritance: - - -RegexpCommandsFilter --------------------- - -.. autoclass:: aiogram.dispatcher.filters.builtin.RegexpCommandsFilter - :members: - :show-inheritance: - - -ContentTypeFilter ------------------ - -.. autoclass:: aiogram.dispatcher.filters.builtin.ContentTypeFilter - :members: - :show-inheritance: - - -StateFilter ------------ - -.. autoclass:: aiogram.dispatcher.filters.builtin.StateFilter - :members: - :show-inheritance: - - -ExceptionsFilter ----------------- - -.. autoclass:: aiogram.dispatcher.filters.builtin.ExceptionsFilter - :members: - :show-inheritance: - - -IDFilter ----------------- - -.. autoclass:: aiogram.dispatcher.filters.builtin.IDFilter - :members: - :show-inheritance: - - -AdminFilter ----------------- - -.. autoclass:: aiogram.dispatcher.filters.builtin.AdminFilter - :members: - :show-inheritance: - - -IsReplyFilter -------------- - -.. autoclass:: aiogram.dispatcher.filters.filters.IsReplyFilter - :members: - :show-inheritance: - - -Making own filters (Custom filters) -=================================== - -Own filter can be: - -- any callable object -- any async function -- any anonymous function (Example: ``lambda msg: msg.text == 'spam'``) -- Subclass of :obj:`AbstractFilter`, :obj:`Filter` or :obj:`BoundFilter` - - -AbstractFilter --------------- -.. autoclass:: aiogram.dispatcher.filters.filters.AbstractFilter - :members: - :show-inheritance: - -Filter ------- -.. autoclass:: aiogram.dispatcher.filters.filters.Filter - :members: - :show-inheritance: - -BoundFilter ------------ -.. autoclass:: aiogram.dispatcher.filters.filters.BoundFilter - :members: - :show-inheritance: - - -.. code-block:: python - - class ChatIdFilter(BoundFilter): - key = 'chat_id' - - def __init__(self, chat_id: typing.Union[typing.Iterable, int]): - if isinstance(chat_id, int): - chat_id = [chat_id] - self.chat_id = chat_id - - def check(self, message: types.Message) -> bool: - return message.chat.id in self.chat_id - - - dp.filters_factory.bind(ChatIdFilter, event_handlers=[dp.message_handlers]) - diff --git a/docs/source/dispatcher/fsm.rst b/docs/source/dispatcher/fsm.rst deleted file mode 100644 index 93c94aa6..00000000 --- a/docs/source/dispatcher/fsm.rst +++ /dev/null @@ -1,45 +0,0 @@ -==================== -Finite state machine -==================== - -Storage -======= -Coming soon... - -Available storage's -------------------- -Coming soon... - -Memory storage -~~~~~~~~~~~~~~ -Coming soon... - -Redis storage -~~~~~~~~~~~~~ -Coming soon... - -Rethink DB storage -~~~~~~~~~~~~~~~~~~ -Coming soon... - -Making own storage's -~~~~~~~~~~~~~~~~~~~~ -Coming soon... - - -States -====== -Coming soon... - - -State utils -=========== -Coming soon... - -State ------ -Coming soon... - -States group ------------- -Coming soon... diff --git a/docs/source/dispatcher/index.rst b/docs/source/dispatcher/index.rst deleted file mode 100644 index 7ca0af82..00000000 --- a/docs/source/dispatcher/index.rst +++ /dev/null @@ -1,33 +0,0 @@ -========== -Dispatcher -========== - -.. toctree:: - - filters - fsm - middleware - webhook - -Basics -====== -Coming soon... - -Available handlers -================== -Coming soon... - -Handler class -------------- -Coming soon... - -Features -======== -Coming soon... - -Dispatcher class -================ - -.. autoclass:: aiogram.Dispatcher - :members: - :show-inheritance: diff --git a/docs/source/dispatcher/middleware.rst b/docs/source/dispatcher/middleware.rst deleted file mode 100644 index fc73b79f..00000000 --- a/docs/source/dispatcher/middleware.rst +++ /dev/null @@ -1,15 +0,0 @@ -========== -Middleware -========== - -Bases -===== -Coming soon... - -Making own middleware's -======================= -Coming soon... - -Available middleware's -====================== -Coming soon... diff --git a/docs/source/dispatcher/webhook.rst b/docs/source/dispatcher/webhook.rst deleted file mode 100644 index b419f9ed..00000000 --- a/docs/source/dispatcher/webhook.rst +++ /dev/null @@ -1,16 +0,0 @@ -======= -Webhook -======= -Coming soon... - -Bases -===== -Coming soon... - -Security -======== -Coming soon... - -Making requests when getting updates -==================================== -Coming soon... diff --git a/docs/source/examples/advanced_executor_example.rst b/docs/source/examples/advanced_executor_example.rst deleted file mode 100644 index 9eb5d950..00000000 --- a/docs/source/examples/advanced_executor_example.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. Autogenerated file at 2018-10-28 19:31:48.335963 - -========================= -Advanced executor example -========================= - -!/usr/bin/env python3 -**This example is outdated** -In this example used ArgumentParser for configuring Your bot. -Provided to start bot with webhook: -python advanced_executor_example.py \ ---token TOKEN_HERE \ ---host 0.0.0.0 \ ---port 8084 \ ---host-name example.com \ ---webhook-port 443 -Or long polling: -python advanced_executor_example.py --token TOKEN_HERE -So... In this example found small trouble: -can't get bot instance in handlers. -If you want to automatic change getting updates method use executor utils (from aiogram.utils.executor) - TODO: Move token to environment variables. - -.. literalinclude:: ../../../examples/advanced_executor_example.py - :caption: advanced_executor_example.py - :language: python - :linenos: - :lines: 25- diff --git a/docs/source/examples/broadcast_example.rst b/docs/source/examples/broadcast_example.rst deleted file mode 100644 index 97556e73..00000000 --- a/docs/source/examples/broadcast_example.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.593501 - -================= -Broadcast example -================= - -.. literalinclude:: ../../../examples/broadcast_example.py - :caption: broadcast_example.py - :language: python - :linenos: diff --git a/docs/source/examples/check_user_language.rst b/docs/source/examples/check_user_language.rst deleted file mode 100644 index 0fc2d90e..00000000 --- a/docs/source/examples/check_user_language.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.558059 - -=================== -Check user language -=================== - -Babel is required. - -.. literalinclude:: ../../../examples/check_user_language.py - :caption: check_user_language.py - :language: python - :linenos: - :lines: 5- diff --git a/docs/source/examples/echo_bot.rst b/docs/source/examples/echo_bot.rst deleted file mode 100644 index 18c45569..00000000 --- a/docs/source/examples/echo_bot.rst +++ /dev/null @@ -1,9 +0,0 @@ -Echo bot -======== - -Very simple example of the bot which will sent text of the received messages to the sender - -.. literalinclude:: ../../../examples/echo_bot.py - :caption: echo_bot.py - :language: python - :linenos: diff --git a/docs/source/examples/finite_state_machine_example.rst b/docs/source/examples/finite_state_machine_example.rst deleted file mode 100644 index 3eea4a22..00000000 --- a/docs/source/examples/finite_state_machine_example.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.595032 - -============================ -Finite state machine example -============================ - -.. literalinclude:: ../../../examples/finite_state_machine_example.py - :caption: finite_state_machine_example.py - :language: python - :linenos: diff --git a/docs/source/examples/i18n_example.rst b/docs/source/examples/i18n_example.rst deleted file mode 100644 index 875006be..00000000 --- a/docs/source/examples/i18n_example.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.591007 - -============ -I18n example -============ - -Internalize your bot -Step 1: extract texts -# pybabel extract i18n_example.py -o locales/mybot.pot -Step 2: create *.po files. For e.g. create en, ru, uk locales. -# echo {en,ru,uk} | xargs -n1 pybabel init -i locales/mybot.pot -d locales -D mybot -l -Step 3: translate texts -Step 4: compile translations -# pybabel compile -d locales -D mybot -Step 5: When you change the code of your bot you need to update po & mo files. -Step 5.1: regenerate pot file: -command from step 1 -Step 5.2: update po files -# pybabel update -d locales -D mybot -i locales/mybot.pot -Step 5.3: update your translations -Step 5.4: compile mo files -command from step 4 - -.. literalinclude:: ../../../examples/i18n_example.py - :caption: i18n_example.py - :language: python - :linenos: - :lines: 22- diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst deleted file mode 100644 index 2bedaa52..00000000 --- a/docs/source/examples/index.rst +++ /dev/null @@ -1,21 +0,0 @@ -======== -Examples -======== - -.. toctree:: - - echo_bot - inline_bot - advanced_executor_example - proxy_and_emojize - finite_state_machine_example - throtling_example - i18n_example - regexp_commands_filter_example - check_user_language - middleware_and_antiflood - webhook_example - webhook_example_2 - payments - broadcast_example - media_group diff --git a/docs/source/examples/inline_bot.rst b/docs/source/examples/inline_bot.rst deleted file mode 100644 index b6059974..00000000 --- a/docs/source/examples/inline_bot.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.561907 - -========== -Inline bot -========== - -.. literalinclude:: ../../../examples/inline_bot.py - :caption: inline_bot.py - :language: python - :linenos: diff --git a/docs/source/examples/media_group.rst b/docs/source/examples/media_group.rst deleted file mode 100644 index ebf2d3f2..00000000 --- a/docs/source/examples/media_group.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.566615 - -=========== -Media group -=========== - -.. literalinclude:: ../../../examples/media_group.py - :caption: media_group.py - :language: python - :linenos: diff --git a/docs/source/examples/middleware_and_antiflood.rst b/docs/source/examples/middleware_and_antiflood.rst deleted file mode 100644 index 4f634c93..00000000 --- a/docs/source/examples/middleware_and_antiflood.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.560132 - -======================== -Middleware and antiflood -======================== - -.. literalinclude:: ../../../examples/middleware_and_antiflood.py - :caption: middleware_and_antiflood.py - :language: python - :linenos: diff --git a/docs/source/examples/payments.rst b/docs/source/examples/payments.rst deleted file mode 100644 index 7b634ce7..00000000 --- a/docs/source/examples/payments.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.579017 - -======== -Payments -======== - -.. literalinclude:: ../../../examples/payments.py - :caption: payments.py - :language: python - :linenos: diff --git a/docs/source/examples/proxy_and_emojize.rst b/docs/source/examples/proxy_and_emojize.rst deleted file mode 100644 index 3dbaa268..00000000 --- a/docs/source/examples/proxy_and_emojize.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.555359 - -================= -Proxy and emojize -================= - -.. literalinclude:: ../../../examples/proxy_and_emojize.py - :caption: proxy_and_emojize.py - :language: python - :linenos: diff --git a/docs/source/examples/regexp_commands_filter_example.rst b/docs/source/examples/regexp_commands_filter_example.rst deleted file mode 100644 index 36452350..00000000 --- a/docs/source/examples/regexp_commands_filter_example.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.568530 - -============================== -Regexp commands filter example -============================== - -.. literalinclude:: ../../../examples/regexp_commands_filter_example.py - :caption: regexp_commands_filter_example.py - :language: python - :linenos: diff --git a/docs/source/examples/throtling_example.rst b/docs/source/examples/throtling_example.rst deleted file mode 100644 index eaeb337e..00000000 --- a/docs/source/examples/throtling_example.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.563878 - -================= -Throtling example -================= - -Example for throttling manager. -You can use that for flood controlling. - -.. literalinclude:: ../../../examples/throtling_example.py - :caption: throtling_example.py - :language: python - :linenos: - :lines: 7- diff --git a/docs/source/examples/webhook_example.rst b/docs/source/examples/webhook_example.rst deleted file mode 100644 index 48de8622..00000000 --- a/docs/source/examples/webhook_example.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. Autogenerated file at 2018-10-28 19:31:48.341172 - -=============== -Webhook example -=============== - -Example outdated - -.. literalinclude:: ../../../examples/webhook_example.py - :caption: webhook_example.py - :language: python - :linenos: - :lines: 5- diff --git a/docs/source/examples/webhook_example_2.rst b/docs/source/examples/webhook_example_2.rst deleted file mode 100644 index 2ffdfed9..00000000 --- a/docs/source/examples/webhook_example_2.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. Autogenerated file at 2018-09-08 02:07:37.576034 - -================= -Webhook example 2 -================= - -.. literalinclude:: ../../../examples/webhook_example_2.py - :caption: webhook_example_2.py - :language: python - :linenos: diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 7b6fd231..00000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,93 +0,0 @@ -Welcome to aiogram's documentation! -=================================== - - - .. image:: https://img.shields.io/badge/telegram-aiogram-blue.svg?style=flat-square - :target: https://t.me/aiogram_live - :alt: [Telegram] aiogram live - - .. image:: https://img.shields.io/pypi/v/aiogram.svg?style=flat-square - :target: https://pypi.python.org/pypi/aiogram - :alt: PyPi Package Version - - .. image:: https://img.shields.io/pypi/status/aiogram.svg?style=flat-square - :target: https://pypi.python.org/pypi/aiogram - :alt: PyPi status - - .. image:: https://img.shields.io/pypi/dm/aiogram.svg?style=flat-square - :target: https://pypi.python.org/pypi/aiogram - :alt: PyPi downloads - - .. image:: https://img.shields.io/pypi/pyversions/aiogram.svg?style=flat-square - :target: https://pypi.python.org/pypi/aiogram - :alt: Supported python versions - - .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.4-blue.svg?style=flat-square&logo=telegram - :target: https://core.telegram.org/bots/api - :alt: Telegram Bot API - - .. image:: https://img.shields.io/readthedocs/aiogram?style=flat-square - :target: http://aiogram.readthedocs.io/en/latest/?badge=latest - :alt: Documentation Status - - .. image:: https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square - :target: https://github.com/aiogram/aiogram/issues - :alt: Github issues - - .. image:: https://img.shields.io/pypi/l/aiogram.svg?style=flat-square - :target: https://opensource.org/licenses/MIT - :alt: MIT License - - -**aiogram** is a pretty simple and fully asynchronous framework for `Telegram Bot API `_ written in Python 3.7 with `asyncio `_ and `aiohttp `_. It helps you to make your bots faster and simpler. - - -Official aiogram resources --------------------------- -- News: `@aiogram_live `_ -- Community: `@aiogram `_ -- Russian community: `@aiogram_ru `_ -- Pip: `aiogram `_ -- Docs: `ReadTheDocs `_ -- Source: `Github repo `_ -- Issues/Bug tracker: `Github issues tracker `_ -- Test bot: `@aiogram_bot `_ - -Features --------- - -- Asynchronous -- Awesome -- Makes things faster -- Has `FSM `_ -- Can reply into webhook. (In other words `make requests in response to updates `_) - - -Contribute ----------- - -- `Issue Tracker `_ -- `Source Code `_ - - -Contents --------- - -.. toctree:: - install - quick_start - migration_1_to_2 - telegram/index - dispatcher/index - utils/index - examples/index - contribution - links - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/source/install.rst b/docs/source/install.rst deleted file mode 100644 index b2fd8e38..00000000 --- a/docs/source/install.rst +++ /dev/null @@ -1,97 +0,0 @@ -Installation Guide -================== - -Using PIP ---------- - .. code-block:: bash - - $ pip install -U aiogram - -Using Pipenv ------------- - .. code-block:: bash - - $ pipenv install aiogram - -Using AUR ---------- -*aiogram* is also available in Arch User Repository, so you can install this library on any Arch-based distribution like ArchLinux, Antergos, Manjaro, etc. To do this, use your favorite AUR-helper and install `python-aiogram `_ package. - -From sources ------------- - - Development versions: - - .. code-block:: bash - - $ git clone https://github.com/aiogram/aiogram.git - $ cd aiogram - $ python setup.py install - - Or if you want to install stable version (The same with version form PyPi): - - .. code-block:: bash - - $ git clone https://github.com/aiogram/aiogram.git - $ cd aiogram - $ git checkout master - $ python setup.py install - - -Recommendations ---------------- -You can speedup your bots by following next instructions: - -- Use `uvloop `_ instead of default asyncio loop. - - *uvloop* is a fast, drop-in replacement of the built-in asyncio event loop. uvloop is implemented in Cython and uses libuv under the hood. - - **Installation:** - - .. code-block:: bash - - $ pip install uvloop - -- Use `ujson `_ instead of default json module. - - *UltraJSON* is an ultra fast JSON encoder and decoder written in pure C with bindings for Python 2.5+ and 3. - - **Installation:** - - .. code-block:: bash - - $ pip install ujson - -- Use aiohttp speedups - - - Use `cchardet `_ instead of chardet module. - - *cChardet* is high speed universal character encoding detector. - - **Installation:** - - .. code-block:: bash - - $ pip install cchardet - - - Use `aiodns `_ for speeding up DNS resolving. - - *aiodns* provides a simple way for doing asynchronous DNS resolutions. - - **Installation:** - - .. code-block:: bash - - $ pip install aiodns - - - Installing speedups altogether. - - The following will get you ``aiohttp`` along with ``cchardet``, ``aiodns`` and ``brotlipy`` in one bundle. - - **Installation:** - - .. code-block:: bash - - $ pip install aiohttp[speedups] - -In addition, you don't need do nothing, *aiogram* is automatically starts using that if is found in your environment. diff --git a/docs/source/links.rst b/docs/source/links.rst deleted file mode 100644 index 2632e335..00000000 --- a/docs/source/links.rst +++ /dev/null @@ -1,4 +0,0 @@ -Links -===== - -TODO diff --git a/docs/source/migration_1_to_2.rst b/docs/source/migration_1_to_2.rst deleted file mode 100644 index 67684831..00000000 --- a/docs/source/migration_1_to_2.rst +++ /dev/null @@ -1,260 +0,0 @@ -========================== -Migration FAQ (1.4 -> 2.0) -========================== - -This update make breaking changes in aiogram API and drop backward capability with previous versions of framework. - -From this point aiogram supports only Python 3.7 and newer. - -Changelog -========= - -- Used contextvars instead of `aiogram.utils.context`; -- Implemented filters factory; -- Implemented new filters mechanism; -- Allowed to customize command prefix in CommandsFilter; -- Implemented mechanism of passing results from filters (as dicts) as kwargs in handlers (like fixtures in pytest); -- Implemented states group feature; -- Implemented FSM storage's proxy; -- Changed files uploading mechanism; -- Implemented pipe for uploading files from URL; -- Implemented I18n Middleware; -- Errors handlers now should accept only two arguments (current update and exception); -- Used `aiohttp_socks` instead of `aiosocksy` for Socks4/5 proxy; -- `types.ContentType` was divided to `types.ContentType` and `types.ContentTypes`; -- Allowed to use rapidjson instead of ujson/json; -- `.current()` method in bot and dispatcher objects was renamed to `get_current()`; - -Instructions -============ - -Contextvars ------------ -Context utility (`aiogram.utils.context`) now is removed due to new features of Python 3.7 and all subclasses of :obj:`aiogram.types.base.TelegramObject`, :obj:`aiogram.Bot` and :obj:`aiogram.Dispatcher` has `.get_current()` and `.set_current()` methods for getting/setting contextual instances of objects. - -Example: - -.. code-block:: python - - async def my_handler(message: types.Message): - bot = Bot.get_current() - user = types.User.get_current() - ... - -Filters -------- - -Custom filters -~~~~~~~~~~~~~~ - -Now `func` keyword argument can't be used for passing filters to the list of filters instead of that you can pass the filters as arguments: - -.. code-block:: python - - @dp.message_handler(lambda message: message.text == 'foo') - @dp.message_handler(types.ChatType.is_private, my_filter) - async def ... - -(func filter is still available until v2.1) - -Filters factory -~~~~~~~~~~~~~~~ -Also you can bind your own filters for using as keyword arguments: - -.. code-block:: python - - from aiogram.dispatcher.filters import BoundFilter - - class MyFilter(BoundFilter): - key = 'is_admin' - - def __init__(self, is_admin): - self.is_admin = is_admin - - async def check(self, message: types.Message): - member = await bot.get_chat_member(message.chat.id, message.from_user.id) - return member.is_admin() - - dp.filters_factory.bind(MyFilter) - - @dp.message_handler(is_admin=True) - async def ... - - -Customize commands prefix -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Commands prefix can be changed by following one of two available methods: - -.. code-block:: python - - @dp.message_handler(commands=['admin'], commands_prefix='!/') - @dp.message_handler(Command('admin', prefixes='!/')) - async def ... - -Passing data from filters as keyword arguments to the handlers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can pass any data from any filter to the handler by returning :obj:`dict` -If any key from the received dictionary not in the handler specification the key will be skipped and and will be unavailable from the handler - -Before (<=v1.4) - -.. code-block:: python - - async def my_filter(message: types.Message): - # do something here - message.conf['foo'] = 'foo' - message.conf['bar'] = 42 - return True - - @dp.message_handler(func=my_filter) - async def my_message_handler(message: types.Message): - bar = message.conf["bar"] - await message.reply(f'bar = {bar}') - - -Now (v2.0) - -.. code-block:: python - - async def my_filter(message: types.Message): - # do something here - return {'foo': 'foo', 'bar': 42} - - @dp.message_handler(my_filter) - async def my_message_handler(message: types.Message, bar: int): - await message.reply(f'bar = {bar}') - - -Other -~~~~~ -Filters can also be used as logical expressions: - -.. code-block:: python - - Text(equals='foo') | Text(endswith='Bar') | ~Text(contains='spam') - - -States group ------------- - -You can use States objects and States groups instead of string names of the states. -String values is still also be available. - -Writing states group: - -.. code-block:: python - - from aiogram.dispatcher.filters.state import State, StatesGroup - - class UserForm(StatesGroup): - name = State() # Will be represented in storage as 'Form:name' - age = State() # Will be represented in storage as 'Form:age' - gender = State() # Will be represented in storage as 'Form:gender' - -After that you can use states as `UserForm.name` and etc. - -FSM storage's proxy -------------------- -Now `Dispatcher.current_context()` can't be used as context-manager. - -Implemented `FSMContext.proxy()` method which returns asynchronous `FSMContextProxy` context manager and can be used for more simply getting data from the storage. - -`FSMContextProxy` load all user-related data on initialization and dump it to the storage when proxy is closing if any part of the data was changed. - - -Usage: - -.. code-block:: python - - @dp.message_handler(commands=['click']) - async def cmd_start(message: types.Message, state: FSMContext): - async with state.proxy() as proxy: # proxy = FSMContextProxy(state); await proxy.load() - proxy.setdefault('counter', 0) - proxy['counter'] += 1 - return await message.reply(f"Counter: {proxy['counter']}") - - -This method is not recommended in high-load solutions in reason named "race-condition". - - -File uploading mechanism ------------------------- -Fixed uploading files. Removed `BaseBot.send_file` method. This allowed to send the `thumb` field. - -Pipe for uploading files from URL ---------------------------------- -Known issue when Telegram can not accept sending file as URL. In this case need to download file locally and then send. - -In this case now you can send file from URL by using pipe. That means you download and send the file without saving it. - -You can open the pipe and use for uploading by calling `types.InputFile.from_file()` - -Example: - -.. code-block:: python - - URL = 'https://aiogram.readthedocs.io/en/dev-2.x/_static/logo.png' - - - @dp.message_handler(commands=['image, img']) - async def cmd_image(message: types.Message): - await bot.send_photo(message.chat.id, types.InputFile.from_url(URL)) - -I18n Middleware ---------------- -You can internalize your bot by following next steps: - -(Code snippets in this example related with `examples/i18n_example.py`) - -First usage -~~~~~~~~~~~ -1. Extract texts - - .. code-block:: bash - - pybabel extract i18n_example.py -o locales/mybot.pot - -2. Create `*.po` files. For e.g. create `en`, `ru`, `uk` locales. -3. Translate texts -4. Compile translations - - .. code-block:: bash - - pybabel compile -d locales -D mybot - -Updating translations -~~~~~~~~~~~~~~~~~~~~~ -When you change the code of your bot you need to update `po` & `mo` files: - -1. Regenerate pot file: - - .. code-block:: bash - - pybabel extract i18n_example.py -o locales/mybot.pot - -2. Update po files - - .. code-block:: bash - - pybabel update -d locales -D mybot -i locales/mybot.pot - -3. Update your translations -4. Compile `mo` files - - .. code-block:: bash - - pybabel compile -d locales -D mybot - -Error handlers --------------- -Previously errors handlers had to have three arguments `dispatcher`, `update` and `exception` now `dispatcher` argument is removed and will no longer be passed to the error handlers. - - -Content types -------------- - -Content types helper was divided to `types.ContentType` and `types.ContentTypes`. - -In filters you can use `types.ContentTypes` but for comparing content types you must use `types.ContentType` class. diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst deleted file mode 100644 index b0724a78..00000000 --- a/docs/source/quick_start.rst +++ /dev/null @@ -1,45 +0,0 @@ -=========== -Quick start -=========== - -Simple template ---------------- - -At first you have to import all necessary modules - -.. literalinclude:: ../../examples/echo_bot.py - :language: python - :lines: 6-8 - -Then you have to initialize bot and dispatcher instances. -Bot token you can get from `@BotFather `_ - -.. literalinclude:: ../../examples/echo_bot.py - :language: python - :lines: 10-17 - -Next step: interaction with bots starts with one command. Register your first command handler: - -.. literalinclude:: ../../examples/echo_bot.py - :language: python - :lines: 20-25 - -If you want to handle all messages in the chat simply add handler without filters: - -.. literalinclude:: ../../examples/echo_bot.py - :language: python - :lines: 35-37 - -Last step: run long polling. - -.. literalinclude:: ../../examples/echo_bot.py - :language: python - :lines: 40-41 - -Summary -------- - -.. literalinclude:: ../../examples/echo_bot.py - :language: python - :linenos: - :lines: -19,27- diff --git a/docs/source/telegram/bot.rst b/docs/source/telegram/bot.rst deleted file mode 100644 index dce4853a..00000000 --- a/docs/source/telegram/bot.rst +++ /dev/null @@ -1,25 +0,0 @@ -Bot object -========== - -Low level API -------------- -Subclass of this class used only for splitting network interface from all of API methods. - -.. autoclass:: aiogram.bot.base.BaseBot - :members: - :show-inheritance: - -Telegram Bot ------------- -This class based on :obj:`aiogram.bot.base.BaseBot` - -.. autoclass:: aiogram.bot.bot.Bot - :members: - :show-inheritance: - -API Helpers ------------ - -.. automodule:: aiogram.bot.api - :members: - :show-inheritance: diff --git a/docs/source/telegram/index.rst b/docs/source/telegram/index.rst deleted file mode 100644 index a9ac96ef..00000000 --- a/docs/source/telegram/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Telegram -======== - -.. toctree:: - - bot - types/index diff --git a/docs/source/telegram/types/animation.rst b/docs/source/telegram/types/animation.rst deleted file mode 100644 index 23eaa511..00000000 --- a/docs/source/telegram/types/animation.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.415416 - -========= -Animation -========= - -.. autoclass:: aiogram.types.animation.Animation - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/audio.rst b/docs/source/telegram/types/audio.rst deleted file mode 100644 index 30f6b4aa..00000000 --- a/docs/source/telegram/types/audio.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.405634 - -===== -Audio -===== - -.. autoclass:: aiogram.types.audio.Audio - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/auth_widget_data.rst b/docs/source/telegram/types/auth_widget_data.rst deleted file mode 100644 index c6c98993..00000000 --- a/docs/source/telegram/types/auth_widget_data.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.435002 - -============== -AuthWidgetData -============== - -.. autoclass:: aiogram.types.auth_widget_data.AuthWidgetData - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/base.rst b/docs/source/telegram/types/base.rst deleted file mode 100644 index 9e5915b5..00000000 --- a/docs/source/telegram/types/base.rst +++ /dev/null @@ -1,19 +0,0 @@ -=================== -Base TelegramObject -=================== - - -MetaTelegramObject -================== - -.. autoclass:: aiogram.types.base.MetaTelegramObject - :members: - :show-inheritance: - - -TelegramObject -============== - -.. autoclass:: aiogram.types.base.TelegramObject - :members: - :show-inheritance: diff --git a/docs/source/telegram/types/callback_game.rst b/docs/source/telegram/types/callback_game.rst deleted file mode 100644 index bc118972..00000000 --- a/docs/source/telegram/types/callback_game.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.401745 - -============ -CallbackGame -============ - -.. autoclass:: aiogram.types.callback_game.CallbackGame - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/callback_query.rst b/docs/source/telegram/types/callback_query.rst deleted file mode 100644 index c05c392c..00000000 --- a/docs/source/telegram/types/callback_query.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.386236 - -============= -CallbackQuery -============= - -.. autoclass:: aiogram.types.callback_query.CallbackQuery - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/chat.rst b/docs/source/telegram/types/chat.rst deleted file mode 100644 index 2449fb98..00000000 --- a/docs/source/telegram/types/chat.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.403478 - -==== -Chat -==== - -.. autoclass:: aiogram.types.chat.Chat - :members: - :show-inheritance: - - -ChatType -======== - -.. autoclass:: aiogram.types.chat.ChatType - :members: - :show-inheritance: - - -ChatActions -=========== - -.. autoclass:: aiogram.types.chat.ChatActions - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/chat_member.rst b/docs/source/telegram/types/chat_member.rst deleted file mode 100644 index 174e5345..00000000 --- a/docs/source/telegram/types/chat_member.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.428185 - -========== -ChatMember -========== - -.. autoclass:: aiogram.types.chat_member.ChatMember - :members: - :show-inheritance: - - -ChatMemberStatus -================ - -.. autoclass:: aiogram.types.chat_member.ChatMemberStatus - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/chat_photo.rst b/docs/source/telegram/types/chat_photo.rst deleted file mode 100644 index 5ffe3e8f..00000000 --- a/docs/source/telegram/types/chat_photo.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.429407 - -========= -ChatPhoto -========= - -.. autoclass:: aiogram.types.chat_photo.ChatPhoto - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/chosen_inline_result.rst b/docs/source/telegram/types/chosen_inline_result.rst deleted file mode 100644 index f4ba2627..00000000 --- a/docs/source/telegram/types/chosen_inline_result.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.425583 - -================== -ChosenInlineResult -================== - -.. autoclass:: aiogram.types.chosen_inline_result.ChosenInlineResult - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/contact.rst b/docs/source/telegram/types/contact.rst deleted file mode 100644 index f383a881..00000000 --- a/docs/source/telegram/types/contact.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.430090 - -======= -Contact -======= - -.. autoclass:: aiogram.types.contact.Contact - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/document.rst b/docs/source/telegram/types/document.rst deleted file mode 100644 index f8b0a404..00000000 --- a/docs/source/telegram/types/document.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.404990 - -======== -Document -======== - -.. autoclass:: aiogram.types.document.Document - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/encrypted_credentials.rst b/docs/source/telegram/types/encrypted_credentials.rst deleted file mode 100644 index aa87e2ec..00000000 --- a/docs/source/telegram/types/encrypted_credentials.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.385369 - -==================== -EncryptedCredentials -==================== - -.. autoclass:: aiogram.types.encrypted_credentials.EncryptedCredentials - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/encrypted_passport_element.rst b/docs/source/telegram/types/encrypted_passport_element.rst deleted file mode 100644 index dcad9522..00000000 --- a/docs/source/telegram/types/encrypted_passport_element.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.397849 - -======================== -EncryptedPassportElement -======================== - -.. autoclass:: aiogram.types.encrypted_passport_element.EncryptedPassportElement - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/fields.rst b/docs/source/telegram/types/fields.rst deleted file mode 100644 index 9f5a5549..00000000 --- a/docs/source/telegram/types/fields.rst +++ /dev/null @@ -1,53 +0,0 @@ -====== -Fields -====== - - -BaseField -========= - -.. autoclass:: aiogram.types.fields.BaseField - :members: - :show-inheritance: - - -Field -===== - -.. autoclass:: aiogram.types.fields.Field - :members: - :show-inheritance: - - -ListField -========= - -.. autoclass:: aiogram.types.fields.ListField - :members: - :show-inheritance: - - -ListOfLists -=========== - -.. autoclass:: aiogram.types.fields.ListOfLists - :members: - :show-inheritance: - - -DateTimeField -============= - -.. autoclass:: aiogram.types.fields.DateTimeField - :members: - :show-inheritance: - - -TextField -========= - -.. autoclass:: aiogram.types.fields.TextField - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/file.rst b/docs/source/telegram/types/file.rst deleted file mode 100644 index 1040dff6..00000000 --- a/docs/source/telegram/types/file.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.400380 - -==== -File -==== - -.. autoclass:: aiogram.types.file.File - :members: - :show-inheritance: diff --git a/docs/source/telegram/types/force_reply.rst b/docs/source/telegram/types/force_reply.rst deleted file mode 100644 index dcc416d1..00000000 --- a/docs/source/telegram/types/force_reply.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.406260 - -========== -ForceReply -========== - -.. autoclass:: aiogram.types.force_reply.ForceReply - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/game.rst b/docs/source/telegram/types/game.rst deleted file mode 100644 index a60dc1c4..00000000 --- a/docs/source/telegram/types/game.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.399200 - -==== -Game -==== - -.. autoclass:: aiogram.types.game.Game - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/game_high_score.rst b/docs/source/telegram/types/game_high_score.rst deleted file mode 100644 index 8a3a6d66..00000000 --- a/docs/source/telegram/types/game_high_score.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.411808 - -============= -GameHighScore -============= - -.. autoclass:: aiogram.types.game_high_score.GameHighScore - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/index.rst b/docs/source/telegram/types/index.rst deleted file mode 100644 index 6ca1350a..00000000 --- a/docs/source/telegram/types/index.rst +++ /dev/null @@ -1,70 +0,0 @@ -=================== -Telegram data types -=================== - -Bases -===== - -.. toctree:: - - base - fields - mixins - - -Types -===== - -.. toctree:: - - sticker_set - encrypted_credentials - callback_query - successful_payment - message_entity - shipping_query - passport_data - inline_keyboard - user - video - encrypted_passport_element - game - file - labeled_price - callback_game - reply_keyboard - chat - document - audio - force_reply - passport_element_error - shipping_address - response_parameters - order_info - game_high_score - sticker - inline_query - location - animation - input_media - inline_query_result - input_file - pre_checkout_query - voice - input_message_content - update - photo_size - venue - chosen_inline_result - video_note - webhook_info - passport_file - chat_member - shipping_option - chat_photo - contact - message - mask_position - user_profile_photos - invoice - auth_widget_data diff --git a/docs/source/telegram/types/inline_keyboard.rst b/docs/source/telegram/types/inline_keyboard.rst deleted file mode 100644 index dd5e9994..00000000 --- a/docs/source/telegram/types/inline_keyboard.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.391643 - -==================== -InlineKeyboardMarkup -==================== - -.. autoclass:: aiogram.types.inline_keyboard.InlineKeyboardMarkup - :members: - :show-inheritance: - - -InlineKeyboardButton -==================== - -.. autoclass:: aiogram.types.inline_keyboard.InlineKeyboardButton - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/inline_query.rst b/docs/source/telegram/types/inline_query.rst deleted file mode 100644 index e9cd9e83..00000000 --- a/docs/source/telegram/types/inline_query.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.413503 - -=========== -InlineQuery -=========== - -.. autoclass:: aiogram.types.inline_query.InlineQuery - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/inline_query_result.rst b/docs/source/telegram/types/inline_query_result.rst deleted file mode 100644 index 518bf925..00000000 --- a/docs/source/telegram/types/inline_query_result.rst +++ /dev/null @@ -1,171 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.417480 - -================= -InlineQueryResult -================= - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResult - :members: - :show-inheritance: - - -InlineQueryResultArticle -======================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultArticle - :members: - :show-inheritance: - - -InlineQueryResultPhoto -====================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultPhoto - :members: - :show-inheritance: - - -InlineQueryResultGif -==================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultGif - :members: - :show-inheritance: - - -InlineQueryResultMpeg4Gif -========================= - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultMpeg4Gif - :members: - :show-inheritance: - - -InlineQueryResultVideo -====================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultVideo - :members: - :show-inheritance: - - -InlineQueryResultAudio -====================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultAudio - :members: - :show-inheritance: - - -InlineQueryResultVoice -====================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultVoice - :members: - :show-inheritance: - - -InlineQueryResultDocument -========================= - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultDocument - :members: - :show-inheritance: - - -InlineQueryResultLocation -========================= - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultLocation - :members: - :show-inheritance: - - -InlineQueryResultVenue -====================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultVenue - :members: - :show-inheritance: - - -InlineQueryResultContact -======================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultContact - :members: - :show-inheritance: - - -InlineQueryResultGame -===================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultGame - :members: - :show-inheritance: - - -InlineQueryResultCachedPhoto -============================ - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultCachedPhoto - :members: - :show-inheritance: - - -InlineQueryResultCachedGif -========================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultCachedGif - :members: - :show-inheritance: - - -InlineQueryResultCachedMpeg4Gif -=============================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultCachedMpeg4Gif - :members: - :show-inheritance: - - -InlineQueryResultCachedSticker -============================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultCachedSticker - :members: - :show-inheritance: - - -InlineQueryResultCachedDocument -=============================== - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultCachedDocument - :members: - :show-inheritance: - - -InlineQueryResultCachedVideo -============================ - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultCachedVideo - :members: - :show-inheritance: - - -InlineQueryResultCachedVoice -============================ - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultCachedVoice - :members: - :show-inheritance: - - -InlineQueryResultCachedAudio -============================ - -.. autoclass:: aiogram.types.inline_query_result.InlineQueryResultCachedAudio - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/input_file.rst b/docs/source/telegram/types/input_file.rst deleted file mode 100644 index 5c6f95e8..00000000 --- a/docs/source/telegram/types/input_file.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.419323 - -========= -InputFile -========= - -.. autoclass:: aiogram.types.input_file.InputFile - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/input_media.rst b/docs/source/telegram/types/input_media.rst deleted file mode 100644 index 186e5eb4..00000000 --- a/docs/source/telegram/types/input_media.rst +++ /dev/null @@ -1,59 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.416173 - -========== -InputMedia -========== - -.. autoclass:: aiogram.types.input_media.InputMedia - :members: - :show-inheritance: - - -InputMediaAnimation -=================== - -.. autoclass:: aiogram.types.input_media.InputMediaAnimation - :members: - :show-inheritance: - - -InputMediaDocument -================== - -.. autoclass:: aiogram.types.input_media.InputMediaDocument - :members: - :show-inheritance: - - -InputMediaAudio -=============== - -.. autoclass:: aiogram.types.input_media.InputMediaAudio - :members: - :show-inheritance: - - -InputMediaPhoto -=============== - -.. autoclass:: aiogram.types.input_media.InputMediaPhoto - :members: - :show-inheritance: - - -InputMediaVideo -=============== - -.. autoclass:: aiogram.types.input_media.InputMediaVideo - :members: - :show-inheritance: - - -MediaGroup -========== - -.. autoclass:: aiogram.types.input_media.MediaGroup - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/input_message_content.rst b/docs/source/telegram/types/input_message_content.rst deleted file mode 100644 index 60a79961..00000000 --- a/docs/source/telegram/types/input_message_content.rst +++ /dev/null @@ -1,43 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.421648 - -=================== -InputMessageContent -=================== - -.. autoclass:: aiogram.types.input_message_content.InputMessageContent - :members: - :show-inheritance: - - -InputContactMessageContent -========================== - -.. autoclass:: aiogram.types.input_message_content.InputContactMessageContent - :members: - :show-inheritance: - - -InputLocationMessageContent -=========================== - -.. autoclass:: aiogram.types.input_message_content.InputLocationMessageContent - :members: - :show-inheritance: - - -InputTextMessageContent -======================= - -.. autoclass:: aiogram.types.input_message_content.InputTextMessageContent - :members: - :show-inheritance: - - -InputVenueMessageContent -======================== - -.. autoclass:: aiogram.types.input_message_content.InputVenueMessageContent - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/invoice.rst b/docs/source/telegram/types/invoice.rst deleted file mode 100644 index 0f4c2344..00000000 --- a/docs/source/telegram/types/invoice.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.434241 - -======= -Invoice -======= - -.. autoclass:: aiogram.types.invoice.Invoice - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/labeled_price.rst b/docs/source/telegram/types/labeled_price.rst deleted file mode 100644 index f7152f9a..00000000 --- a/docs/source/telegram/types/labeled_price.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.401180 - -============ -LabeledPrice -============ - -.. autoclass:: aiogram.types.labeled_price.LabeledPrice - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/location.rst b/docs/source/telegram/types/location.rst deleted file mode 100644 index f163d33d..00000000 --- a/docs/source/telegram/types/location.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.414572 - -======== -Location -======== - -.. autoclass:: aiogram.types.location.Location - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/mask_position.rst b/docs/source/telegram/types/mask_position.rst deleted file mode 100644 index 4f9bfab9..00000000 --- a/docs/source/telegram/types/mask_position.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.432577 - -============ -MaskPosition -============ - -.. autoclass:: aiogram.types.mask_position.MaskPosition - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/message.rst b/docs/source/telegram/types/message.rst deleted file mode 100644 index 79616e57..00000000 --- a/docs/source/telegram/types/message.rst +++ /dev/null @@ -1,35 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.430779 - -======= -Message -======= - -.. autoclass:: aiogram.types.message.Message - :members: - :show-inheritance: - - -ContentType -=========== - -.. autoclass:: aiogram.types.message.ContentType - :members: - :show-inheritance: - - -ContentTypes -============ - -.. autoclass:: aiogram.types.message.ContentTypes - :members: - :show-inheritance: - - -ParseMode -========= - -.. autoclass:: aiogram.types.message.ParseMode - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/message_entity.rst b/docs/source/telegram/types/message_entity.rst deleted file mode 100644 index 46e7e6f1..00000000 --- a/docs/source/telegram/types/message_entity.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.388302 - -============= -MessageEntity -============= - -.. autoclass:: aiogram.types.message_entity.MessageEntity - :members: - :show-inheritance: - - -MessageEntityType -================= - -.. autoclass:: aiogram.types.message_entity.MessageEntityType - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/mixins.rst b/docs/source/telegram/types/mixins.rst deleted file mode 100644 index f7887a3b..00000000 --- a/docs/source/telegram/types/mixins.rst +++ /dev/null @@ -1,12 +0,0 @@ -====== -Mixins -====== - -Downloadable -============ - -.. autoclass:: aiogram.types.mixins.Downloadable - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/order_info.rst b/docs/source/telegram/types/order_info.rst deleted file mode 100644 index e2d3271a..00000000 --- a/docs/source/telegram/types/order_info.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.410818 - -========= -OrderInfo -========= - -.. autoclass:: aiogram.types.order_info.OrderInfo - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/passport_data.rst b/docs/source/telegram/types/passport_data.rst deleted file mode 100644 index fdec5848..00000000 --- a/docs/source/telegram/types/passport_data.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.390186 - -============ -PassportData -============ - -.. autoclass:: aiogram.types.passport_data.PassportData - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/passport_element_error.rst b/docs/source/telegram/types/passport_element_error.rst deleted file mode 100644 index e94ce9e3..00000000 --- a/docs/source/telegram/types/passport_element_error.rst +++ /dev/null @@ -1,59 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.406868 - -==================== -PassportElementError -==================== - -.. autoclass:: aiogram.types.passport_element_error.PassportElementError - :members: - :show-inheritance: - - -PassportElementErrorDataField -============================= - -.. autoclass:: aiogram.types.passport_element_error.PassportElementErrorDataField - :members: - :show-inheritance: - - -PassportElementErrorFile -======================== - -.. autoclass:: aiogram.types.passport_element_error.PassportElementErrorFile - :members: - :show-inheritance: - - -PassportElementErrorFiles -========================= - -.. autoclass:: aiogram.types.passport_element_error.PassportElementErrorFiles - :members: - :show-inheritance: - - -PassportElementErrorFrontSide -============================= - -.. autoclass:: aiogram.types.passport_element_error.PassportElementErrorFrontSide - :members: - :show-inheritance: - - -PassportElementErrorReverseSide -=============================== - -.. autoclass:: aiogram.types.passport_element_error.PassportElementErrorReverseSide - :members: - :show-inheritance: - - -PassportElementErrorSelfie -========================== - -.. autoclass:: aiogram.types.passport_element_error.PassportElementErrorSelfie - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/passport_file.rst b/docs/source/telegram/types/passport_file.rst deleted file mode 100644 index 70ecef19..00000000 --- a/docs/source/telegram/types/passport_file.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.427584 - -============ -PassportFile -============ - -.. autoclass:: aiogram.types.passport_file.PassportFile - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/photo_size.rst b/docs/source/telegram/types/photo_size.rst deleted file mode 100644 index 014b2782..00000000 --- a/docs/source/telegram/types/photo_size.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.424408 - -========= -PhotoSize -========= - -.. autoclass:: aiogram.types.photo_size.PhotoSize - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/pre_checkout_query.rst b/docs/source/telegram/types/pre_checkout_query.rst deleted file mode 100644 index ac4c49e1..00000000 --- a/docs/source/telegram/types/pre_checkout_query.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.420010 - -================ -PreCheckoutQuery -================ - -.. autoclass:: aiogram.types.pre_checkout_query.PreCheckoutQuery - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/reply_keyboard.rst b/docs/source/telegram/types/reply_keyboard.rst deleted file mode 100644 index 75963d52..00000000 --- a/docs/source/telegram/types/reply_keyboard.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.402352 - -=================== -ReplyKeyboardMarkup -=================== - -.. autoclass:: aiogram.types.reply_keyboard.ReplyKeyboardMarkup - :members: - :show-inheritance: - - -KeyboardButton -============== - -.. autoclass:: aiogram.types.reply_keyboard.KeyboardButton - :members: - :show-inheritance: - - -ReplyKeyboardRemove -=================== - -.. autoclass:: aiogram.types.reply_keyboard.ReplyKeyboardRemove - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/response_parameters.rst b/docs/source/telegram/types/response_parameters.rst deleted file mode 100644 index a3eb0140..00000000 --- a/docs/source/telegram/types/response_parameters.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.409659 - -================== -ResponseParameters -================== - -.. autoclass:: aiogram.types.response_parameters.ResponseParameters - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/shipping_address.rst b/docs/source/telegram/types/shipping_address.rst deleted file mode 100644 index 648e170a..00000000 --- a/docs/source/telegram/types/shipping_address.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.408101 - -=============== -ShippingAddress -=============== - -.. autoclass:: aiogram.types.shipping_address.ShippingAddress - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/shipping_option.rst b/docs/source/telegram/types/shipping_option.rst deleted file mode 100644 index 802e63cb..00000000 --- a/docs/source/telegram/types/shipping_option.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.428822 - -============== -ShippingOption -============== - -.. autoclass:: aiogram.types.shipping_option.ShippingOption - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/shipping_query.rst b/docs/source/telegram/types/shipping_query.rst deleted file mode 100644 index 3a335f85..00000000 --- a/docs/source/telegram/types/shipping_query.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.389286 - -============= -ShippingQuery -============= - -.. autoclass:: aiogram.types.shipping_query.ShippingQuery - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/sticker.rst b/docs/source/telegram/types/sticker.rst deleted file mode 100644 index e9706c1a..00000000 --- a/docs/source/telegram/types/sticker.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.412723 - -======= -Sticker -======= - -.. autoclass:: aiogram.types.sticker.Sticker - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/sticker_set.rst b/docs/source/telegram/types/sticker_set.rst deleted file mode 100644 index f5495580..00000000 --- a/docs/source/telegram/types/sticker_set.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.383921 - -========== -StickerSet -========== - -.. autoclass:: aiogram.types.sticker_set.StickerSet - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/successful_payment.rst b/docs/source/telegram/types/successful_payment.rst deleted file mode 100644 index d51bc8b3..00000000 --- a/docs/source/telegram/types/successful_payment.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.386887 - -================= -SuccessfulPayment -================= - -.. autoclass:: aiogram.types.successful_payment.SuccessfulPayment - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/update.rst b/docs/source/telegram/types/update.rst deleted file mode 100644 index 3b0baa3e..00000000 --- a/docs/source/telegram/types/update.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.422384 - -====== -Update -====== - -.. autoclass:: aiogram.types.update.Update - :members: - :show-inheritance: - - -AllowedUpdates -============== - -.. autoclass:: aiogram.types.update.AllowedUpdates - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/user.rst b/docs/source/telegram/types/user.rst deleted file mode 100644 index d3ef8326..00000000 --- a/docs/source/telegram/types/user.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.392947 - -==== -User -==== - -.. autoclass:: aiogram.types.user.User - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/user_profile_photos.rst b/docs/source/telegram/types/user_profile_photos.rst deleted file mode 100644 index 1d686910..00000000 --- a/docs/source/telegram/types/user_profile_photos.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.433355 - -================= -UserProfilePhotos -================= - -.. autoclass:: aiogram.types.user_profile_photos.UserProfilePhotos - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/venue.rst b/docs/source/telegram/types/venue.rst deleted file mode 100644 index cc61fbe5..00000000 --- a/docs/source/telegram/types/venue.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.424991 - -===== -Venue -===== - -.. autoclass:: aiogram.types.venue.Venue - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/video.rst b/docs/source/telegram/types/video.rst deleted file mode 100644 index 508f4c09..00000000 --- a/docs/source/telegram/types/video.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.396006 - -===== -Video -===== - -.. autoclass:: aiogram.types.video.Video - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/video_note.rst b/docs/source/telegram/types/video_note.rst deleted file mode 100644 index c0fbfb78..00000000 --- a/docs/source/telegram/types/video_note.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.426135 - -========= -VideoNote -========= - -.. autoclass:: aiogram.types.video_note.VideoNote - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/voice.rst b/docs/source/telegram/types/voice.rst deleted file mode 100644 index 7ec39d7a..00000000 --- a/docs/source/telegram/types/voice.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.420903 - -===== -Voice -===== - -.. autoclass:: aiogram.types.voice.Voice - :members: - :show-inheritance: - - diff --git a/docs/source/telegram/types/webhook_info.rst b/docs/source/telegram/types/webhook_info.rst deleted file mode 100644 index 36ba3eb7..00000000 --- a/docs/source/telegram/types/webhook_info.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. Autogenerated file at 2018-09-07 21:31:24.426969 - -=========== -WebhookInfo -=========== - -.. autoclass:: aiogram.types.webhook_info.WebhookInfo - :members: - :show-inheritance: - - diff --git a/docs/source/utils/auth_widget.rst b/docs/source/utils/auth_widget.rst deleted file mode 100644 index 95cb3913..00000000 --- a/docs/source/utils/auth_widget.rst +++ /dev/null @@ -1,6 +0,0 @@ -=========== -Auth Widget -=========== - -.. automodule:: aiogram.utils.auth_widget - :members: diff --git a/docs/source/utils/deprecated.rst b/docs/source/utils/deprecated.rst deleted file mode 100644 index 619224f8..00000000 --- a/docs/source/utils/deprecated.rst +++ /dev/null @@ -1,6 +0,0 @@ -========== -Deprecated -========== - -.. automodule:: aiogram.utils.deprecated - :members: diff --git a/docs/source/utils/emoji.rst b/docs/source/utils/emoji.rst deleted file mode 100644 index 1be210e3..00000000 --- a/docs/source/utils/emoji.rst +++ /dev/null @@ -1,6 +0,0 @@ -===== -Emoji -===== - -.. automodule:: aiogram.utils.emoji - :members: diff --git a/docs/source/utils/exceptions.rst b/docs/source/utils/exceptions.rst deleted file mode 100644 index b296afd3..00000000 --- a/docs/source/utils/exceptions.rst +++ /dev/null @@ -1,6 +0,0 @@ -========== -Exceptions -========== - -.. automodule:: aiogram.utils.exceptions - :members: diff --git a/docs/source/utils/executor.rst b/docs/source/utils/executor.rst deleted file mode 100644 index f88dd8c5..00000000 --- a/docs/source/utils/executor.rst +++ /dev/null @@ -1,7 +0,0 @@ -======== -Executor -======== - -.. automodule:: aiogram.utils.executor - :members: - diff --git a/docs/source/utils/helper.rst b/docs/source/utils/helper.rst deleted file mode 100644 index ba8bf016..00000000 --- a/docs/source/utils/helper.rst +++ /dev/null @@ -1,6 +0,0 @@ -====== -Helper -====== - -.. automodule:: aiogram.utils.helper - :members: diff --git a/docs/source/utils/index.rst b/docs/source/utils/index.rst deleted file mode 100644 index 1ac3777c..00000000 --- a/docs/source/utils/index.rst +++ /dev/null @@ -1,15 +0,0 @@ -Utils -===== - -.. toctree:: - - auth_widget - executor - exceptions - markdown - helper - deprecated - payload - parts - json - emoji diff --git a/docs/source/utils/json.rst b/docs/source/utils/json.rst deleted file mode 100644 index 68577ff4..00000000 --- a/docs/source/utils/json.rst +++ /dev/null @@ -1,6 +0,0 @@ -==== -JSON -==== - -.. automodule:: aiogram.utils.json - :members: diff --git a/docs/source/utils/markdown.rst b/docs/source/utils/markdown.rst deleted file mode 100644 index bcbe0497..00000000 --- a/docs/source/utils/markdown.rst +++ /dev/null @@ -1,6 +0,0 @@ -======== -Markdown -======== - -.. automodule:: aiogram.utils.markdown - :members: diff --git a/docs/source/utils/parts.rst b/docs/source/utils/parts.rst deleted file mode 100644 index fd2e91de..00000000 --- a/docs/source/utils/parts.rst +++ /dev/null @@ -1,6 +0,0 @@ -===== -Parts -===== - -.. automodule:: aiogram.utils.parts - :members: diff --git a/docs/source/utils/payload.rst b/docs/source/utils/payload.rst deleted file mode 100644 index e3e0331a..00000000 --- a/docs/source/utils/payload.rst +++ /dev/null @@ -1,6 +0,0 @@ -======= -Payload -======= - -.. automodule:: aiogram.utils.payload - :members: diff --git a/environment.yml b/environment.yml deleted file mode 100644 index a93281f9..00000000 --- a/environment.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: py37 -# channels: -# - conda-forge -dependencies: - - python=3.7 - - sphinx - - sphinx_rtd_theme - - pip - - openssl - - xz - - setuptools - - wheel - - zlib - - babel - - docutils - - freetype - - imagesize - - jbig - - jinja2 - - jpeg - - libpng - - libtiff - - markupsafe - - mock - - olefile - - pbr - - pillow - - pygments - - pytz - - requests - - six - - snowballstemmer - - sphinx - - sqlite - - xz - - aiohttp diff --git a/generator/__init__.py b/generator/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/generator/__main__.py b/generator/__main__.py deleted file mode 100644 index 5b846972..00000000 --- a/generator/__main__.py +++ /dev/null @@ -1,7 +0,0 @@ -import logging -import sys - -from generator.cli import main - -if __name__ == "__main__": - sys.exit(main(sys.argv)) diff --git a/generator/cli.py b/generator/cli.py deleted file mode 100644 index e2263073..00000000 --- a/generator/cli.py +++ /dev/null @@ -1,22 +0,0 @@ -import logging -import pathlib -import sys -import typing - -from generator.generator import Generator -from generator.parser import Parser - -script_path = pathlib.Path(__file__).parent -out_dir = script_path.parent / "aiogram" / "_telegram" - - -def main(argv: typing.List[str]) -> int: - logging.basicConfig(level=logging.ERROR, stream=sys.stdout) - parser = Parser() - parser.parse() - generator = Generator(parser) - - with (out_dir / "types.py").open("w") as f: - f.write(generator.render_types()) - - return 0 diff --git a/generator/consts.py b/generator/consts.py deleted file mode 100644 index f5fa22ca..00000000 --- a/generator/consts.py +++ /dev/null @@ -1,32 +0,0 @@ -import re - -DOCS_URL = "https://core.telegram.org/bots/api" - -RE_FLAGS = re.IGNORECASE -ANCHOR_HEADER_PATTERN = re.compile(r"^h([34])$") -RETURN_PATTERNS = [ - re.compile(r"(?PArray of [a-z]+) objects", flags=RE_FLAGS), - re.compile(r"a (?P[a-z]+) object", flags=RE_FLAGS), - re.compile(r"Returns (?P[a-z]+) on success", flags=RE_FLAGS), - re.compile(r"(?P[a-z]+) on success", flags=RE_FLAGS), - re.compile( - r"(?P[a-z]+) is returned, otherwise (?P[a-zA-Z]+) is returned", flags=RE_FLAGS - ), - re.compile( - r"returns the edited (?P[a-z]+), otherwise returns (?P[a-zA-Z]+)", - flags=RE_FLAGS, - ), - re.compile(r"(?P[a-z]+) is returned", flags=RE_FLAGS), - re.compile(r"Returns (?P[a-z]+)", flags=RE_FLAGS), -] -BUILTIN_TYPES = { - "String": "str", - "Integer": "int", - "Float": "float", - "Boolean": "bool", - "InputFile": "types.InputFile", -} -READ_MORE_PATTERN = re.compile( - r" ((More info on|More about)([\W\w]+»)|»)", flags=re.MULTILINE & re.IGNORECASE -) -SYMBOLS_MAP = {"“": "'", "”": "'"} diff --git a/generator/generator.py b/generator/generator.py deleted file mode 100644 index df425d6d..00000000 --- a/generator/generator.py +++ /dev/null @@ -1,34 +0,0 @@ -import datetime -import pathlib - -import black -import jinja2 - -from generator.parser import Parser - -templates_dir: pathlib.Path = pathlib.Path(__file__).parent / "templates" - - -class Generator: - def __init__(self, parser: Parser): - self.parser = parser - self.env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=[templates_dir])) - - @property - def context(self): - return { - "groups": self.parser.groups, - "timestamp": datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC"), - } - - def _render_template(self, template: str) -> str: - template = self.env.get_template(template) - content = template.render(self.context) - return content - - def _reformat_code(self, code: str) -> str: - return black.format_str(code, mode=black.FileMode()) - - def render_types(self): - content = self._render_template("types.py.jinja2") - return self._reformat_code(content) diff --git a/generator/normalizers.py b/generator/normalizers.py deleted file mode 100644 index 7d260809..00000000 --- a/generator/normalizers.py +++ /dev/null @@ -1,92 +0,0 @@ -import functools - -from generator.consts import BUILTIN_TYPES, RETURN_PATTERNS, READ_MORE_PATTERN, SYMBOLS_MAP - - -def normalize_description(text: str) -> str: - for bad, good in SYMBOLS_MAP.items(): - text = text.replace(bad, good) - text = READ_MORE_PATTERN.sub("", text) - text.strip() - return text - - -def normalize_annotation(item: dict): - for key in list(item.keys()): - item[key.lower()] = item.pop(key) - - item["description"] = normalize_description(item["description"]) - - -def normalize_method_annotation(item: dict): - normalize_annotation(item) - item["required"] = {"Optional": False, "Yes": True}[item["required"]] - item["name"] = item.pop("parameter") - - -def normalize_type_annotation(item: dict): - normalize_annotation(item) - - item["name"] = item.pop("field") - - if item["description"].startswith("Optional"): - item["required"] = False - item["description"] = item["description"][10:] - else: - item["required"] = True - - -@functools.lru_cache() -def normalize_type(string): - if not string: - return "typing.Any" - - lower = string.lower() - split = lower.split() - - if split[0] == "array": - new_string = string[lower.index("of") + 2 :].strip() - return f"typing.List[{normalize_type(new_string)}]" - if "or" in split: - split_types = string.split(" or ") - norm_str = ", ".join(map(normalize_type, map(str.strip, split_types))) - return f"typing.Union[{norm_str}]" - if "number" in lower: - return normalize_type(string.replace("number", "").strip()) - if lower in ["true", "false"]: - return "bool" - if string not in BUILTIN_TYPES and string[0].isupper(): - return f"types.{string}" - elif string in BUILTIN_TYPES: - return BUILTIN_TYPES[string] - return "typing.Any" - - -@functools.lru_cache() -def get_returning(description): - parts = list(filter(lambda item: "return" in item.lower(), description.split("."))) - if not parts: - return "typing.Any", "" - sentence = ". ".join(map(str.strip, parts)) - return_type = None - - for pattern in RETURN_PATTERNS: - temp = pattern.search(sentence) - if temp: - return_type = temp.group("type") - if "other" in temp.groupdict(): - otherwise = temp.group("other") - return_type += f" or {otherwise}" - if return_type: - break - - return return_type, sentence + "." - - -def normalize_optional(python_type: str, required: bool = True) -> str: - if not required: - if "typing.Union" in python_type: - python_type = python_type.replace("typing.Union", "typing.Optional") - else: - python_type = f"typing.Optional[{python_type}]" - return python_type diff --git a/generator/parser.py b/generator/parser.py deleted file mode 100644 index c5150e51..00000000 --- a/generator/parser.py +++ /dev/null @@ -1,134 +0,0 @@ -import logging - -import requests -from lxml import html -from lxml.html import HtmlElement - -from generator.consts import DOCS_URL, ANCHOR_HEADER_PATTERN -from generator.normalizers import ( - normalize_type_annotation, - normalize_method_annotation, - normalize_description, -) -from generator.structures import Group, Entity, Annotation - -log = logging.getLogger(__name__) - - -class Parser: - def __init__(self): - self.docs = self.load(DOCS_URL) - self.groups = [] - - @staticmethod - def load_page(url: str) -> str: - log.info("Load page %r", url) - response = requests.get(url) - response.raise_for_status() - return response.text - - @staticmethod - def to_html(content: str, url: str) -> HtmlElement: - page = html.fromstring(content, url) - - for br in page.xpath("*//br"): - br.tail = "\n" + br.tail if br.tail else "\n" - - return page - - def load(self, url: str) -> HtmlElement: - content = self.load_page(url) - return self.to_html(content, url) - - def optimize_group(self, group: Group): - if not group.childs: - log.warning("Remove empty %s", group) - self.groups.remove(group) - return - - if not group.childs[0].annotations: - log.warning("Update group %r description from first child element", group.title) - group.description = group.childs[0].description - group.childs.pop(0) - - def parse(self): - self.groups.clear() - - group = None - - for item in self.docs.xpath("//a[@class='anchor']"): # type: HtmlElement - parent_tag: HtmlElement = item.getparent() - anchor_name = item.get("name", None) - matches = ANCHOR_HEADER_PATTERN.match(parent_tag.tag) - if not matches or not anchor_name: - continue - level = int(matches.group(1)) - title = item.tail - - if level == 3: - if group: - self.optimize_group(group) - - log.info("Parse group %r (#%s)", title, anchor_name) - group = Group(title=title, anchor=anchor_name) - self.groups.append(group) - - if level == 4 and len(title.split()) > 1: - continue - - elif anchor_name not in ["recent-changes", "authorizing-your-bot", "making-requests"]: - child = self._parse_child(parent_tag, anchor_name) - group.childs.append(child) - - return self.groups - - def _parse_child(self, start_tag: HtmlElement, anchor: str): - name = start_tag.text_content() - description = [] - annotations = [] - - is_method = name[0].islower() - - log.info("Parse block: %r (#%s)", name, anchor) - - for item in self._parse_tags_group(start_tag): - if item.tag == "table": - for raw in self._parse_table(item): - if is_method: - normalize_method_annotation(raw) - else: - normalize_type_annotation(raw) - annotations.append(Annotation(**raw)) - - elif item.tag == "p": - description.extend(item.text_content().splitlines()) - elif item.tag == "blockquote": - description.extend(self._parse_blockquote(item)) - elif item.tag == "ul": - description.extend(self._parse_list(item)) - - description = normalize_description("\n".join(description)) - block = Entity(anchor=anchor, name=name, description=description, annotations=annotations) - log.info("%s", block) - return block - - def _parse_tags_group(self, start_tag: HtmlElement): - tag: HtmlElement = start_tag.getnext() - while tag is not None and tag.tag not in ["h3", "h4"]: - yield tag - tag: HtmlElement = tag.getnext() - - def _parse_table(self, table: HtmlElement): - head, body = table.getchildren() # type: HtmlElement, HtmlElement - header = [item.text_content() for item in head.getchildren()[0]] - - for body_item in body: - yield {k: v for k, v in zip(header, [item.text_content() for item in body_item])} - - def _parse_blockquote(self, blockquote: HtmlElement): - for item in blockquote.getchildren(): - yield from item.text_content().splitlines() - - def _parse_list(self, data: HtmlElement): - for item in data.getchildren(): - yield " - " + item.text_content() diff --git a/generator/structures.py b/generator/structures.py deleted file mode 100644 index 8b5d720e..00000000 --- a/generator/structures.py +++ /dev/null @@ -1,91 +0,0 @@ -from __future__ import annotations - -import typing -from dataclasses import dataclass, field - -from generator.normalizers import normalize_type, get_returning, normalize_optional - - -@dataclass -class Annotation: - name: str - type: str - description: str - required: bool = True - - @property - def python_name(self): - if self.name == "from": - return "from_user" - return self.name - - @property - def python_type(self) -> str: - result = normalize_type(self.type) - return normalize_optional(result, self.required) - - @property - def python_field(self): - result = f"{self.python_name}: {self.python_type}" - - value = "" if self.required else "None" - if self.name == "from": - value = f"pydantic.Schema({value or '...'}, alias=\"from\")" - - if value: - result += f" = {value}" - return result - - -@dataclass -class Entity: - name: str - anchor: str - description: str = None - annotations: typing.List[Annotation] = field(default_factory=list) - - @property - def is_method(self) -> bool: - return self.name[0].islower() - - @property - def is_type(self) -> bool: - return not self.is_method - - @property - def python_name(self) -> str: - return self.name - - def _get_returning(self): - if self.is_type: - return self.name, "" - - return get_returning(self.description) - - @property - def returning(self): - return self._get_returning()[1] - - @property - def returning_type(self): - return self._get_returning()[0] - - @property - def python_returning_type(self): - return normalize_type(self.returning_type) - - -@dataclass -class Group: - title: str - anchor: str - description: str = None - childs: typing.List[Entity] = field(default_factory=list) - - @property - def has_methods(self): - return any(entity.is_method for entity in self.childs) - - @property - def has_types(self): - return any(entity.is_method for entity in self.childs) diff --git a/generator/templates/type.py.jinja2 b/generator/templates/type.py.jinja2 deleted file mode 100644 index 2c3bd694..00000000 --- a/generator/templates/type.py.jinja2 +++ /dev/null @@ -1,12 +0,0 @@ -class {{ entity.python_name }}(pydantic.BaseModel): - """ - {{ entity.description|indent(width=4) }} - - Source: https://core.telegram.org/bots/api#{{ entity.anchor }} - """ -{% for annotation in entity.annotations %} - {{ annotation.python_field }} - """{{ annotation.description|indent(width=4) }}""" -{% else %} - pass -{% endfor %} diff --git a/generator/templates/types.py.jinja2 b/generator/templates/types.py.jinja2 deleted file mode 100644 index cd5cd73a..00000000 --- a/generator/templates/types.py.jinja2 +++ /dev/null @@ -1,20 +0,0 @@ -""" -!!! DO NOT EDIT THIS FILE !!! -This file is autogenerated from Docs of Telegram Bot API at {{ timestamp }} -""" -import typing - -import pydantic - -from aiogram import types - -__all__ = [ -{% for group in groups %}{% for entity in group.childs %}{% if entity.is_type %} - "{{ entity.python_name }}", -{% endif %}{% endfor %}{% endfor %} -] - - -{% for group in groups %} -{% include 'types_group.py.jinja2' %} -{% endfor %} diff --git a/generator/templates/types_group.py.jinja2 b/generator/templates/types_group.py.jinja2 deleted file mode 100644 index d25fda26..00000000 --- a/generator/templates/types_group.py.jinja2 +++ /dev/null @@ -1,10 +0,0 @@ -# %% Region: '{{ group.title }}' -"""{% if group.description %} -{{ group.description }} -{% endif %} -link: https://core.telegram.org/bots/api#{{ group.anchor }} -""" -{% for entity in group.childs %}{% if entity.is_type %} -{% include 'type.py.jinja2' %} -{% endif %}{% endfor %} -# %% End of region '{{ group.title }}' diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..35b80efe --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,28 @@ +site_name: aiogram +site_description: 'Documentations of aiogram framework' +site_author: 'Alex Root Junior' +site_url: 'https://illemius.xyz' +repo_name: 'aiogram/aiogram' +repo_url: 'https://github.com/aiogram/aiogram' + +theme: + name: 'material' + language: 'en' + palette: + primary: 'blue' + accent: 'blue' + favicon: 'assets/images/logo.png' + logo: 'assets/images/logo.png' + +plugins: + - search +markdown_extensions: + - admonition + - codehilite + - footnotes + - toc: + permalink: true + +nav: + - index.md + - install.md diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..2b27629c --- /dev/null +++ b/poetry.lock @@ -0,0 +1,585 @@ +[[package]] +category = "main" +description = "Async http client/server framework (asyncio)" +name = "aiohttp" +optional = false +python-versions = ">=3.5.3" +version = "3.6.2" + +[package.dependencies] +async-timeout = ">=3.0,<4.0" +attrs = ">=17.3.0" +chardet = ">=2.0,<4.0" +multidict = ">=4.5,<5.0" +yarl = ">=1.0,<2.0" + +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.3" + +[[package]] +category = "dev" +description = "Asyncio testing server. Similar to the responses library used for 'requests'" +name = "aresponses" +optional = false +python-versions = "*" +version = "1.1.1" + +[package.dependencies] +aiohttp = ">=3.1.0,<4.0.0" +pytest-asyncio = "*" + +[[package]] +category = "main" +description = "Timeout context manager for asyncio programs" +name = "async-timeout" +optional = false +python-versions = ">=3.5.3" +version = "3.0.1" + +[[package]] +category = "dev" +description = "Atomic file writes." +name = "atomicwrites" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.3.0" + +[[package]] +category = "main" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.3.0" + +[[package]] +category = "main" +description = "Internationalization utilities" +name = "babel" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.7.0" + +[package.dependencies] +pytz = ">=2015.7" + +[[package]] +category = "dev" +description = "The uncompromising code formatter." +name = "black" +optional = false +python-versions = ">=3.6" +version = "18.9b0" + +[package.dependencies] +appdirs = "*" +attrs = ">=17.4.0" +click = ">=6.5" +toml = ">=0.9.4" + +[[package]] +category = "main" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" +optional = false +python-versions = "*" +version = "3.0.4" + +[[package]] +category = "dev" +description = "Composable command line interface toolkit" +name = "click" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "7.0" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.4.1" + +[[package]] +category = "dev" +description = "Discover and load entry points from installed packages." +name = "entrypoints" +optional = false +python-versions = ">=2.7" +version = "0.3" + +[[package]] +category = "dev" +description = "the modular source code checker: pep8, pyflakes and co" +name = "flake8" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.7.9" + +[package.dependencies] +entrypoints = ">=0.3.0,<0.4.0" +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.5.0,<2.6.0" +pyflakes = ">=2.1.0,<2.2.0" + +[[package]] +category = "dev" +description = "An HTML Minifier" +name = "htmlmin" +optional = false +python-versions = "*" +version = "0.1.12" + +[[package]] +category = "main" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.8" + +[[package]] +category = "dev" +description = "Read metadata from Python packages" +marker = "python_version < \"3.8\"" +name = "importlib-metadata" +optional = false +python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" +version = "0.23" + +[package.dependencies] +zipp = ">=0.5" + +[[package]] +category = "dev" +description = "A Python utility / library to sort Python imports." +name = "isort" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "4.3.21" + +[[package]] +category = "dev" +description = "A very fast and expressive template engine." +name = "jinja2" +optional = false +python-versions = "*" +version = "2.10.3" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[[package]] +category = "dev" +description = "JavaScript minifier." +name = "jsmin" +optional = false +python-versions = "*" +version = "2.2.2" + +[[package]] +category = "dev" +description = "Python LiveReload is an awesome tool for web developers" +name = "livereload" +optional = false +python-versions = "*" +version = "2.6.1" + +[package.dependencies] +six = "*" +tornado = "*" + +[[package]] +category = "dev" +description = "Python implementation of Markdown." +name = "markdown" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "3.1.1" + +[package.dependencies] +setuptools = ">=36" + +[[package]] +category = "dev" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" + +[[package]] +category = "dev" +description = "McCabe checker, plugin for flake8" +name = "mccabe" +optional = false +python-versions = "*" +version = "0.6.1" + +[[package]] +category = "dev" +description = "Project documentation with Markdown." +name = "mkdocs" +optional = false +python-versions = ">=2.7.9,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.0.4" + +[package.dependencies] +Jinja2 = ">=2.7.1" +Markdown = ">=2.3.1" +PyYAML = ">=3.10" +click = ">=3.3" +livereload = ">=2.5.1" +tornado = ">=5.0" + +[[package]] +category = "dev" +description = "A Material Design theme for MkDocs" +name = "mkdocs-material" +optional = false +python-versions = "*" +version = "4.4.3" + +[package.dependencies] +Pygments = ">=2.2" +mkdocs = ">=1" +mkdocs-minify-plugin = ">=0.2" +pymdown-extensions = ">=4.11" + +[[package]] +category = "dev" +description = "An MkDocs plugin to minify HTML and/or JS files prior to being written to disk" +name = "mkdocs-minify-plugin" +optional = false +python-versions = ">=2.7" +version = "0.2.1" + +[package.dependencies] +htmlmin = ">=0.1.4" +jsmin = ">=2.2.2" +mkdocs = ">=1.0.4" + +[[package]] +category = "dev" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = false +python-versions = ">=3.4" +version = "7.2.0" + +[[package]] +category = "main" +description = "multidict implementation" +name = "multidict" +optional = false +python-versions = ">=3.4.1" +version = "4.5.2" + +[[package]] +category = "dev" +description = "Optional static typing for Python" +name = "mypy" +optional = false +python-versions = ">=3.5" +version = "0.740" + +[package.dependencies] +mypy-extensions = ">=0.4.0,<0.5.0" +typed-ast = ">=1.4.0,<1.5.0" +typing-extensions = ">=3.7.4" + +[[package]] +category = "dev" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +name = "mypy-extensions" +optional = false +python-versions = "*" +version = "0.4.3" + +[[package]] +category = "dev" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.2" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +category = "dev" +description = "Backport of PEP 562." +name = "pep562" +optional = false +python-versions = "*" +version = "1.0" + +[[package]] +category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.0" + +[package.dependencies] +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.8.0" + +[[package]] +category = "dev" +description = "Python style guide checker" +name = "pycodestyle" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.5.0" + +[[package]] +category = "main" +description = "Data validation and settings management using python 3.6 type hinting" +name = "pydantic" +optional = false +python-versions = ">=3.6" +version = "1.1" + +[[package]] +category = "dev" +description = "passive checker of Python programs" +name = "pyflakes" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.1.1" + +[[package]] +category = "dev" +description = "Pygments is a syntax highlighting package written in Python." +name = "pygments" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.4.2" + +[[package]] +category = "dev" +description = "Extension pack for Python Markdown." +name = "pymdown-extensions" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "6.1" + +[package.dependencies] +Markdown = ">=3.0.1" +pep562 = "*" + +[[package]] +category = "dev" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.5" + +[[package]] +category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +python-versions = ">=3.5" +version = "5.2.3" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +colorama = "*" +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[[package]] +category = "dev" +description = "Pytest support for asyncio." +name = "pytest-asyncio" +optional = false +python-versions = ">= 3.5" +version = "0.10.0" + +[package.dependencies] +pytest = ">=3.0.6" + +[[package]] +category = "main" +description = "World timezone definitions, modern and historical" +name = "pytz" +optional = false +python-versions = "*" +version = "2019.3" + +[[package]] +category = "dev" +description = "YAML parser and emitter for Python" +name = "pyyaml" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "5.1.2" + +[[package]] +category = "dev" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "1.13.0" + +[[package]] +category = "dev" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = false +python-versions = "*" +version = "0.10.0" + +[[package]] +category = "dev" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +name = "tornado" +optional = false +python-versions = ">= 3.5" +version = "6.0.3" + +[[package]] +category = "dev" +description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "typed-ast" +optional = false +python-versions = "*" +version = "1.4.0" + +[[package]] +category = "dev" +description = "Backported and Experimental Type Hints for Python 3.5+" +name = "typing-extensions" +optional = false +python-versions = "*" +version = "3.7.4.1" + +[[package]] +category = "dev" +description = "Fast implementation of asyncio event loop on top of libuv" +name = "uvloop" +optional = false +python-versions = "*" +version = "0.14.0" + +[[package]] +category = "dev" +description = "Measures number of Terminal column cells of wide-character codes" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.1.7" + +[[package]] +category = "main" +description = "Yet another URL library" +name = "yarl" +optional = false +python-versions = ">=3.5.3" +version = "1.3.0" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +category = "dev" +description = "Backport of pathlib-compatible object wrapper for zip files" +marker = "python_version < \"3.8\"" +name = "zipp" +optional = false +python-versions = ">=2.7" +version = "0.6.0" + +[package.dependencies] +more-itertools = "*" + +[metadata] +content-hash = "39221d7aa0d3e63fb6e8ab693e36ec6a9af686f3b23aadbdbfc5c58722f51cca" +python-versions = "^3.7" + +[metadata.hashes] +aiohttp = ["1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", "259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", "2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", "32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", "344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", "460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", "4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", "50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", "6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", "65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", "ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", "b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"] +appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] +aresponses = ["d1d6ef52b9a97142d106688cf9b112602ef3dc66f6368de8f91f47241d8cfc9c", "f62bcdd739612b6254dd552467b5897a81dcf785e4bb48463bf71e40df398580"] +async-timeout = ["0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", "4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"] +atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] +attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"] +babel = ["af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", "e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"] +black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"] +chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] +click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] +colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] +entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] +flake8 = ["45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", "49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"] +htmlmin = ["50c1ef4630374a5d723900096a961cff426dff46b48f34d194a81bbe14eca178"] +idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] +importlib-metadata = ["aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"] +isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"] +jinja2 = ["74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", "9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"] +jsmin = ["b6df99b2cd1c75d9d342e4335b535789b8da9107ec748212706ef7bbe5c2553b"] +livereload = ["78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b", "89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"] +markdown = ["2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a", "56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"] +markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] +mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] +mkdocs = ["17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939", "8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"] +mkdocs-material = ["a5246b550299d00a135a3f739e70ac6db73d7127480f0fecbda113d0095a674a", "e4a9ac73db7c65fdae1dbd248091e4b0a3f5db3e6bf87a46bb457db013a045e4"] +mkdocs-minify-plugin = ["3000a5069dd0f42f56a8aaf7fd5ea1222c67487949617e39585d6b6434b074b6", "d54fdd5be6843dd29fd7af2f7fdd20a9eb4db46f1f6bed914e03b2f58d2d488e"] +more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"] +multidict = ["024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", "041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3", "045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef", "047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b", "068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73", "148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc", "1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3", "1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd", "31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351", "34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941", "3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d", "4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1", "4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b", "4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a", "5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3", "61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7", "6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0", "76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0", "7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014", "7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5", "7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036", "8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d", "8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a", "c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce", "c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1", "ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a", "d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9", "d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7", "db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"] +mypy = ["1521c186a3d200c399bd5573c828ea2db1362af7209b2adb1bb8532cea2fb36f", "31a046ab040a84a0fc38bc93694876398e62bc9f35eca8ccbf6418b7297f4c00", "3b1a411909c84b2ae9b8283b58b48541654b918e8513c20a400bb946aa9111ae", "48c8bc99380575deb39f5d3400ebb6a8a1cb5cc669bbba4d3bb30f904e0a0e7d", "540c9caa57a22d0d5d3c69047cc9dd0094d49782603eb03069821b41f9e970e9", "672e418425d957e276c291930a3921b4a6413204f53fe7c37cad7bc57b9a3391", "6ed3b9b3fdc7193ea7aca6f3c20549b377a56f28769783a8f27191903a54170f", "9371290aa2cad5ad133e4cdc43892778efd13293406f7340b9ffe99d5ec7c1d9", "ace6ac1d0f87d4072f05b5468a084a45b4eda970e4d26704f201e06d47ab2990", "b428f883d2b3fe1d052c630642cc6afddd07d5cd7873da948644508be3b9d4a7", "d5bf0e6ec8ba346a2cf35cb55bf4adfddbc6b6576fcc9e10863daa523e418dbb", "d7574e283f83c08501607586b3167728c58e8442947e027d2d4c7dcd6d82f453", "dc889c84241a857c263a2b1cd1121507db7d5b5f5e87e77147097230f374d10b", "f4748697b349f373002656bf32fede706a0e713d67bfdcf04edf39b1f61d46eb"] +mypy-extensions = ["090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", "2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"] +packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"] +pep562 = ["58cb1cc9ee63d93e62b4905a50357618d526d289919814bea1f0da8f53b79395", "d2a48b178ebf5f8dd31709cc26a19808ef794561fa2fe50ea01ea2bad4d667ef"] +pluggy = ["0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", "fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"] +py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"] +pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] +pydantic = ["2588e5c68b7927fd44c4bf2c5768cdaff7948fc4ae0e3f602376b76b1913c3b0", "2d8d8c14b6349066e27aca9bfd3fb973fb2c45a860a6ba83fbea16ee42efbfd1", "40b62f5e8db4957221e4aa06cea015c4a74740405edea0e11def4c6418e7c62c", "43f8ac74e114aaac0d844040fea971fc6c53434f24cee2e1aa0486e6601817b4", "4f3baf0b4ef8f68021186448efddbcc259ed473e18acf4433ef75dd13d795594", "f85a63028003d8778dc3bf27c5c5cd25da64593422ab82a696cca32b1ce3e750"] +pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] +pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] +pymdown-extensions = ["24c1a0afbae101c4e2b2675ff4dd936470a90133f93398b9cbe0c855e2d2ec10", "960486dea995f1759dfd517aa140b3d851cd7b44d4c48d276fd2c74fc4e1bce9"] +pyparsing = ["20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", "4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a"] +pytest = ["15837d2880cb94821087bc07476892ea740696b20e90288fd6c19e44b435abdb", "b6cf7ad9064049ee486586b3a0ddd70dc5136c40e1147e7d286efd77ba66c5eb"] +pytest-asyncio = ["9fac5100fd716cbecf6ef89233e8590a4ad61d729d1732e0a96b84182df1daaf", "d734718e25cfc32d2bf78d346e99d33724deeba774cc4afdf491530c6184b63b"] +pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"] +pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"] +six = ["1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", "30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"] +toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] +tornado = ["349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", "398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60", "4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281", "559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5", "abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7", "c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9", "c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"] +typed-ast = ["1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", "838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"] +typing-extensions = ["091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", "910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", "cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"] +uvloop = ["08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd", "123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e", "4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09", "4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726", "afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891", "b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7", "bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5", "e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95", "f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"] +wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] +yarl = ["024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9", "2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f", "3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb", "3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320", "5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842", "73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0", "7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829", "b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310", "c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4", "c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8", "e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"] +zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 7f7dc1ac..00000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -aiohttp>=3.5.4,<4.0.0 -Babel>=2.6.0 -certifi>=2019.3.9 diff --git a/setup.py b/setup.py deleted file mode 100755 index a7876f96..00000000 --- a/setup.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 -import pathlib -import re -import sys - -from setuptools import find_packages, setup - -try: - from pip.req import parse_requirements -except ImportError: # pip >= 10.0.0 - from pip._internal.req import parse_requirements - -WORK_DIR = pathlib.Path(__file__).parent - -# Check python version -MINIMAL_PY_VERSION = (3, 7) -if sys.version_info < MINIMAL_PY_VERSION: - raise RuntimeError('aiogram works only with Python {}+'.format('.'.join(map(str, MINIMAL_PY_VERSION)))) - - -def get_version(): - """ - Read version - - :return: str - """ - txt = (WORK_DIR / 'aiogram' / '__init__.py').read_text('utf-8') - try: - return re.findall(r"^__version__ = '([^']+)'\r?$", txt, re.M)[0] - except IndexError: - raise RuntimeError('Unable to determine version.') - - -def get_description(): - """ - Read full description from 'README.rst' - - :return: description - :rtype: str - """ - with open('README.rst', 'r', encoding='utf-8') as f: - return f.read() - - -def get_requirements(filename=None): - """ - Read requirements from 'requirements txt' - - :return: requirements - :rtype: list - """ - if filename is None: - filename = 'requirements.txt' - - file = WORK_DIR / filename - - install_reqs = parse_requirements(str(file), session='hack') - return [str(ir.req) for ir in install_reqs] - - -setup( - name='aiogram', - version=get_version(), - packages=find_packages(exclude=('tests', 'tests.*', 'examples.*', 'docs',)), - url='https://github.com/aiogram/aiogram', - license='MIT', - author='Alex Root Junior', - requires_python='>=3.7', - author_email='jroot.junior@gmail.com', - description='Is a pretty simple and fully asynchronous framework for Telegram Bot API', - long_description=get_description(), - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Framework :: AsyncIO', - 'Intended Audience :: Developers', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3.7', - 'Topic :: Software Development :: Libraries :: Application Frameworks', - ], - install_requires=get_requirements(), - package_data={'': ['requirements.txt']}, - include_package_data=False, -) diff --git a/tests/test_bot.py b/tests/test_bot.py index 3e48ea57..b7cf3250 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -3,7 +3,7 @@ import pytest from aiogram import Bot, types -TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff-1234567890' +TOKEN = "123456789:AABBCCDDEEFFaabbccddeeff-1234567890" class FakeTelegram(aresponses.ResponsesMockServer): @@ -13,23 +13,25 @@ class FakeTelegram(aresponses.ResponsesMockServer): async def __aenter__(self): await super().__aenter__() - _response = self.Response(text=self._body, headers=self._headers, status=200, reason='OK') + _response = self.Response(text=self._body, headers=self._headers, status=200, reason="OK") self.add(self.ANY, response=_response) @staticmethod def parse_data(message_dict): import json - _body = '{"ok":true,"result":' + json.dumps(message_dict) + '}' - _headers = {'Server': 'nginx/1.12.2', - 'Date': 'Tue, 03 Apr 2018 16:59:54 GMT', - 'Content-Type': 'application/json', - 'Content-Length': str(len(_body)), - 'Connection': 'keep-alive', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', - 'Access-Control-Expose-Headers': 'Content-Length,Content-Type,Date,Server,Connection', - 'Strict-Transport-Security': 'max-age=31536000; includeSubdomains'} + _body = '{"ok":true,"result":' + json.dumps(message_dict) + "}" + _headers = { + "Server": "nginx/1.12.2", + "Date": "Tue, 03 Apr 2018 16:59:54 GMT", + "Content-Type": "application/json", + "Content-Length": str(len(_body)), + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Expose-Headers": "Content-Length,Content-Type,Date,Server,Connection", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains", + } return _body, _headers @@ -46,6 +48,7 @@ async def bot(event_loop): async def test_get_me(bot: Bot, event_loop): """ getMe method test """ from .types.dataset import USER + user = types.User(**USER) async with FakeTelegram(message_dict=USER, loop=event_loop): @@ -57,6 +60,7 @@ async def test_get_me(bot: Bot, event_loop): async def test_send_message(bot: Bot, event_loop): """ sendMessage method test """ from .types.dataset import MESSAGE + msg = types.Message(**MESSAGE) async with FakeTelegram(message_dict=MESSAGE, loop=event_loop): @@ -68,11 +72,15 @@ async def test_send_message(bot: Bot, event_loop): async def test_forward_message(bot: Bot, event_loop): """ forwardMessage method test """ from .types.dataset import FORWARDED_MESSAGE + msg = types.Message(**FORWARDED_MESSAGE) async with FakeTelegram(message_dict=FORWARDED_MESSAGE, loop=event_loop): - result = await bot.forward_message(chat_id=msg.chat.id, from_chat_id=msg.forward_from_chat.id, - message_id=msg.forward_from_message_id) + result = await bot.forward_message( + chat_id=msg.chat.id, + from_chat_id=msg.forward_from_chat.id, + message_id=msg.forward_from_message_id, + ) assert result == msg @@ -80,12 +88,18 @@ async def test_forward_message(bot: Bot, event_loop): async def test_send_photo(bot: Bot, event_loop): """ sendPhoto method test with file_id """ from .types.dataset import MESSAGE_WITH_PHOTO, PHOTO + msg = types.Message(**MESSAGE_WITH_PHOTO) photo = types.PhotoSize(**PHOTO) async with FakeTelegram(message_dict=MESSAGE_WITH_PHOTO, loop=event_loop): - result = await bot.send_photo(msg.chat.id, photo=photo.file_id, caption=msg.caption, - parse_mode=types.ParseMode.HTML, disable_notification=False) + result = await bot.send_photo( + msg.chat.id, + photo=photo.file_id, + caption=msg.caption, + parse_mode=types.ParseMode.HTML, + disable_notification=False, + ) assert result == msg @@ -93,12 +107,20 @@ async def test_send_photo(bot: Bot, event_loop): async def test_send_audio(bot: Bot, event_loop): """ sendAudio method test with file_id """ from .types.dataset import MESSAGE_WITH_AUDIO + msg = types.Message(**MESSAGE_WITH_AUDIO) async with FakeTelegram(message_dict=MESSAGE_WITH_AUDIO, loop=event_loop): - result = await bot.send_audio(chat_id=msg.chat.id, audio=msg.audio.file_id, caption=msg.caption, - parse_mode=types.ParseMode.HTML, duration=msg.audio.duration, - performer=msg.audio.performer, title=msg.audio.title, disable_notification=False) + result = await bot.send_audio( + chat_id=msg.chat.id, + audio=msg.audio.file_id, + caption=msg.caption, + parse_mode=types.ParseMode.HTML, + duration=msg.audio.duration, + performer=msg.audio.performer, + title=msg.audio.title, + disable_notification=False, + ) assert result == msg @@ -106,11 +128,17 @@ async def test_send_audio(bot: Bot, event_loop): async def test_send_document(bot: Bot, event_loop): """ sendDocument method test with file_id """ from .types.dataset import MESSAGE_WITH_DOCUMENT + msg = types.Message(**MESSAGE_WITH_DOCUMENT) async with FakeTelegram(message_dict=MESSAGE_WITH_DOCUMENT, loop=event_loop): - result = await bot.send_document(chat_id=msg.chat.id, document=msg.document.file_id, caption=msg.caption, - parse_mode=types.ParseMode.HTML, disable_notification=False) + result = await bot.send_document( + chat_id=msg.chat.id, + document=msg.document.file_id, + caption=msg.caption, + parse_mode=types.ParseMode.HTML, + disable_notification=False, + ) assert result == msg @@ -118,14 +146,22 @@ async def test_send_document(bot: Bot, event_loop): async def test_send_video(bot: Bot, event_loop): """ sendVideo method test with file_id """ from .types.dataset import MESSAGE_WITH_VIDEO, VIDEO + msg = types.Message(**MESSAGE_WITH_VIDEO) video = types.Video(**VIDEO) async with FakeTelegram(message_dict=MESSAGE_WITH_VIDEO, loop=event_loop): - result = await bot.send_video(chat_id=msg.chat.id, video=video.file_id, duration=video.duration, - width=video.width, height=video.height, caption=msg.caption, - parse_mode=types.ParseMode.HTML, supports_streaming=True, - disable_notification=False) + result = await bot.send_video( + chat_id=msg.chat.id, + video=video.file_id, + duration=video.duration, + width=video.width, + height=video.height, + caption=msg.caption, + parse_mode=types.ParseMode.HTML, + supports_streaming=True, + disable_notification=False, + ) assert result == msg @@ -133,13 +169,19 @@ async def test_send_video(bot: Bot, event_loop): async def test_send_voice(bot: Bot, event_loop): """ sendVoice method test with file_id """ from .types.dataset import MESSAGE_WITH_VOICE, VOICE + msg = types.Message(**MESSAGE_WITH_VOICE) voice = types.Voice(**VOICE) async with FakeTelegram(message_dict=MESSAGE_WITH_VOICE, loop=event_loop): - result = await bot.send_voice(chat_id=msg.chat.id, voice=voice.file_id, caption=msg.caption, - parse_mode=types.ParseMode.HTML, duration=voice.duration, - disable_notification=False) + result = await bot.send_voice( + chat_id=msg.chat.id, + voice=voice.file_id, + caption=msg.caption, + parse_mode=types.ParseMode.HTML, + duration=voice.duration, + disable_notification=False, + ) assert result == msg @@ -147,13 +189,18 @@ async def test_send_voice(bot: Bot, event_loop): async def test_send_video_note(bot: Bot, event_loop): """ sendVideoNote method test with file_id """ from .types.dataset import MESSAGE_WITH_VIDEO_NOTE, VIDEO_NOTE + msg = types.Message(**MESSAGE_WITH_VIDEO_NOTE) video_note = types.VideoNote(**VIDEO_NOTE) async with FakeTelegram(message_dict=MESSAGE_WITH_VIDEO_NOTE, loop=event_loop): - result = await bot.send_video_note(chat_id=msg.chat.id, video_note=video_note.file_id, - duration=video_note.duration, length=video_note.length, - disable_notification=False) + result = await bot.send_video_note( + chat_id=msg.chat.id, + video_note=video_note.file_id, + duration=video_note.duration, + length=video_note.length, + disable_notification=False, + ) assert result == msg @@ -161,11 +208,17 @@ async def test_send_video_note(bot: Bot, event_loop): async def test_send_media_group(bot: Bot, event_loop): """ sendMediaGroup method test with file_id """ from .types.dataset import MESSAGE_WITH_MEDIA_GROUP, PHOTO + msg = types.Message(**MESSAGE_WITH_MEDIA_GROUP) photo = types.PhotoSize(**PHOTO) - media = [types.InputMediaPhoto(media=photo.file_id), types.InputMediaPhoto(media=photo.file_id)] + media = [ + types.InputMediaPhoto(media=photo.file_id), + types.InputMediaPhoto(media=photo.file_id), + ] - async with FakeTelegram(message_dict=[MESSAGE_WITH_MEDIA_GROUP, MESSAGE_WITH_MEDIA_GROUP], loop=event_loop): + async with FakeTelegram( + message_dict=[MESSAGE_WITH_MEDIA_GROUP, MESSAGE_WITH_MEDIA_GROUP], loop=event_loop + ): result = await bot.send_media_group(msg.chat.id, media=media, disable_notification=False) assert len(result) == len(media) assert result.pop().media_group_id @@ -175,12 +228,18 @@ async def test_send_media_group(bot: Bot, event_loop): async def test_send_location(bot: Bot, event_loop): """ sendLocation method test """ from .types.dataset import MESSAGE_WITH_LOCATION, LOCATION + msg = types.Message(**MESSAGE_WITH_LOCATION) location = types.Location(**LOCATION) async with FakeTelegram(message_dict=MESSAGE_WITH_LOCATION, loop=event_loop): - result = await bot.send_location(msg.chat.id, latitude=location.latitude, longitude=location.longitude, - live_period=10, disable_notification=False) + result = await bot.send_location( + msg.chat.id, + latitude=location.latitude, + longitude=location.longitude, + live_period=10, + disable_notification=False, + ) assert result == msg @@ -188,13 +247,18 @@ async def test_send_location(bot: Bot, event_loop): async def test_edit_message_live_location_by_bot(bot: Bot, event_loop): """ editMessageLiveLocation method test """ from .types.dataset import MESSAGE_WITH_LOCATION, LOCATION + msg = types.Message(**MESSAGE_WITH_LOCATION) location = types.Location(**LOCATION) # editing bot message async with FakeTelegram(message_dict=MESSAGE_WITH_LOCATION, loop=event_loop): - result = await bot.edit_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id, - latitude=location.latitude, longitude=location.longitude) + result = await bot.edit_message_live_location( + chat_id=msg.chat.id, + message_id=msg.message_id, + latitude=location.latitude, + longitude=location.longitude, + ) assert result == msg @@ -202,13 +266,18 @@ async def test_edit_message_live_location_by_bot(bot: Bot, event_loop): async def test_edit_message_live_location_by_user(bot: Bot, event_loop): """ editMessageLiveLocation method test """ from .types.dataset import MESSAGE_WITH_LOCATION, LOCATION + msg = types.Message(**MESSAGE_WITH_LOCATION) location = types.Location(**LOCATION) # editing user's message async with FakeTelegram(message_dict=True, loop=event_loop): - result = await bot.edit_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id, - latitude=location.latitude, longitude=location.longitude) + result = await bot.edit_message_live_location( + chat_id=msg.chat.id, + message_id=msg.message_id, + latitude=location.latitude, + longitude=location.longitude, + ) assert isinstance(result, bool) and result is True @@ -216,11 +285,14 @@ async def test_edit_message_live_location_by_user(bot: Bot, event_loop): async def test_stop_message_live_location_by_bot(bot: Bot, event_loop): """ stopMessageLiveLocation method test """ from .types.dataset import MESSAGE_WITH_LOCATION + msg = types.Message(**MESSAGE_WITH_LOCATION) # stopping bot message async with FakeTelegram(message_dict=MESSAGE_WITH_LOCATION, loop=event_loop): - result = await bot.stop_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id) + result = await bot.stop_message_live_location( + chat_id=msg.chat.id, message_id=msg.message_id + ) assert result == msg @@ -228,11 +300,14 @@ async def test_stop_message_live_location_by_bot(bot: Bot, event_loop): async def test_stop_message_live_location_by_user(bot: Bot, event_loop): """ stopMessageLiveLocation method test """ from .types.dataset import MESSAGE_WITH_LOCATION + msg = types.Message(**MESSAGE_WITH_LOCATION) # stopping user's message async with FakeTelegram(message_dict=True, loop=event_loop): - result = await bot.stop_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id) + result = await bot.stop_message_live_location( + chat_id=msg.chat.id, message_id=msg.message_id + ) assert isinstance(result, bool) assert result is True @@ -241,14 +316,21 @@ async def test_stop_message_live_location_by_user(bot: Bot, event_loop): async def test_send_venue(bot: Bot, event_loop): """ sendVenue method test """ from .types.dataset import MESSAGE_WITH_VENUE, VENUE, LOCATION + msg = types.Message(**MESSAGE_WITH_VENUE) location = types.Location(**LOCATION) venue = types.Venue(**VENUE) async with FakeTelegram(message_dict=MESSAGE_WITH_VENUE, loop=event_loop): - result = await bot.send_venue(msg.chat.id, latitude=location.latitude, longitude=location.longitude, - title=venue.title, address=venue.address, foursquare_id=venue.foursquare_id, - disable_notification=False) + result = await bot.send_venue( + msg.chat.id, + latitude=location.latitude, + longitude=location.longitude, + title=venue.title, + address=venue.address, + foursquare_id=venue.foursquare_id, + disable_notification=False, + ) assert result == msg @@ -256,12 +338,18 @@ async def test_send_venue(bot: Bot, event_loop): async def test_send_contact(bot: Bot, event_loop): """ sendContact method test """ from .types.dataset import MESSAGE_WITH_CONTACT, CONTACT + msg = types.Message(**MESSAGE_WITH_CONTACT) contact = types.Contact(**CONTACT) async with FakeTelegram(message_dict=MESSAGE_WITH_CONTACT, loop=event_loop): - result = await bot.send_contact(msg.chat.id, phone_number=contact.phone_number, first_name=contact.first_name, - last_name=contact.last_name, disable_notification=False) + result = await bot.send_contact( + msg.chat.id, + phone_number=contact.phone_number, + first_name=contact.first_name, + last_name=contact.last_name, + disable_notification=False, + ) assert result == msg @@ -269,6 +357,7 @@ async def test_send_contact(bot: Bot, event_loop): async def test_send_chat_action(bot: Bot, event_loop): """ sendChatAction method test """ from .types.dataset import CHAT + chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=True, loop=event_loop): @@ -281,6 +370,7 @@ async def test_send_chat_action(bot: Bot, event_loop): async def test_get_user_profile_photo(bot: Bot, event_loop): """ getUserProfilePhotos method test """ from .types.dataset import USER_PROFILE_PHOTOS, USER + user = types.User(**USER) async with FakeTelegram(message_dict=USER_PROFILE_PHOTOS, loop=event_loop): @@ -292,6 +382,7 @@ async def test_get_user_profile_photo(bot: Bot, event_loop): async def test_get_file(bot: Bot, event_loop): """ getFile method test """ from .types.dataset import FILE + file = types.File(**FILE) async with FakeTelegram(message_dict=FILE, loop=event_loop): @@ -303,6 +394,7 @@ async def test_get_file(bot: Bot, event_loop): async def test_kick_chat_member(bot: Bot, event_loop): """ kickChatMember method test """ from .types.dataset import USER, CHAT + user = types.User(**USER) chat = types.Chat(**CHAT) @@ -316,6 +408,7 @@ async def test_kick_chat_member(bot: Bot, event_loop): async def test_unban_chat_member(bot: Bot, event_loop): """ unbanChatMember method test """ from .types.dataset import USER, CHAT + user = types.User(**USER) chat = types.Chat(**CHAT) @@ -329,6 +422,7 @@ async def test_unban_chat_member(bot: Bot, event_loop): async def test_restrict_chat_member(bot: Bot, event_loop): """ restrictChatMember method test """ from .types.dataset import USER, CHAT + user = types.User(**USER) chat = types.Chat(**CHAT) @@ -340,8 +434,10 @@ async def test_restrict_chat_member(bot: Bot, event_loop): can_add_web_page_previews=False, can_send_media_messages=False, can_send_messages=False, - can_send_other_messages=False - ), until_date=123) + can_send_other_messages=False, + ), + until_date=123, + ) assert isinstance(result, bool) assert result is True @@ -350,14 +446,23 @@ async def test_restrict_chat_member(bot: Bot, event_loop): async def test_promote_chat_member(bot: Bot, event_loop): """ promoteChatMember method test """ from .types.dataset import USER, CHAT + user = types.User(**USER) chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=True, loop=event_loop): - result = await bot.promote_chat_member(chat_id=chat.id, user_id=user.id, can_change_info=True, - can_delete_messages=True, can_edit_messages=True, - can_invite_users=True, can_pin_messages=True, can_post_messages=True, - can_promote_members=True, can_restrict_members=True) + result = await bot.promote_chat_member( + chat_id=chat.id, + user_id=user.id, + can_change_info=True, + can_delete_messages=True, + can_edit_messages=True, + can_invite_users=True, + can_pin_messages=True, + can_post_messages=True, + can_promote_members=True, + can_restrict_members=True, + ) assert isinstance(result, bool) assert result is True @@ -366,6 +471,7 @@ async def test_promote_chat_member(bot: Bot, event_loop): async def test_export_chat_invite_link(bot: Bot, event_loop): """ exportChatInviteLink method test """ from .types.dataset import CHAT, INVITE_LINK + chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=INVITE_LINK, loop=event_loop): @@ -377,6 +483,7 @@ async def test_export_chat_invite_link(bot: Bot, event_loop): async def test_delete_chat_photo(bot: Bot, event_loop): """ deleteChatPhoto method test """ from .types.dataset import CHAT + chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=True, loop=event_loop): @@ -389,10 +496,11 @@ async def test_delete_chat_photo(bot: Bot, event_loop): async def test_set_chat_title(bot: Bot, event_loop): """ setChatTitle method test """ from .types.dataset import CHAT + chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=True, loop=event_loop): - result = await bot.set_chat_title(chat_id=chat.id, title='Test title') + result = await bot.set_chat_title(chat_id=chat.id, title="Test title") assert isinstance(result, bool) assert result is True @@ -401,10 +509,11 @@ async def test_set_chat_title(bot: Bot, event_loop): async def test_set_chat_description(bot: Bot, event_loop): """ setChatDescription method test """ from .types.dataset import CHAT + chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=True, loop=event_loop): - result = await bot.set_chat_description(chat_id=chat.id, description='Test description') + result = await bot.set_chat_description(chat_id=chat.id, description="Test description") assert isinstance(result, bool) assert result is True @@ -413,11 +522,13 @@ async def test_set_chat_description(bot: Bot, event_loop): async def test_pin_chat_message(bot: Bot, event_loop): """ pinChatMessage method test """ from .types.dataset import MESSAGE + message = types.Message(**MESSAGE) async with FakeTelegram(message_dict=True, loop=event_loop): - result = await bot.pin_chat_message(chat_id=message.chat.id, message_id=message.message_id, - disable_notification=False) + result = await bot.pin_chat_message( + chat_id=message.chat.id, message_id=message.message_id, disable_notification=False + ) assert isinstance(result, bool) assert result is True @@ -426,6 +537,7 @@ async def test_pin_chat_message(bot: Bot, event_loop): async def test_unpin_chat_message(bot: Bot, event_loop): """ unpinChatMessage method test """ from .types.dataset import CHAT + chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=True, loop=event_loop): @@ -438,6 +550,7 @@ async def test_unpin_chat_message(bot: Bot, event_loop): async def test_leave_chat(bot: Bot, event_loop): """ leaveChat method test """ from .types.dataset import CHAT + chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=True, loop=event_loop): @@ -450,6 +563,7 @@ async def test_leave_chat(bot: Bot, event_loop): async def test_get_chat(bot: Bot, event_loop): """ getChat method test """ from .types.dataset import CHAT + chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=CHAT, loop=event_loop): @@ -461,6 +575,7 @@ async def test_get_chat(bot: Bot, event_loop): async def test_get_chat_administrators(bot: Bot, event_loop): """ getChatAdministrators method test """ from .types.dataset import CHAT, CHAT_MEMBER + chat = types.Chat(**CHAT) member = types.ChatMember(**CHAT_MEMBER) @@ -474,6 +589,7 @@ async def test_get_chat_administrators(bot: Bot, event_loop): async def test_get_chat_members_count(bot: Bot, event_loop): """ getChatMembersCount method test """ from .types.dataset import CHAT + chat = types.Chat(**CHAT) count = 5 @@ -486,6 +602,7 @@ async def test_get_chat_members_count(bot: Bot, event_loop): async def test_get_chat_member(bot: Bot, event_loop): """ getChatMember method test """ from .types.dataset import CHAT, CHAT_MEMBER + chat = types.Chat(**CHAT) member = types.ChatMember(**CHAT_MEMBER) @@ -499,10 +616,13 @@ async def test_get_chat_member(bot: Bot, event_loop): async def test_set_chat_sticker_set(bot: Bot, event_loop): """ setChatStickerSet method test """ from .types.dataset import CHAT + chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=True, loop=event_loop): - result = await bot.set_chat_sticker_set(chat_id=chat.id, sticker_set_name='aiogram_stickers') + result = await bot.set_chat_sticker_set( + chat_id=chat.id, sticker_set_name="aiogram_stickers" + ) assert isinstance(result, bool) assert result is True @@ -511,6 +631,7 @@ async def test_set_chat_sticker_set(bot: Bot, event_loop): async def test_delete_chat_sticker_set(bot: Bot, event_loop): """ setChatStickerSet method test """ from .types.dataset import CHAT + chat = types.Chat(**CHAT) async with FakeTelegram(message_dict=True, loop=event_loop): @@ -524,7 +645,7 @@ async def test_answer_callback_query(bot: Bot, event_loop): """ answerCallbackQuery method test """ async with FakeTelegram(message_dict=True, loop=event_loop): - result = await bot.answer_callback_query(callback_query_id='QuERyId', text='Test Answer') + result = await bot.answer_callback_query(callback_query_id="QuERyId", text="Test Answer") assert isinstance(result, bool) assert result is True @@ -533,11 +654,14 @@ async def test_answer_callback_query(bot: Bot, event_loop): async def test_edit_message_text_by_bot(bot: Bot, event_loop): """ editMessageText method test """ from .types.dataset import EDITED_MESSAGE + msg = types.Message(**EDITED_MESSAGE) # message by bot async with FakeTelegram(message_dict=EDITED_MESSAGE, loop=event_loop): - result = await bot.edit_message_text(text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id) + result = await bot.edit_message_text( + text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id + ) assert result == msg @@ -545,10 +669,13 @@ async def test_edit_message_text_by_bot(bot: Bot, event_loop): async def test_edit_message_text_by_user(bot: Bot, event_loop): """ editMessageText method test """ from .types.dataset import EDITED_MESSAGE + msg = types.Message(**EDITED_MESSAGE) # message by user async with FakeTelegram(message_dict=True, loop=event_loop): - result = await bot.edit_message_text(text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id) + result = await bot.edit_message_text( + text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id + ) assert isinstance(result, bool) assert result is True diff --git a/tests/test_bot/test_api.py b/tests/test_bot/test_api.py index c5193bcc..11eaaf03 100644 --- a/tests/test_bot/test_api.py +++ b/tests/test_bot/test_api.py @@ -1,15 +1,13 @@ import pytest -from aiogram.bot.api import check_token +from aiogram.bot.api import check_token from aiogram.utils.exceptions import ValidationError - -VALID_TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff-1234567890' -INVALID_TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff 123456789' # Space in token and wrong length +VALID_TOKEN = "123456789:AABBCCDDEEFFaabbccddeeff-1234567890" +INVALID_TOKEN = "123456789:AABBCCDDEEFFaabbccddeeff 123456789" # Space in token and wrong length class Test_check_token: - def test_valid(self): assert check_token(VALID_TOKEN) is True diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py index f41d1a51..7b3367ab 100644 --- a/tests/test_dispatcher.py +++ b/tests/test_dispatcher.py @@ -1,6 +1,6 @@ import pytest -from aiogram import Dispatcher, Bot +from aiogram import Bot, Dispatcher pytestmark = pytest.mark.asyncio diff --git a/tests/test_dispatcher/test_filters/test_builtin.py b/tests/test_dispatcher/test_filters/test_builtin.py index 86344cec..43b59fc6 100644 --- a/tests/test_dispatcher/test_filters/test_builtin.py +++ b/tests/test_dispatcher/test_filters/test_builtin.py @@ -4,15 +4,17 @@ from aiogram.dispatcher.filters.builtin import Text class TestText: - - @pytest.mark.parametrize('param, key', [ - ('text', 'equals'), - ('text_contains', 'contains'), - ('text_startswith', 'startswith'), - ('text_endswith', 'endswith'), - ]) + @pytest.mark.parametrize( + "param, key", + [ + ("text", "equals"), + ("text_contains", "contains"), + ("text_startswith", "startswith"), + ("text_endswith", "endswith"), + ], + ) def test_validate(self, param, key): - value = 'spam and eggs' + value = "spam and eggs" config = {param: value} res = Text.validate(config) assert res == {key: value} diff --git a/tests/test_dispatcher/test_filters/test_state.py b/tests/test_dispatcher/test_filters/test_state.py index b7f5a5fd..51212cb8 100644 --- a/tests/test_dispatcher/test_filters/test_state.py +++ b/tests/test_dispatcher/test_filters/test_state.py @@ -1,9 +1,8 @@ from aiogram.dispatcher.filters.state import StatesGroup + class TestStatesGroup: - def test_all_childs(self): - class InnerState1(StatesGroup): pass diff --git a/tests/test_filters.py b/tests/test_filters.py index 609db736..23403a9a 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1,29 +1,26 @@ import pytest from aiogram.dispatcher.filters import Text -from aiogram.types import Message, CallbackQuery, InlineQuery, Poll +from aiogram.types import CallbackQuery, InlineQuery, Message, Poll def data_sample_1(): return [ - ('', ''), - ('', 'exAmple_string'), - - ('example_string', 'example_string'), - ('example_string', 'exAmple_string'), - ('exAmple_string', 'example_string'), - - ('example_string', 'example_string_dsf'), - ('example_string', 'example_striNG_dsf'), - ('example_striNG', 'example_string_dsf'), - - ('example_string', 'not_example_string'), - ('example_string', 'not_eXample_string'), - ('EXample_string', 'not_example_string'), + ("", ""), + ("", "exAmple_string"), + ("example_string", "example_string"), + ("example_string", "exAmple_string"), + ("exAmple_string", "example_string"), + ("example_string", "example_string_dsf"), + ("example_string", "example_striNG_dsf"), + ("example_striNG", "example_string_dsf"), + ("example_string", "not_example_string"), + ("example_string", "not_eXample_string"), + ("EXample_string", "not_example_string"), ] -class TestTextFilter: +class TestTextFilter: async def _run_check(self, check, test_text): assert await check(Message(text=test_text)) assert await check(CallbackQuery(data=test_text)) @@ -31,7 +28,7 @@ class TestTextFilter: assert await check(Poll(question=test_text)) @pytest.mark.asyncio - @pytest.mark.parametrize('ignore_case', (True, False)) + @pytest.mark.parametrize("ignore_case", (True, False)) @pytest.mark.parametrize("test_prefix, test_text", data_sample_1()) async def test_startswith(self, test_prefix, test_text, ignore_case): test_filter = Text(startswith=test_prefix, ignore_case=ignore_case) @@ -50,23 +47,23 @@ class TestTextFilter: await self._run_check(check, test_text) @pytest.mark.asyncio - @pytest.mark.parametrize('ignore_case', (True, False)) - @pytest.mark.parametrize("test_prefix_list, test_text", [ - (['not_example', ''], ''), - (['', 'not_example'], 'exAmple_string'), - - (['not_example', 'example_string'], 'example_string'), - (['example_string', 'not_example'], 'exAmple_string'), - (['not_example', 'exAmple_string'], 'example_string'), - - (['not_example', 'example_string'], 'example_string_dsf'), - (['example_string', 'not_example'], 'example_striNG_dsf'), - (['not_example', 'example_striNG'], 'example_string_dsf'), - - (['not_example', 'example_string'], 'not_example_string'), - (['example_string', 'not_example'], 'not_eXample_string'), - (['not_example', 'EXample_string'], 'not_example_string'), - ]) + @pytest.mark.parametrize("ignore_case", (True, False)) + @pytest.mark.parametrize( + "test_prefix_list, test_text", + [ + (["not_example", ""], ""), + (["", "not_example"], "exAmple_string"), + (["not_example", "example_string"], "example_string"), + (["example_string", "not_example"], "exAmple_string"), + (["not_example", "exAmple_string"], "example_string"), + (["not_example", "example_string"], "example_string_dsf"), + (["example_string", "not_example"], "example_striNG_dsf"), + (["not_example", "example_striNG"], "example_string_dsf"), + (["not_example", "example_string"], "not_example_string"), + (["example_string", "not_example"], "not_eXample_string"), + (["not_example", "EXample_string"], "not_example_string"), + ], + ) async def test_startswith_list(self, test_prefix_list, test_text, ignore_case): test_filter = Text(startswith=test_prefix_list, ignore_case=ignore_case) @@ -84,7 +81,7 @@ class TestTextFilter: await self._run_check(check, test_text) @pytest.mark.asyncio - @pytest.mark.parametrize('ignore_case', (True, False)) + @pytest.mark.parametrize("ignore_case", (True, False)) @pytest.mark.parametrize("test_postfix, test_text", data_sample_1()) async def test_endswith(self, test_postfix, test_text, ignore_case): test_filter = Text(endswith=test_postfix, ignore_case=ignore_case) @@ -103,23 +100,23 @@ class TestTextFilter: await self._run_check(check, test_text) @pytest.mark.asyncio - @pytest.mark.parametrize('ignore_case', (True, False)) - @pytest.mark.parametrize("test_postfix_list, test_text", [ - (['', 'not_example'], ''), - (['not_example', ''], 'exAmple_string'), - - (['example_string', 'not_example'], 'example_string'), - (['not_example', 'example_string'], 'exAmple_string'), - (['exAmple_string', 'not_example'], 'example_string'), - - (['not_example', 'example_string'], 'example_string_dsf'), - (['example_string', 'not_example'], 'example_striNG_dsf'), - (['not_example', 'example_striNG'], 'example_string_dsf'), - - (['not_example', 'example_string'], 'not_example_string'), - (['example_string', 'not_example'], 'not_eXample_string'), - (['not_example', 'EXample_string'], 'not_example_string'), - ]) + @pytest.mark.parametrize("ignore_case", (True, False)) + @pytest.mark.parametrize( + "test_postfix_list, test_text", + [ + (["", "not_example"], ""), + (["not_example", ""], "exAmple_string"), + (["example_string", "not_example"], "example_string"), + (["not_example", "example_string"], "exAmple_string"), + (["exAmple_string", "not_example"], "example_string"), + (["not_example", "example_string"], "example_string_dsf"), + (["example_string", "not_example"], "example_striNG_dsf"), + (["not_example", "example_striNG"], "example_string_dsf"), + (["not_example", "example_string"], "not_example_string"), + (["example_string", "not_example"], "not_eXample_string"), + (["not_example", "EXample_string"], "not_example_string"), + ], + ) async def test_endswith_list(self, test_postfix_list, test_text, ignore_case): test_filter = Text(endswith=test_postfix_list, ignore_case=ignore_case) @@ -133,26 +130,27 @@ class TestTextFilter: _test_text = test_text return result is any(map(_test_text.endswith, _test_postfix_list)) + await self._run_check(check, test_text) @pytest.mark.asyncio - @pytest.mark.parametrize('ignore_case', (True, False)) - @pytest.mark.parametrize("test_string, test_text", [ - ('', ''), - ('', 'exAmple_string'), - - ('example_string', 'example_string'), - ('example_string', 'exAmple_string'), - ('exAmple_string', 'example_string'), - - ('example_string', 'example_string_dsf'), - ('example_string', 'example_striNG_dsf'), - ('example_striNG', 'example_string_dsf'), - - ('example_string', 'not_example_strin'), - ('example_string', 'not_eXample_strin'), - ('EXample_string', 'not_example_strin'), - ]) + @pytest.mark.parametrize("ignore_case", (True, False)) + @pytest.mark.parametrize( + "test_string, test_text", + [ + ("", ""), + ("", "exAmple_string"), + ("example_string", "example_string"), + ("example_string", "exAmple_string"), + ("exAmple_string", "example_string"), + ("example_string", "example_string_dsf"), + ("example_string", "example_striNG_dsf"), + ("example_striNG", "example_string_dsf"), + ("example_string", "not_example_strin"), + ("example_string", "not_eXample_strin"), + ("EXample_string", "not_example_strin"), + ], + ) async def test_contains(self, test_string, test_text, ignore_case): test_filter = Text(contains=test_string, ignore_case=ignore_case) @@ -170,13 +168,16 @@ class TestTextFilter: await self._run_check(check, test_text) @pytest.mark.asyncio - @pytest.mark.parametrize('ignore_case', (True, False)) - @pytest.mark.parametrize("test_filter_list, test_text", [ - (['a', 'ab', 'abc'], 'A'), - (['a', 'ab', 'abc'], 'ab'), - (['a', 'ab', 'abc'], 'aBc'), - (['a', 'ab', 'abc'], 'd'), - ]) + @pytest.mark.parametrize("ignore_case", (True, False)) + @pytest.mark.parametrize( + "test_filter_list, test_text", + [ + (["a", "ab", "abc"], "A"), + (["a", "ab", "abc"], "ab"), + (["a", "ab", "abc"], "aBc"), + (["a", "ab", "abc"], "d"), + ], + ) async def test_contains_list(self, test_filter_list, test_text, ignore_case): test_filter = Text(contains=test_filter_list, ignore_case=ignore_case) @@ -194,19 +195,20 @@ class TestTextFilter: await self._run_check(check, test_text) @pytest.mark.asyncio - @pytest.mark.parametrize('ignore_case', (True, False)) - @pytest.mark.parametrize("test_filter_text, test_text", [ - ('', ''), - ('', 'exAmple_string'), - - ('example_string', 'example_string'), - ('example_string', 'exAmple_string'), - ('exAmple_string', 'example_string'), - - ('example_string', 'not_example_string'), - ('example_string', 'not_eXample_string'), - ('EXample_string', 'not_example_string'), - ]) + @pytest.mark.parametrize("ignore_case", (True, False)) + @pytest.mark.parametrize( + "test_filter_text, test_text", + [ + ("", ""), + ("", "exAmple_string"), + ("example_string", "example_string"), + ("example_string", "exAmple_string"), + ("exAmple_string", "example_string"), + ("example_string", "not_example_string"), + ("example_string", "not_eXample_string"), + ("EXample_string", "not_example_string"), + ], + ) async def test_equals_string(self, test_filter_text, test_text, ignore_case): test_filter = Text(equals=test_filter_text, ignore_case=ignore_case) @@ -223,27 +225,26 @@ class TestTextFilter: await self._run_check(check, test_text) @pytest.mark.asyncio - @pytest.mark.parametrize('ignore_case', (True, False)) - @pytest.mark.parametrize("test_filter_list, test_text", [ - (['new_string', ''], ''), - (['', 'new_string'], 'exAmple_string'), - - (['example_string'], 'example_string'), - (['example_string'], 'exAmple_string'), - (['exAmple_string'], 'example_string'), - - (['example_string'], 'not_example_string'), - (['example_string'], 'not_eXample_string'), - (['EXample_string'], 'not_example_string'), - - (['example_string', 'new_string'], 'example_string'), - (['new_string', 'example_string'], 'exAmple_string'), - (['exAmple_string', 'new_string'], 'example_string'), - - (['example_string', 'new_string'], 'not_example_string'), - (['new_string', 'example_string'], 'not_eXample_string'), - (['EXample_string', 'new_string'], 'not_example_string'), - ]) + @pytest.mark.parametrize("ignore_case", (True, False)) + @pytest.mark.parametrize( + "test_filter_list, test_text", + [ + (["new_string", ""], ""), + (["", "new_string"], "exAmple_string"), + (["example_string"], "example_string"), + (["example_string"], "exAmple_string"), + (["exAmple_string"], "example_string"), + (["example_string"], "not_example_string"), + (["example_string"], "not_eXample_string"), + (["EXample_string"], "not_example_string"), + (["example_string", "new_string"], "example_string"), + (["new_string", "example_string"], "exAmple_string"), + (["exAmple_string", "new_string"], "example_string"), + (["example_string", "new_string"], "not_example_string"), + (["new_string", "example_string"], "not_eXample_string"), + (["EXample_string", "new_string"], "not_example_string"), + ], + ) async def test_equals_list(self, test_filter_list, test_text, ignore_case): test_filter = Text(equals=test_filter_list, ignore_case=ignore_case) diff --git a/tests/test_message.py b/tests/test_message.py index 9b9af582..56dc3079 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -3,7 +3,8 @@ from asyncio import BaseEventLoop import pytest from aiogram import Bot, types -from . import FakeTelegram, TOKEN + +from . import TOKEN, FakeTelegram pytestmark = pytest.mark.asyncio diff --git a/tests/test_utils/test_auth_widget.py b/tests/test_utils/test_auth_widget.py index 8c6f5941..321552be 100644 --- a/tests/test_utils/test_auth_widget.py +++ b/tests/test_utils/test_auth_widget.py @@ -1,46 +1,45 @@ import pytest -from aiogram.utils.auth_widget import check_integrity, \ - generate_hash, check_token +from aiogram.utils.auth_widget import check_integrity, check_token, generate_hash -TOKEN = '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11' +TOKEN = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" @pytest.fixture def data(): return { - 'id': '42', - 'first_name': 'John', - 'last_name': 'Smith', - 'username': 'username', - 'photo_url': 'https://t.me/i/userpic/320/picname.jpg', - 'auth_date': '1565810688', - 'hash': 'c303db2b5a06fe41d23a9b14f7c545cfc11dcc7473c07c9c5034ae60062461ce', + "id": "42", + "first_name": "John", + "last_name": "Smith", + "username": "username", + "photo_url": "https://t.me/i/userpic/320/picname.jpg", + "auth_date": "1565810688", + "hash": "c303db2b5a06fe41d23a9b14f7c545cfc11dcc7473c07c9c5034ae60062461ce", } def test_generate_hash(data): res = generate_hash(data, TOKEN) - assert res == data['hash'] + assert res == data["hash"] class Test_check_token: """ This case gonna be deleted """ + def test_ok(self, data): assert check_token(data, TOKEN) is True def test_fail(self, data): - data.pop('username') + data.pop("username") assert check_token(data, TOKEN) is False class Test_check_integrity: - def test_ok(self, data): assert check_integrity(TOKEN, data) is True def test_fail(self, data): - data.pop('username') + data.pop("username") assert check_integrity(TOKEN, data) is False diff --git a/tests/test_utils/test_helper.py b/tests/test_utils/test_helper.py index d202d289..0a5bd857 100644 --- a/tests/test_utils/test_helper.py +++ b/tests/test_utils/test_helper.py @@ -1,8 +1,7 @@ -from aiogram.utils.helper import OrderedHelper, Item, ListItem +from aiogram.utils.helper import Item, ListItem, OrderedHelper class TestOrderedHelper: - def test_items_are_ordered(self): class Helper(OrderedHelper): A = Item() @@ -10,7 +9,7 @@ class TestOrderedHelper: C = Item() B = Item() - assert Helper.all() == ['A', 'D', 'C', 'B'] + assert Helper.all() == ["A", "D", "C", "B"] def test_list_items_are_ordered(self): class Helper(OrderedHelper): @@ -19,4 +18,4 @@ class TestOrderedHelper: C = ListItem() B = ListItem() - assert Helper.all() == ['A', 'D', 'C', 'B'] + assert Helper.all() == ["A", "D", "C", "B"] diff --git a/tests/types/dataset.py b/tests/types/dataset.py index 18bcbdad..acfb2a34 100644 --- a/tests/types/dataset.py +++ b/tests/types/dataset.py @@ -47,11 +47,7 @@ CHAT_MEMBER = { "can_promote_members": False, } -CONTACT = { - "phone_number": "88005553535", - "first_name": "John", - "last_name": "Smith", -} +CONTACT = {"phone_number": "88005553535", "first_name": "John", "last_name": "Smith"} DOCUMENT = { "file_name": "test.docx", @@ -68,42 +64,17 @@ ANIMATION = { "file_size": 65837, } -ENTITY_BOLD = { - "offset": 5, - "length": 2, - "type": "bold", -} +ENTITY_BOLD = {"offset": 5, "length": 2, "type": "bold"} -ENTITY_ITALIC = { - "offset": 8, - "length": 1, - "type": "italic", -} +ENTITY_ITALIC = {"offset": 8, "length": 1, "type": "italic"} -ENTITY_LINK = { - "offset": 10, - "length": 6, - "type": "text_link", - "url": "http://google.com/", -} +ENTITY_LINK = {"offset": 10, "length": 6, "type": "text_link", "url": "http://google.com/"} -ENTITY_CODE = { - "offset": 17, - "length": 7, - "type": "code", -} +ENTITY_CODE = {"offset": 17, "length": 7, "type": "code"} -ENTITY_PRE = { - "offset": 30, - "length": 4, - "type": "pre", -} +ENTITY_PRE = {"offset": 30, "length": 4, "type": "pre"} -ENTITY_MENTION = { - "offset": 47, - "length": 9, - "type": "mention", -} +ENTITY_MENTION = {"offset": 47, "length": 9, "type": "mention"} GAME = { "title": "Karate Kido", @@ -115,18 +86,15 @@ GAME = { INVOICE = { "title": "Working Time Machine", "description": "Want to visit your great-great-great-grandparents? " - "Make a fortune at the races? " - "Shake hands with Hammurabi and take a stroll in the Hanging Gardens? " - "Order our Working Time Machine today!", + "Make a fortune at the races? " + "Shake hands with Hammurabi and take a stroll in the Hanging Gardens? " + "Order our Working Time Machine today!", "start_parameter": "time-machine-example", "currency": "USD", "total_amount": 6250, } -LOCATION = { - "latitude": 50.693416, - "longitude": 30.624605, -} +LOCATION = {"latitude": 50.693416, "longitude": 30.624605} VENUE = { "location": LOCATION, @@ -153,7 +121,7 @@ STICKER = { "file_id": "AAbbCCddEEffGGhh1234567890", "file_size": 1234, "width": 128, - "height": 128 + "height": 128, }, "file_id": "AAbbCCddEEffGGhh1234567890", "file_size": 12345, @@ -218,8 +186,15 @@ FORWARDED_MESSAGE = { "forward_from_message_id": 123, "forward_date": 1522749037, "text": "Forwarded text with entities from public channel ", - "entities": [ENTITY_BOLD, ENTITY_CODE, ENTITY_ITALIC, ENTITY_LINK, - ENTITY_LINK, ENTITY_MENTION, ENTITY_PRE], + "entities": [ + ENTITY_BOLD, + ENTITY_CODE, + ENTITY_ITALIC, + ENTITY_LINK, + ENTITY_LINK, + ENTITY_MENTION, + ENTITY_PRE, + ], } INLINE_QUERY = {} @@ -416,27 +391,12 @@ SHIPPING_QUERY = { "shipping_address": SHIPPING_ADDRESS, } -USER_PROFILE_PHOTOS = { - "total_count": 1, "photos": [ - [PHOTO, PHOTO, PHOTO], - ], -} +USER_PROFILE_PHOTOS = {"total_count": 1, "photos": [[PHOTO, PHOTO, PHOTO]]} -FILE = { - "file_id": "XXXYYYZZZ", - "file_size": 5254, - "file_path": "voice/file_8", -} +FILE = {"file_id": "XXXYYYZZZ", "file_size": 5254, "file_path": "voice/file_8"} -INVITE_LINK = 'https://t.me/joinchat/AbCdEfjKILDADwdd123' +INVITE_LINK = "https://t.me/joinchat/AbCdEfjKILDADwdd123" -UPDATE = { - "update_id": 123456789, - "message": MESSAGE, -} +UPDATE = {"update_id": 123456789, "message": MESSAGE} -WEBHOOK_INFO = { - "url": "", - "has_custom_certificate": False, - "pending_update_count": 0, -} +WEBHOOK_INFO = {"url": "", "has_custom_certificate": False, "pending_update_count": 0} diff --git a/tests/types/test_animation.py b/tests/types/test_animation.py index d8f7cb54..a8816aec 100644 --- a/tests/types/test_animation.py +++ b/tests/types/test_animation.py @@ -1,4 +1,5 @@ from aiogram import types + from .dataset import ANIMATION animation = types.Animation(**ANIMATION) diff --git a/tests/types/test_chat.py b/tests/types/test_chat.py index a7a7c937..a436b57c 100644 --- a/tests/types/test_chat.py +++ b/tests/types/test_chat.py @@ -1,4 +1,5 @@ from aiogram import types + from .dataset import CHAT chat = types.Chat(**CHAT) diff --git a/tests/types/test_chat_member.py b/tests/types/test_chat_member.py index 2cea44ce..8070caf9 100644 --- a/tests/types/test_chat_member.py +++ b/tests/types/test_chat_member.py @@ -1,4 +1,5 @@ from aiogram import types + from .dataset import CHAT_MEMBER chat_member = types.ChatMember(**CHAT_MEMBER) @@ -16,30 +17,30 @@ def test_user(): def test_status(): assert isinstance(chat_member.status, str) - assert chat_member.status == CHAT_MEMBER['status'] + assert chat_member.status == CHAT_MEMBER["status"] def test_privileges(): assert isinstance(chat_member.can_be_edited, bool) - assert chat_member.can_be_edited == CHAT_MEMBER['can_be_edited'] + assert chat_member.can_be_edited == CHAT_MEMBER["can_be_edited"] assert isinstance(chat_member.can_change_info, bool) - assert chat_member.can_change_info == CHAT_MEMBER['can_change_info'] + assert chat_member.can_change_info == CHAT_MEMBER["can_change_info"] assert isinstance(chat_member.can_delete_messages, bool) - assert chat_member.can_delete_messages == CHAT_MEMBER['can_delete_messages'] + assert chat_member.can_delete_messages == CHAT_MEMBER["can_delete_messages"] assert isinstance(chat_member.can_invite_users, bool) - assert chat_member.can_invite_users == CHAT_MEMBER['can_invite_users'] + assert chat_member.can_invite_users == CHAT_MEMBER["can_invite_users"] assert isinstance(chat_member.can_restrict_members, bool) - assert chat_member.can_restrict_members == CHAT_MEMBER['can_restrict_members'] + assert chat_member.can_restrict_members == CHAT_MEMBER["can_restrict_members"] assert isinstance(chat_member.can_pin_messages, bool) - assert chat_member.can_pin_messages == CHAT_MEMBER['can_pin_messages'] + assert chat_member.can_pin_messages == CHAT_MEMBER["can_pin_messages"] assert isinstance(chat_member.can_promote_members, bool) - assert chat_member.can_promote_members == CHAT_MEMBER['can_promote_members'] + assert chat_member.can_promote_members == CHAT_MEMBER["can_promote_members"] def test_int(): @@ -48,12 +49,12 @@ def test_int(): def test_chat_member_status(): - assert types.ChatMemberStatus.CREATOR == 'creator' - assert types.ChatMemberStatus.ADMINISTRATOR == 'administrator' - assert types.ChatMemberStatus.MEMBER == 'member' - assert types.ChatMemberStatus.RESTRICTED == 'restricted' - assert types.ChatMemberStatus.LEFT == 'left' - assert types.ChatMemberStatus.KICKED == 'kicked' + assert types.ChatMemberStatus.CREATOR == "creator" + assert types.ChatMemberStatus.ADMINISTRATOR == "administrator" + assert types.ChatMemberStatus.MEMBER == "member" + assert types.ChatMemberStatus.RESTRICTED == "restricted" + assert types.ChatMemberStatus.LEFT == "left" + assert types.ChatMemberStatus.KICKED == "kicked" def test_chat_member_status_filters(): diff --git a/tests/types/test_document.py b/tests/types/test_document.py index 686f0481..ec26cb14 100644 --- a/tests/types/test_document.py +++ b/tests/types/test_document.py @@ -1,4 +1,5 @@ from aiogram import types + from .dataset import DOCUMENT document = types.Document(**DOCUMENT) diff --git a/tests/types/test_game.py b/tests/types/test_game.py index 8d0433cd..67212ff4 100644 --- a/tests/types/test_game.py +++ b/tests/types/test_game.py @@ -1,4 +1,5 @@ from aiogram import types + from .dataset import GAME game = types.Game(**GAME) diff --git a/tests/types/test_message.py b/tests/types/test_message.py index e25c338d..85adfcae 100644 --- a/tests/types/test_message.py +++ b/tests/types/test_message.py @@ -1,6 +1,7 @@ import datetime from aiogram import types + from .dataset import MESSAGE message = types.Message(**MESSAGE) diff --git a/tests/types/test_photo.py b/tests/types/test_photo.py index 36e965d5..7e33a4d4 100644 --- a/tests/types/test_photo.py +++ b/tests/types/test_photo.py @@ -1,4 +1,5 @@ from aiogram import types + from .dataset import PHOTO photo = types.PhotoSize(**PHOTO) diff --git a/tests/types/test_update.py b/tests/types/test_update.py index adda3123..0c68045e 100644 --- a/tests/types/test_update.py +++ b/tests/types/test_update.py @@ -1,4 +1,5 @@ from aiogram import types + from .dataset import UPDATE update = types.Update(**UPDATE) diff --git a/tests/types/test_user.py b/tests/types/test_user.py index 37861205..d6887ff1 100644 --- a/tests/types/test_user.py +++ b/tests/types/test_user.py @@ -1,6 +1,7 @@ from babel import Locale from aiogram import types + from .dataset import USER user = types.User(**USER) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index aff44213..00000000 --- a/tox.ini +++ /dev/null @@ -1,7 +0,0 @@ -[tox] -envlist = py37 - -[testenv] -deps = -rdev_requirements.txt -commands = pytest -skip_install = true \ No newline at end of file