Merge branch 'dev-2.x' into patch-2

This commit is contained in:
Andrey Tikhonov 2022-08-14 18:03:08 +03:00
commit bef9c66929
47 changed files with 1728 additions and 720 deletions

98
.github/ISSUE_TEMPLATE/bug.yaml vendored Normal file
View file

@ -0,0 +1,98 @@
name: Bug report
description: Report issues affecting the framework or the documentation.
labels:
- bug
body:
- type: checkboxes
attributes:
label: Checklist
options:
- label: I am sure the error is coming from aiogram code
required: true
- label: I have searched in the issue tracker for similar bug reports, including closed ones
required: true
- type: markdown
attributes:
value: |
## Context
Please provide as much information as possible. This will help us to reproduce the issue and fix it.
- type: input
attributes:
label: Operating system
placeholder: e.g. Ubuntu 20.04.2 LTS
validations:
required: true
- type: input
attributes:
label: Python version
placeholder: e.g. 3.10.1
validations:
required: true
- type: input
attributes:
label: aiogram version
placeholder: e.g. 2.21 or 3.0b3
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: Please describe the behavior you are expecting.
placeholder: E.g. the bot should send a message with the text "Hello, world!".
validations:
required: true
- type: textarea
attributes:
label: Current behavior
description: Please describe the behavior you are currently experiencing.
placeholder: E.g. the bot doesn't send any message.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Please describe the steps you took to reproduce the behavior.
placeholder: |
1. step 1
2. step 2
3. ...
4. you get it...
validations:
required: true
- type: textarea
attributes:
label: Code example
description: Provide a [minimal, reproducible](https://stackoverflow.com/help/minimal-reproducible-example) and properly formatted example (if applicable).
placeholder: |
from aiogram import Bot, Dispatcher
...
render: python3
- type: textarea
attributes:
label: Logs
description: Provide the complete traceback (if applicable) or other kind of logs.
placeholder: |
Traceback (most recent call last):
File "main.py", line 1, in <module>
...
SomeException: ...
render: sh
- type: textarea
attributes:
label: Additional information
description: Please provide any additional information that may help us to reproduce the issue.
placeholder: |
E.g. this behavior is reproducible only in group chats.
You can also attach additional screenshots, logs, or other files.

View file

@ -1,45 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
---
---
name: Bug report
about: Create a report to help us improve
---
## Context
Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions.
* Operating System:
* Python Version:
* aiogram version:
* aiohttp version:
* uvloop version (if installed):
## Expected Behavior
Please describe the behavior you are expecting
## Current Behavior
What is the current behavior?
## Failure Information (for bugs)
Please help provide information about the failure if this is a bug. If it is not a bug, please remove the rest of this template.
### Steps to Reproduce
Please provide detailed steps for reproducing the issue.
1. step 1
2. step 2
3. you get it...
### Failure Logs
Please include any relevant log snippets or files here.

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,11 @@
blank_issues_enabled: true
contact_links:
- name: Discuss anything related to the framework
url: https://github.com/aiogram/aiogram/discussions
about: Ask a question about aiogram or share your code snippets and ideas.
- name: Join our Telegram channel
url: https://t.me/aiogram_live
about: Get the latest updates about the framework.
- name: Join our Telegram chat
url: https://t.me/aiogram
about: Get help, ask questions, and discuss the framework in real-time.

44
.github/ISSUE_TEMPLATE/feature.yaml vendored Normal file
View file

@ -0,0 +1,44 @@
name: Feature request
description: Report features you would like to see or improve in the framework.
labels:
- enhancement
body:
- type: textarea
attributes:
label: Problem
description: Is your feature request related to a specific problem? If not, please describe the general idea of your request.
placeholder: e.g. I want to send a photo to a user by url.
validations:
required: true
- type: textarea
attributes:
label: Possible solution
description: Describe the solution you would like to see in the framework.
placeholder: e.g. Add a method to send a photo to a user by url.
validations:
required: true
- type: textarea
attributes:
label: Alternatives
description: What other solutions do you have in mind?
placeholder: e.g. I'm sending a text message with photo url.
- type: textarea
attributes:
label: Code example
description: A small code example that demonstrates the behavior you would like to see.
placeholder: |
await bot.send_photo(user_id, photo_url)
...
render: python3
- type: textarea
attributes:
label: Additional information
description: Any additional information you would like to provide.
placeholder: |
E.g. this method should also cache images to speed up further sending.
You can also attach additional pictures or other files.

View file

@ -1,17 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,35 +1,52 @@
# Description
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
<!--
Please include a summary of the change.
Fixes # (issue)
e.g. Add a new awesome feature or Fix documentation typo.
Please also include relevant motivation and context.
If you are fixing an issue, specify what issue is fixed.
e.g. Fix #12345
-->
Type here...
## Type of change
<!--
Please delete options that are not relevant.
-->
- [ ] Documentation (typos, code examples or any documentation update)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
- Bug fix (non-breaking change that fixes an issue)
- New feature (non-breaking change that adds functionality)
- Documentation (typos, code examples or any documentation update)
# How Has This Been Tested?
# How has this been tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
<!--
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
Please also list any relevant details for your test configuration.
-->
- [ ] Test A
- [ ] Test B
Type here...
**Test Configuration**:
* Operating System:
* Python version:
## Test Configuration
- Operating system: e.g. Ubuntu 20.04.2 LTS
-Python version: e.g. 3.10.1
# Checklist:
<!--
Please delete options that are not relevant to your change.
-->
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have added tests that prove my fix is effective or that my feature works as expected
- [ ] New and existing unit tests pass locally with my changes
- [ ] My changes generate no new warnings or errors
- [ ] My changes are compatible with minimum requirements of the project
- [ ] I have made corresponding changes to the documentation

17
.github/workflows/label_pr.yaml vendored Normal file
View file

@ -0,0 +1,17 @@
name: Label new pull request
on:
pull_request_target:
types:
- opened
branches:
- dev-2.x
jobs:
put-label:
runs-on: ubuntu-latest
steps:
- name: Add 2.x label
uses: andymckay/labeler@master
with:
add-labels: 2.x

View file

@ -1,4 +1,4 @@
VENV_NAME := venv
VENV_NAME := .venv
PYTHON := $(VENV_NAME)/bin/python
AIOGRAM_VERSION := $(shell $(PYTHON) -c "import aiogram;print(aiogram.__version__)")

View file

@ -6,7 +6,7 @@
[![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-5.7-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api)
[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-6.2-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://docs.aiogram.dev/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)

View file

@ -21,7 +21,7 @@ AIOGramBot
:target: https://pypi.python.org/pypi/aiogram
:alt: Supported python versions
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.7-blue.svg?style=flat-square&logo=telegram
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.2-blue.svg?style=flat-square&logo=telegram
:target: https://core.telegram.org/bots/api
:alt: Telegram Bot API

View file

@ -43,5 +43,5 @@ __all__ = (
'utils',
)
__version__ = '2.19'
__api_version__ = '5.7'
__version__ = '2.22'
__api_version__ = '6.2'

View file

@ -269,6 +269,7 @@ class Methods(Helper):
SEND_STICKER = Item() # sendSticker
GET_STICKER_SET = Item() # getStickerSet
UPLOAD_STICKER_FILE = Item() # uploadStickerFile
GET_CUSTOM_EMOJI_STICKERS = Item() # getCustomEmojiStickers
CREATE_NEW_STICKER_SET = Item() # createNewStickerSet
ADD_STICKER_TO_SET = Item() # addStickerToSet
SET_STICKER_POSITION_IN_SET = Item() # setStickerPositionInSet
@ -278,8 +279,16 @@ class Methods(Helper):
# Inline mode
ANSWER_INLINE_QUERY = Item() # answerInlineQuery
ANSWER_WEB_APP_QUERY = Item() # answerWebAppQuery
SET_CHAT_MENU_BUTTON = Item() # setChatMenuButton
GET_CHAT_MENU_BUTTON = Item() # getChatMenuButton
SET_MY_DEFAULT_ADMINISTRATOR_RIGHTS = Item() # setMyDefaultAdministratorRights
GET_MY_DEFAULT_ADMINISTRATOR_RIGHTS = Item() # getMyDefaultAdministratorRights
# Payments
SEND_INVOICE = Item() # sendInvoice
CREATE_INVOICE_LINK = Item() # createInvoiceLink
ANSWER_SHIPPING_QUERY = Item() # answerShippingQuery
ANSWER_PRE_CHECKOUT_QUERY = Item() # answerPreCheckoutQuery

View file

@ -38,6 +38,7 @@ class BaseBot:
validate_token: Optional[base.Boolean] = True,
parse_mode: typing.Optional[base.String] = None,
disable_web_page_preview: Optional[base.Boolean] = None,
protect_content: Optional[base.Boolean] = None,
timeout: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]] = None,
server: TelegramAPIServer = TELEGRAM_PRODUCTION
):
@ -60,6 +61,9 @@ class BaseBot:
:type parse_mode: :obj:`str`
:param disable_web_page_preview: You can set default disable web page preview parameter
:type disable_web_page_preview: :obj:`bool`
:param protect_content: Protects the contents of sent messages
from forwarding and saving
:type protect_content: :obj:`typing.Optional[base.Boolean]`
:param timeout: Request timeout
:type timeout: :obj:`typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]]`
:param server: Telegram Bot API Server endpoint.
@ -111,6 +115,7 @@ class BaseBot:
self.parse_mode = parse_mode
self.disable_web_page_preview = disable_web_page_preview
self.protect_content = protect_content
async def get_new_session(self) -> aiohttp.ClientSession:
return aiohttp.ClientSession(
@ -361,5 +366,22 @@ class BaseBot:
def disable_web_page_preview(self):
self.disable_web_page_preview = None
@property
def protect_content(self):
return getattr(self, "_protect_content", None)
@protect_content.setter
def protect_content(self, value):
if value is None:
setattr(self, "_protect_content", None)
return
if not isinstance(value, bool):
raise TypeError(f"Protect content must be bool, not {type(value)}")
setattr(self, "_protect_content", value)
@protect_content.deleter
def protect_content(self):
self.protect_content = None
def check_auth_widget(self, data):
return check_integrity(self.__token, data)

View file

@ -8,7 +8,7 @@ import warnings
from .base import BaseBot, api
from .. import types
from ..types import base
from ..utils.deprecated import deprecated, removed_argument
from ..utils.deprecated import deprecated
from ..utils.exceptions import ValidationError
from ..utils.mixins import DataMixin, ContextInstanceMixin
from ..utils.payload import generate_payload, prepare_arg, prepare_attachment, prepare_file
@ -117,6 +117,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
max_connections: typing.Optional[base.Integer] = None,
allowed_updates: typing.Optional[typing.List[base.String]] = None,
drop_pending_updates: typing.Optional[base.Boolean] = None,
secret_token: typing.Optional[str] = None,
) -> base.Boolean:
"""
Use this method to specify a url and receive incoming updates via an outgoing
@ -165,6 +166,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param drop_pending_updates: Pass True to drop all pending updates
:type drop_pending_updates: :obj:`typing.Optional[base.Boolean]`
:param secret_token: A secret token to be sent in a header X-Telegram-Bot-Api-Secret-Token
in every webhook request, 1-256 characters. Only characters A-Z, a-z, 0-9, _ and - are allowed.
The header is useful to ensure that the request comes from a webhook set by you.
:type secret_token: :obj:`typing.Optional[str]`
:return: Returns true
:rtype: :obj:`base.Boolean`
"""
@ -330,6 +335,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload.setdefault('parse_mode', self.parse_mode)
if self.disable_web_page_preview:
payload.setdefault('disable_web_page_preview', self.disable_web_page_preview)
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
result = await self.request(api.Methods.SEND_MESSAGE, payload)
return types.Message(**result)
@ -370,6 +377,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`types.Message`
"""
payload = generate_payload(**locals())
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
result = await self.request(api.Methods.FORWARD_MESSAGE, payload)
return types.Message(**result)
@ -452,6 +461,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals())
if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
result = await self.request(api.Methods.COPY_MESSAGE, payload)
return types.MessageId(**result)
@ -520,6 +531,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals(), exclude=['photo'])
if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
files = {}
prepare_file(payload, files, 'photo', photo)
@ -610,6 +623,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals(), exclude=['audio', 'thumb'])
if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
files = {}
prepare_file(payload, files, 'audio', audio)
@ -700,6 +715,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals(), exclude=['document'])
if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
files = {}
prepare_file(payload, files, 'document', document)
@ -792,6 +809,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals(), exclude=['video', 'thumb'])
if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
files = {}
prepare_file(payload, files, 'video', video)
@ -887,6 +906,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals(), exclude=["animation", "thumb"])
if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
files = {}
prepare_file(payload, files, 'animation', animation)
@ -967,6 +988,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals(), exclude=['voice'])
if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
files = {}
prepare_file(payload, files, 'voice', voice)
@ -1033,6 +1056,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
reply_markup = prepare_arg(reply_markup)
payload = generate_payload(**locals(), exclude=['video_note'])
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
files = {}
prepare_file(payload, files, 'video_note', video_note)
@ -1089,12 +1114,15 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
# Check MediaGroup quantity
if not (1 <= len(media.media) <= 10):
raise ValidationError("Media group must include 2-10 items as written in docs, but also it works with 1 element")
raise ValidationError(
"Media group must include 2-10 items as written in docs, but also it works with 1 element")
files = dict(media.get_files())
media = prepare_arg(media)
payload = generate_payload(**locals(), exclude=['files'])
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
result = await self.request(api.Methods.SEND_MEDIA_GROUP, payload, files)
return [types.Message(**message) for message in result]
@ -1168,6 +1196,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
reply_markup = prepare_arg(reply_markup)
payload = generate_payload(**locals())
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
result = await self.request(api.Methods.SEND_LOCATION, payload)
return types.Message(**result)
@ -1346,6 +1376,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
reply_markup = prepare_arg(reply_markup)
payload = generate_payload(**locals())
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
result = await self.request(api.Methods.SEND_VENUE, payload)
return types.Message(**result)
@ -1409,6 +1441,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals())
result = await self.request(api.Methods.SEND_CONTACT, payload)
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
return types.Message(**result)
async def send_poll(self,
@ -1527,6 +1561,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals())
if self.parse_mode and explanation_entities is None:
payload.setdefault('explanation_parse_mode', self.parse_mode)
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
result = await self.request(api.Methods.SEND_POLL, payload)
return types.Message(**result)
@ -1586,6 +1622,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
reply_markup = prepare_arg(reply_markup)
payload = generate_payload(**locals())
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
result = await self.request(api.Methods.SEND_DICE, payload)
return types.Message(**result)
@ -1704,9 +1742,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
datetime.datetime, datetime.timedelta, None]`
:param revoke_messages: Pass True to delete all messages from
the chat for the user that is being removed. If False, the user
will be able to see messages in the group that were sent before
the user was removed. Always True for supergroups and channels.
the chat for the user that is being removed. If False, the user
will be able to see messages in the group that were sent before
the user was removed. Always True for supergroups and channels.
:type revoke_messages: :obj:`typing.Optional[base.Boolean]`
:return: Returns True on success
@ -1834,6 +1872,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
can_restrict_members: typing.Optional[base.Boolean] = None,
can_pin_messages: typing.Optional[base.Boolean] = None,
can_promote_members: typing.Optional[base.Boolean] = None,
can_manage_video_chats: typing.Optional[base.Boolean] = None,
) -> base.Boolean:
"""
Use this method to promote or demote a user in a supergroup or a channel.
@ -1885,9 +1924,17 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
directly or indirectly (promoted by administrators that were appointed by him)
:type can_promote_members: :obj:`typing.Optional[base.Boolean]`
:param can_manage_video_chats: Pass True, if the administrator can manage video chats
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
if can_manage_voice_chats:
warnings.warn(
"Argument `can_manage_voice_chats` was renamed to `can_manage_video_chats` and will be removed in aiogram 2.21")
can_manage_video_chats = can_manage_voice_chats
can_manage_voice_chats = None
payload = generate_payload(**locals())
return await self.request(api.Methods.PROMOTE_CHAT_MEMBER, payload)
@ -1910,11 +1957,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return await self.request(api.Methods.SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE, payload)
@removed_argument("until_date", "2.19")
async def ban_chat_sender_chat(
self,
chat_id: typing.Union[base.Integer, base.String],
sender_chat_id: base.Integer,
self,
chat_id: typing.Union[base.Integer, base.String],
sender_chat_id: base.Integer,
):
"""Ban a channel chat in a supergroup or a channel.
@ -1937,9 +1983,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return await self.request(api.Methods.BAN_CHAT_SENDER_CHAT, payload)
async def unban_chat_sender_chat(
self,
chat_id: typing.Union[base.Integer, base.String],
sender_chat_id: base.Integer,
self,
chat_id: typing.Union[base.Integer, base.String],
sender_chat_id: base.Integer,
):
"""Unban a previously banned channel chat in a supergroup or
channel.
@ -2587,6 +2633,87 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.GET_MY_COMMANDS, payload)
return [types.BotCommand(**bot_command_data) for bot_command_data in result]
async def set_chat_menu_button(self, chat_id: typing.Optional[base.Integer] = None,
menu_button: typing.Optional[types.MenuButton] = None) -> bool:
"""
Use this method to change bot's menu button in a private chat, or the default menu button.
Returns True on success.
Source https://core.telegram.org/bots/api#setchatmenubutton
:param chat_id: Unique identifier for the target private chat.
If not specified, default bot's menu button will be changed
:param menu_button:
A JSON-serialized object for the new bot's menu button. Defaults to MenuButtonDefault
:return: Returns True on success.
"""
menu_button = prepare_arg(menu_button)
payload = generate_payload(**locals())
return await self.request(api.Methods.SET_CHAT_MENU_BUTTON, payload)
async def get_chat_menu_button(self, chat_id: typing.Optional[base.Integer] = None) -> typing.Union[
"types.MenuButtonCommands",
"types.MenuButtonDefault",
"types.MenuButtonWebApp",
]:
"""
Use this method to get the current value of the bot's menu button in a private chat,
or the default menu button.
Returns MenuButton on success.
Source https://core.telegram.org/bots/api#getchatmenu
:param chat_id: Unique identifier for the target private chat. If not specified,
default bot's menu button will be returned
:return: Returns MenuButton on success.
"""
payload = generate_payload(**locals())
result = await self.request(api.Methods.GET_CHAT_MENU_BUTTON, payload)
return types.MenuButton.resolve(**result)
async def set_my_default_administrator_rights(self, rights: typing.Optional[types.ChatAdministratorRights] = None,
for_channels: typing.Optional[base.Boolean] = None) -> base.Boolean:
"""
Use this method to change default administrator rights of the bot for adding it as an administrator
to groups or channels.
Returns True on success.
Source: https://core.telegram.org/bots/api#setmydefaultadministratorrights
:param rights: A JSON-serialized object, describing new default administrator rights.
If not specified, the default administrator rights will be cleared.
:param for_channels:
Pass True to change default administrator rights of the bot in channels.
Otherwise, default administrator rights of the bot for groups and supergroups will be changed.
:return: Returns True on success.
"""
rights = prepare_arg(rights)
payload = generate_payload(**locals())
return await self.request(api.Methods.SET_MY_DEFAULT_ADMINISTRATOR_RIGHTS, payload)
async def get_my_default_administrator_rights(self,
for_channels: typing.Optional[base.Boolean] = None
) -> types.ChatAdministratorRights:
"""
Use this method to get the current default administrator rights of the bot.
Returns ChatAdministratorRights on success.
Source: https://core.telegram.org/bots/api#getmydefaultadministratorrights
:param for_channels: Pass True to get default administrator rights of the bot in channels.
Otherwise, default administrator rights of the bot for groups and supergroups will be returned.
:return:
"""
payload = generate_payload(**locals())
result = await self.request(api.Methods.GET_MY_DEFAULT_ADMINISTRATOR_RIGHTS, payload)
return types.ChatAdministratorRights(**result)
async def edit_message_text(self,
text: base.String,
chat_id: typing.Union[base.Integer, base.String, None] = None,
@ -2871,6 +2998,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
reply_markup = prepare_arg(reply_markup)
payload = generate_payload(**locals(), exclude=['sticker'])
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
files = {}
prepare_file(payload, files, 'sticker', sticker)
@ -2917,6 +3046,23 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.UPLOAD_STICKER_FILE, payload, files)
return types.File(**result)
async def get_custom_emoji_stickers(self, custom_emoji_ids: typing.List[base.String]) -> typing.List[types.Sticker]:
"""
Use this method to get information about custom emoji stickers by their identifiers.
Source: https://core.telegram.org/bots/api#uploadstickerfile
:param custom_emoji_ids: User identifier of sticker file owner
:type custom_emoji_ids: :obj:`typing.List[base.String]`
:return: Returns an Array of Sticker objects.
:rtype: :obj:`typing.List[types.Sticker]`
"""
payload = generate_payload(**locals())
result = await self.request(api.Methods.GET_CUSTOM_EMOJI_STICKERS, payload)
return [types.Sticker(**item) for item in result]
async def create_new_sticker_set(self,
user_id: base.Integer,
name: base.String,
@ -2926,6 +3072,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
tgs_sticker: base.InputFile = None,
webm_sticker: base.InputFile = None,
contains_masks: typing.Optional[base.Boolean] = None,
sticker_type: typing.Optional[base.String] = None,
mask_position: typing.Optional[types.MaskPosition] = None) -> base.Boolean:
"""
Use this method to create a new sticker set owned by a user.
@ -2954,7 +3101,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:type tgs_sticker: :obj:`base.InputFile`
:param webm_sticker: WEBM video with the sticker, uploaded using multipart/form-data.
See https://core.telegram.org/stickers#video-sticker-requirements for technical requirements
:type webm_sticker: :obj:`base.InputFile`
:type webm_sticker: :obj:`base.String`
:param sticker_type: Type of stickers in the set, pass regular or mask.
Custom emoji sticker sets can't be created via the Bot API at the moment.
By default, a regular sticker set is created.
:type sticker_type: :obj:`base.InputFile`
: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
@ -2966,6 +3117,12 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
mask_position = prepare_arg(mask_position)
payload = generate_payload(**locals(), exclude=['png_sticker', 'tgs_sticker', 'webm_sticker'])
if contains_masks is not None:
warnings.warn(
message="The parameter `contains_masks` deprecated, use `sticker_type` instead.",
category=DeprecationWarning,
stacklevel=2
)
files = {}
prepare_file(payload, files, 'png_sticker', png_sticker)
@ -3133,6 +3290,25 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return await self.request(api.Methods.ANSWER_INLINE_QUERY, payload)
async def answer_web_app_query(self, web_app_query_id: base.String,
result: types.InlineQueryResult) -> types.SentWebAppMessage:
"""
Use this method to set result of interaction with web app and send corresponding message
on behalf of the user to the chat from which the query originated.
On success, SentWebAppMessage is returned.
Source https://core.telegram.org/bots/api#answerwebappquery
:param web_app_query_id: Unique identifier for the answered query
:param result: A JSON-serialized object with a description of the message to send
:return: On success, SentWebAppMessage is returned.
"""
result = prepare_arg(result)
payload = generate_payload(**locals())
response = await self.request(api.Methods.ANSWER_WEB_APP_QUERY, payload)
return types.SentWebAppMessage(**response)
# === Payments ===
# https://core.telegram.org/bots/api#payments
@ -3284,10 +3460,75 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
reply_markup = prepare_arg(reply_markup)
provider_data = prepare_arg(provider_data)
payload_ = generate_payload(**locals())
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
result = await self.request(api.Methods.SEND_INVOICE, payload_)
return types.Message(**result)
async def create_invoice_link(self,
title: base.String,
description: base.String,
payload: base.String,
provider_token: base.String,
currency: base.String,
prices: typing.List[types.LabeledPrice],
max_tip_amount: typing.Optional[int] = None,
suggested_tip_amounts: typing.Optional[typing.List[int]] = None,
provider_data: typing.Optional[base.String] = None,
photo_url: typing.Optional[str] = None,
photo_size: typing.Optional[int] = None,
photo_width: typing.Optional[int] = None,
photo_height: typing.Optional[int] = None,
need_name: typing.Optional[bool] = None,
need_phone_number: typing.Optional[bool] = None,
need_email: typing.Optional[bool] = None,
need_shipping_address: typing.Optional[bool] = None,
send_phone_number_to_provider: typing.Optional[bool] = None,
send_email_to_provider: typing.Optional[bool] = None,
is_flexible: typing.Optional[bool] = None,
) -> str:
"""
Use this method to create a link for an invoice. On success, the created link is returned.
Source: https://core.telegram.org/bots/api#createinvoicelink
:param title: Product name, 1-32 characters
:param description: Product description, 1-255 characters
:param payload: Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.
:param provider_token: Payment provider token, obtained via BotFather
:param currency: Three-letter ISO 4217 currency code, see more on currencies
:param prices: Price breakdown, a JSON-serialized list of components
(e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)
:param max_tip_amount: The maximum accepted amount for tips in the smallest units of the currency
(integer, not float/double). For example, for a maximum tip of US$ 1.45 pass max_tip_amount = 145.
See the exp parameter in currencies.json, it shows the number of digits past the decimal point for
each currency (2 for the majority of currencies). Defaults to 0
:param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the smallest units
of the currency (integer, not float/double). At most 4 suggested tip amounts can be specified.
The suggested tip amounts must be positive, passed in a strictly increased order and must not
exceed max_tip_amount.
:param provider_data: JSON-serialized data about the invoice, which will be shared with the payment provider.
A detailed description of required fields should be provided by the payment provider.
:param photo_url: URL of the product photo for the invoice.
Can be a photo of the goods or a marketing image for a service.
:param photo_size: Photo size in bytes
:param photo_width: Photo width
:param photo_height: Photo height
:param need_name: Pass True, if you require the user's full name to complete the order
:param need_phone_number: Pass True, if you require the user's phone number to complete the order
:param need_email: Pass True, if you require the user's email address to complete the order
:param need_shipping_address: Pass True, if you require the user's shipping address to complete the order
:param send_phone_number_to_provider: Pass True, if the user's phone number should be sent to the provider
:param send_email_to_provider: Pass True, if the user's email address should be sent to the provider
:param is_flexible: Pass True, if the final price depends on the shipping method
:return:
"""
prices = prepare_arg([price.to_python() if hasattr(price, 'to_python') else price for price in prices])
payload = generate_payload(**locals())
return await self.request(api.Methods.CREATE_INVOICE_LINK, payload)
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.Optional[base.String] = None) -> base.Boolean:
@ -3426,6 +3667,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
reply_markup = prepare_arg(reply_markup)
payload = generate_payload(**locals())
if self.protect_content is not None:
payload.setdefault('protect_content', self.protect_content)
result = await self.request(api.Methods.SEND_GAME, payload)
return types.Message(**result)

View file

@ -213,5 +213,5 @@ class MongoStorage(BaseStorage):
:return: list of tuples where first element is chat id and second is user id
"""
db = await self.get_db()
items = await db[STATE].find().to_list()
items = await db[STATE].find().to_list(length=None)
return [(int(item['chat']), int(item['user'])) for item in items]

View file

@ -1367,7 +1367,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
if bucket and key in bucket:
del bucket['key']
del bucket[key]
await self.storage.set_bucket(chat=chat_id, user=user_id, bucket=bucket)
return True
return False

View file

@ -10,7 +10,7 @@ from babel.support import LazyProxy
from aiogram import types
from aiogram.dispatcher.filters.filters import BoundFilter, Filter
from aiogram.types import CallbackQuery, ChatType, InlineQuery, Message, Poll, ChatMemberUpdated
from aiogram.types import CallbackQuery, ChatType, InlineQuery, Message, Poll, ChatMemberUpdated, BotCommand
ChatIDArgumentType = typing.Union[typing.Iterable[typing.Union[int, str]], str, int]
@ -34,7 +34,7 @@ class Command(Filter):
By default this filter is registered for messages and edited messages handlers.
"""
def __init__(self, commands: Union[Iterable, str],
def __init__(self, commands: Union[Iterable[Union[str, BotCommand]], str, BotCommand],
prefixes: Union[Iterable, str] = '/',
ignore_case: bool = True,
ignore_mention: bool = False,
@ -66,8 +66,19 @@ class Command(Filter):
@dp.message_handler(commands=['myCommand'], commands_ignore_caption=False, content_types=ContentType.ANY)
@dp.message_handler(Command(['myCommand'], ignore_caption=False), content_types=[ContentType.TEXT, ContentType.DOCUMENT])
"""
if isinstance(commands, str):
if isinstance(commands, (str, BotCommand)):
commands = (commands,)
elif isinstance(commands, Iterable):
if not all(isinstance(cmd, (str, BotCommand)) for cmd in commands):
raise ValueError(
"Command filter only supports str, BotCommand object or their Iterable"
)
else:
raise ValueError(
"Command filter doesn't support {} as input. "
"It only supports str, BotCommand object or their Iterable".format(type(commands))
)
commands = [cmd.command if isinstance(cmd, BotCommand) else cmd for cmd in commands]
self.commands = list(map(str.lower, commands)) if ignore_case else commands
self.prefixes = prefixes
@ -728,18 +739,20 @@ class ChatTypeFilter(BoundFilter):
self.chat_type: typing.Set[str] = set(chat_type)
async def check(self, obj: Union[Message, CallbackQuery, ChatMemberUpdated]):
async def check(self, obj: Union[Message, CallbackQuery, ChatMemberUpdated, InlineQuery]):
if isinstance(obj, Message):
obj = obj.chat
chat_type = obj.chat.type
elif isinstance(obj, CallbackQuery):
obj = obj.message.chat
chat_type = obj.message.chat.type if obj.message else None
elif isinstance(obj, ChatMemberUpdated):
obj = obj.chat
chat_type = obj.chat.type
elif isinstance(obj, InlineQuery):
chat_type = obj.chat_type
else:
warnings.warn("ChatTypeFilter doesn't support %s as input", type(obj))
return False
return obj.type in self.chat_type
return chat_type in self.chat_type
class MediaGroupFilter(BoundFilter):

View file

@ -76,8 +76,8 @@ class Handler:
"""
for handler_obj in self.handlers:
registered = handler_obj.handler
if handler is registered:
self.handlers.remove(handler_obj)
if handler in self.handlers:
self.handlers.remove(handler)
return True
raise ValueError('This handler is not registered!')

View file

@ -271,7 +271,7 @@ class BaseStorage:
:param user:
:return:
"""
await self.set_data(chat=chat, user=user, data={})
await self.set_bucket(chat=chat, user=user, bucket={})
@staticmethod
def resolve_state(value):

View file

@ -448,6 +448,29 @@ class DisableWebPagePreviewMixin:
return bot.disable_web_page_preview
class ProtectContentMixin:
def protect_content(self):
"""
Protect content
:return:
"""
setattr(self, "protect_content", True)
return self
@staticmethod
def _global_protect_content():
"""
Detect global protect content value
:return:
"""
from aiogram import Bot
bot = Bot.get_current()
if bot is not None:
return bot.protect_content
class ParseModeMixin:
def as_html(self):
"""
@ -480,7 +503,9 @@ class ParseModeMixin:
return bot.parse_mode
class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificationMixin, DisableWebPagePreviewMixin):
class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin,
DisableNotificationMixin, DisableWebPagePreviewMixin,
ProtectContentMixin):
"""
You can send message with webhook by using this instance of this object.
All arguments is equal with Bot.send_message method.
@ -488,7 +513,7 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio
__slots__ = ('chat_id', 'text', 'parse_mode',
'disable_web_page_preview', 'disable_notification',
'reply_to_message_id', 'reply_markup')
'protect_content', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_MESSAGE
@ -497,6 +522,7 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio
parse_mode: Optional[String] = None,
disable_web_page_preview: Optional[Boolean] = None,
disable_notification: Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[Union[
types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
@ -509,6 +535,8 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio
: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 protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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,
@ -520,12 +548,15 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio
parse_mode = self._global_parse_mode()
if disable_web_page_preview is None:
disable_web_page_preview = self._global_disable_web_page_preview()
if protect_content is None:
protect_content = self._global_protect_content()
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.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -536,6 +567,7 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio
'parse_mode': self.parse_mode,
'disable_web_page_preview': self.disable_web_page_preview,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
@ -565,18 +597,19 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio
return self
class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
Use that response type for forward messages of any kind on to webhook.
"""
__slots__ = ('chat_id', 'from_chat_id', 'message_id', 'disable_notification')
__slots__ = ('chat_id', 'from_chat_id', 'message_id', 'disable_notification', 'protect_content')
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):
disable_notification: Optional[Boolean] = None,
protect_content: 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)
@ -584,12 +617,18 @@ class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin):
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 protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
:param message_id: Integer - Message identifier in the chat specified in from_chat_id
"""
if protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.from_chat_id = from_chat_id
self.message_id = message_id
self.disable_notification = disable_notification
self.protect_content = protect_content
def message(self, message: types.Message):
"""
@ -607,16 +646,18 @@ class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'chat_id': self.chat_id,
'from_chat_id': self.from_chat_id,
'message_id': self.message_id,
'disable_notification': self.disable_notification
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
}
class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
Use that response type for send photo on to webhook.
"""
__slots__ = ('chat_id', 'photo', 'caption', 'disable_notification', 'reply_to_message_id', 'reply_markup')
__slots__ = ('chat_id', 'photo', 'caption', 'disable_notification',
'protect_content', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_PHOTO
@ -624,6 +665,7 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
photo: String,
caption: Optional[String] = None,
disable_notification: Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[Union[
types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
@ -637,15 +679,21 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
0-1024 characters after entities parsing
:param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive
a notification with no sound.
:param protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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 protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.photo = photo
self.caption = caption
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -655,18 +703,20 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'photo': self.photo,
'caption': self.caption,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
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')
'disable_notification', 'protect_content',
'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_AUDIO
@ -677,6 +727,7 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
performer: Optional[String] = None,
title: Optional[String] = None,
disable_notification: Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[Union[
types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
@ -693,11 +744,16 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
:param title: String (Optional) - Track name
:param disable_notification: Boolean (Optional) - Sends the message silently.
Users will receive a notification with no sound.
:param protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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 protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.audio = audio
self.caption = caption
@ -705,6 +761,7 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
self.performer = performer
self.title = title
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -717,17 +774,19 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'performer': self.performer,
'title': self.title,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
Use that response type for send document on to webhook.
"""
__slots__ = ('chat_id', 'document', 'caption', 'disable_notification', 'reply_to_message_id', 'reply_markup')
__slots__ = ('chat_id', 'document', 'caption', 'disable_notification',
'protect_content', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_DOCUMENT
@ -735,6 +794,7 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin):
document: String,
caption: Optional[String] = None,
disable_notification: Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[Union[
types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
@ -749,15 +809,21 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin):
(may also be used when resending documents by file_id), 0-1024 characters after entities parsing
:param disable_notification: Boolean (Optional) - Sends the message silently.
Users will receive a notification with no sound.
:param protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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 protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.document = document
self.caption = caption
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -767,17 +833,19 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'document': self.document,
'caption': self.caption,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
Use that response type for send video on to webhook.
"""
__slots__ = ('chat_id', 'video', 'duration', 'width', 'height', 'caption', 'disable_notification',
__slots__ = ('chat_id', 'video', 'duration', 'width', 'height', 'caption',
'disable_notification', 'protect_content',
'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_VIDEO
@ -789,6 +857,7 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
height: Optional[Integer] = None,
caption: Optional[String] = None,
disable_notification: Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[Union[
types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
@ -806,11 +875,16 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
0-1024 characters after entities parsing
:param disable_notification: Boolean (Optional) - Sends the message silently.
Users will receive a notification with no sound.
:param protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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 protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.video = video
self.duration = duration
@ -818,6 +892,7 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
self.height = height
self.caption = caption
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -830,18 +905,19 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'height': self.height,
'caption': self.caption,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
Use that response type for send voice on to webhook.
"""
__slots__ = ('chat_id', 'voice', 'caption', 'duration', 'disable_notification',
'reply_to_message_id', 'reply_markup')
'protect_content', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_VOICE
@ -850,6 +926,7 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
caption: Optional[String] = None,
duration: Optional[Integer] = None,
disable_notification: Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[Union[
types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
@ -864,16 +941,22 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
: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 protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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 protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.voice = voice
self.caption = caption
self.duration = duration
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -884,18 +967,19 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'caption': self.caption,
'duration': self.duration,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
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')
'protect_content', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_VIDEO_NOTE
@ -904,6 +988,7 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin):
duration: Optional[Integer] = None,
length: Optional[Integer] = None,
disable_notification: Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[Union[
types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
@ -917,16 +1002,22 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin):
: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 protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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 protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.video_note = video_note
self.duration = duration
self.length = length
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -937,23 +1028,26 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'duration': self.duration,
'length': self.length,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
Use this method to send a group of photos or videos as an album.
"""
__slots__ = ('chat_id', 'media', 'disable_notification', 'reply_to_message_id')
__slots__ = ('chat_id', 'media', 'disable_notification',
'protect_content', '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.Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: typing.Optional[Integer] = None):
"""
Use this method to send a group of photos or videos as an album.
@ -966,6 +1060,8 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
: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.Optional[base.Boolean]`
:param protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
:param reply_to_message_id: If the message is a reply, ID of the original message
:type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
:return: On success, an array of the sent Messages is returned.
@ -976,10 +1072,13 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
elif isinstance(media, list):
# Convert list to MediaGroup
media = types.MediaGroup(media)
if protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.media = media
self.disable_notifications = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
def prepare(self):
@ -993,6 +1092,7 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'chat_id': self.chat_id,
'media': media,
'disable_notifications': self.disable_notifications,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id
}
@ -1023,18 +1123,20 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
return self
class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
Use that response type for send location on to webhook.
"""
__slots__ = ('chat_id', 'latitude', 'longitude', 'disable_notification', 'reply_to_message_id', 'reply_markup')
__slots__ = ('chat_id', 'latitude', 'longitude', 'disable_notification',
'protect_content', '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,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[Union[
types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
@ -1045,15 +1147,21 @@ class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin):
:param longitude: Float - Longitude of location
:param disable_notification: Boolean (Optional) - Sends the message silently.
Users will receive a notification with no sound.
:param protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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 protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.latitude = latitude
self.longitude = longitude
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -1063,18 +1171,21 @@ class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'latitude': self.latitude,
'longitude': self.longitude,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
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')
__slots__ = ('chat_id', 'latitude', 'longitude', 'title',
'address', 'foursquare_id',
'disable_notification', 'protect_content',
'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_VENUE
@ -1085,6 +1196,7 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin):
address: String,
foursquare_id: Optional[String] = None,
disable_notification: Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[Union[
types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
@ -1098,11 +1210,16 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin):
: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 protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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 protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.latitude = latitude
self.longitude = longitude
@ -1110,6 +1227,7 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin):
self.address = address
self.foursquare_id = foursquare_id
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -1122,18 +1240,19 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'address': self.address,
'foursquare_id': self.foursquare_id,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
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')
'protect_content', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_CONTACT
@ -1142,6 +1261,7 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin):
first_name: String,
last_name: Optional[String] = None,
disable_notification: Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[Union[
types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
@ -1153,16 +1273,22 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin):
: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 protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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.
"""
if protect_content is None:
protect_content = self._global_protect_content()
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.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -1173,6 +1299,7 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'first_name': self.first_name,
'last_name': self.last_name,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
@ -1730,18 +1857,20 @@ class DeleteMessage(BaseResponse):
}
class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
Use that response type for send sticker on to webhook.
"""
__slots__ = ('chat_id', 'sticker', 'disable_notification', 'reply_to_message_id', 'reply_markup')
__slots__ = ('chat_id', 'sticker', 'disable_notification', 'protect_content',
'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,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[
Union[types.InlineKeyboardMarkup,
@ -1755,14 +1884,20 @@ class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin):
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 protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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 protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.sticker = sticker
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -1771,6 +1906,7 @@ class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'chat_id': self.chat_id,
'sticker': self.sticker,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
@ -1985,7 +2121,7 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'currency', 'prices', 'photo_url', 'photo_size', 'photo_width', 'photo_height',
'need_name', 'need_phone_number', 'need_email', 'need_shipping_address',
'send_phone_number_to_provider', 'send_email_to_provider', 'is_flexible',
'disable_notification', 'reply_to_message_id', 'reply_markup')
'disable_notification', 'protect_content', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_INVOICE
@ -2009,6 +2145,7 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
send_email_to_provider: Optional[Boolean] = None,
is_flexible: Optional[Boolean] = None,
disable_notification: Optional[Boolean] = None,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[types.InlineKeyboardMarkup] = None):
"""
@ -2042,6 +2179,8 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
: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 protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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.
@ -2066,6 +2205,7 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
self.send_email_to_provider = send_email_to_provider
self.is_flexible = is_flexible
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -2091,6 +2231,7 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'send_email_to_provider': self.send_email_to_provider,
'is_flexible': self.is_flexible,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}
@ -2168,18 +2309,20 @@ class AnswerPreCheckoutQuery(BaseResponse):
}
class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin):
class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin):
"""
Use that response type for send game on to webhook.
"""
__slots__ = ('chat_id', 'game_short_name', 'disable_notification', 'reply_to_message_id', 'reply_markup')
__slots__ = ('chat_id', 'game_short_name', 'disable_notification',
'protect_content', '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,
protect_content: Optional[Boolean] = None,
reply_to_message_id: Optional[Integer] = None,
reply_markup: Optional[types.InlineKeyboardMarkup] = None):
"""
@ -2188,13 +2331,19 @@ class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Set up your games via Botfather.
:param disable_notification: Boolean (Optional) - Sends the message silently.
Users will receive a notification with no sound.
:param protect_content: Boolean (Optional) - Protects the contents of sent messages
from forwarding and saving
: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.
"""
if protect_content is None:
protect_content = self._global_protect_content()
self.chat_id = chat_id
self.game_short_name = game_short_name
self.disable_notification = disable_notification
self.protect_content = protect_content
self.reply_to_message_id = reply_to_message_id
self.reply_markup = reply_markup
@ -2203,6 +2352,7 @@ class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin):
'chat_id': self.chat_id,
'game_short_name': self.game_short_name,
'disable_notification': self.disable_notification,
'protect_content': self.protect_content,
'reply_to_message_id': self.reply_to_message_id,
'reply_markup': prepare_arg(self.reply_markup),
}

View file

@ -11,6 +11,7 @@ from .bot_command_scope import BotCommandScope, BotCommandScopeAllChatAdministra
from .callback_game import CallbackGame
from .callback_query import CallbackQuery
from .chat import Chat, ChatActions, ChatType
from .chat_administrator_rights import ChatAdministratorRights
from .chat_invite_link import ChatInviteLink
from .chat_join_request import ChatJoinRequest
from .chat_location import ChatLocation
@ -48,6 +49,7 @@ from .labeled_price import LabeledPrice
from .location import Location
from .login_url import LoginUrl
from .mask_position import MaskPosition
from .menu_button import MenuButton, MenuButtonCommands, MenuButtonWebApp, MenuButtonDefault
from .message import ContentType, ContentTypes, Message, ParseMode
from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged
from .message_entity import MessageEntity, MessageEntityType
@ -64,6 +66,7 @@ from .pre_checkout_query import PreCheckoutQuery
from .proximity_alert_triggered import ProximityAlertTriggered
from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, KeyboardButtonPollType
from .response_parameters import ResponseParameters
from .sent_web_app_message import SentWebAppMessage
from .shipping_address import ShippingAddress
from .shipping_option import ShippingOption
from .shipping_query import ShippingQuery
@ -75,12 +78,18 @@ from .user import User
from .user_profile_photos import UserProfilePhotos
from .venue import Venue
from .video import Video
from .video_chat_ended import VideoChatEnded
from .video_chat_participants_invited import VideoChatParticipantsInvited
from .video_chat_scheduled import VideoChatScheduled
from .video_chat_started import VideoChatStarted
from .video_note import VideoNote
from .voice import Voice
from .voice_chat_ended import VoiceChatEnded
from .voice_chat_participants_invited import VoiceChatParticipantsInvited
from .voice_chat_scheduled import VoiceChatScheduled
from .voice_chat_started import VoiceChatStarted
from .web_app_data import WebAppData
from .web_app_info import WebAppInfo
from .webhook_info import WebhookInfo
__all__ = (
@ -102,6 +111,7 @@ __all__ = (
'CallbackQuery',
'Chat',
'ChatActions',
'ChatAdministratorRights',
'ChatInviteLink',
'ChatJoinRequest',
'ChatLocation',
@ -174,6 +184,10 @@ __all__ = (
'Location',
'LoginUrl',
'MaskPosition',
'MenuButton',
'MenuButtonCommands',
'MenuButtonWebApp',
'MenuButtonDefault',
'MediaGroup',
'Message',
'MessageAutoDeleteTimerChanged',
@ -201,6 +215,7 @@ __all__ = (
'ReplyKeyboardMarkup',
'ReplyKeyboardRemove',
'ResponseParameters',
'SentWebAppMessage',
'ShippingAddress',
'ShippingOption',
'ShippingQuery',
@ -212,12 +227,18 @@ __all__ = (
'UserProfilePhotos',
'Venue',
'Video',
'VideoChatEnded',
'VideoChatParticipantsInvited',
'VideoChatScheduled',
'VideoChatStarted',
'VideoNote',
'Voice',
'VoiceChatEnded',
'VoiceChatParticipantsInvited',
'VoiceChatScheduled',
'VoiceChatStarted',
'WebAppData',
'WebAppInfo',
'WebhookInfo',
'base',
'fields',

View file

@ -3,12 +3,14 @@ from __future__ import annotations
import io
import logging
import typing
import warnings
from typing import TypeVar
from babel.support import LazyProxy
from .fields import BaseField
from ..utils import json
from ..utils.exceptions import AIOGramWarning
from ..utils.mixins import ContextInstanceMixin
if typing.TYPE_CHECKING:
from ..bot.bot import Bot
@ -243,8 +245,10 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
return self.props[key].set_value(self, value, self.conf.get('parent', self))
self.values[key] = value
# Log warning when Telegram silently adds new Fields
log.warning("Field '%s' doesn't exist in %s", key, self.__class__)
# Show warning when Telegram silently adds new Fields
warnings.warn(f"Bot API Field {key!r} is not defined in {self.__class__!r} class.\n"
"Bot API has been updated. Check for updates at https://telegram.org/blog and "
"https://github.com/aiogram/aiogram/releases", AIOGramWarning)
def __contains__(self, item: str) -> bool:
"""

View file

@ -31,6 +31,9 @@ class Chat(base.TelegramObject):
photo: ChatPhoto = fields.Field(base=ChatPhoto)
bio: base.String = fields.Field()
has_private_forwards: base.Boolean = fields.Field()
has_restricted_voice_and_video_messages: base.Boolean = fields.Field()
join_to_send_messages: base.Boolean = fields.Field()
join_by_request: base.Boolean = fields.Field()
description: base.String = fields.Field()
invite_link: base.String = fields.Field()
pinned_message: 'Message' = fields.Field(base='Message')
@ -652,6 +655,7 @@ class ChatType(helper.Helper):
"""
List of chat types
:key: SENDER
:key: PRIVATE
:key: GROUP
:key: SUPER_GROUP
@ -661,6 +665,7 @@ class ChatType(helper.Helper):
mode = helper.HelperMode.lowercase
SENDER = helper.Item() # sender
PRIVATE = helper.Item() # private
GROUP = helper.Item() # group
SUPERGROUP = helper.Item() # supergroup

View file

@ -0,0 +1,46 @@
from . import base
from . import fields
class ChatAdministratorRights(base.TelegramObject):
"""
Represents rights of an administrator in a chat.
Source: https://core.telegram.org/bots/api#chatadministratorrights
"""
is_anonymous: base.Boolean = fields.Field()
can_manage_chat: base.Boolean = fields.Field()
can_delete_messages: base.Boolean = fields.Field()
can_manage_video_chats: base.Boolean = fields.Field()
can_restrict_members: base.Boolean = fields.Field()
can_promote_members: base.Boolean = fields.Field()
can_change_info: base.Boolean = fields.Field()
can_invite_users: base.Boolean = fields.Field()
can_post_messages: base.Boolean = fields.Field()
can_edit_messages: base.Boolean = fields.Field()
can_pin_messages: base.Boolean = fields.Field()
def __init__(self,
is_anonymous: base.Boolean = None,
can_manage_chat: base.Boolean = None,
can_delete_messages: base.Boolean = None,
can_manage_video_chats: base.Boolean = None,
can_restrict_members: base.Boolean = None,
can_promote_members: base.Boolean = None,
can_change_info: base.Boolean = None,
can_invite_users: base.Boolean = None,
can_post_messages: base.Boolean = None,
can_edit_messages: base.Boolean = None,
can_pin_messages: base.Boolean = None):
super(ChatAdministratorRights, self).__init__(
is_anonymous=is_anonymous,
can_manage_chat=can_manage_chat,
can_delete_messages=can_delete_messages,
can_manage_video_chats=can_manage_video_chats,
can_restrict_members=can_restrict_members,
can_promote_members=can_promote_members,
can_change_info=can_change_info,
can_invite_users=can_invite_users,
can_post_messages=can_post_messages,
can_edit_messages=can_edit_messages,
can_pin_messages=can_pin_messages)

View file

@ -5,7 +5,6 @@ from . import base, fields
from .user import User
from ..utils import helper
T = typing.TypeVar('T')
@ -153,6 +152,7 @@ class ChatMemberAdministrator(ChatMember):
can_edit_messages: base.Boolean = fields.Field()
can_delete_messages: base.Boolean = fields.Field()
can_manage_voice_chats: base.Boolean = fields.Field()
can_manage_video_chats: base.Boolean = fields.Field()
can_restrict_members: base.Boolean = fields.Field()
can_promote_members: base.Boolean = fields.Field()
can_change_info: base.Boolean = fields.Field()

View file

@ -1,6 +1,7 @@
import abc
import datetime
import weakref
import sys
__all__ = ('BaseField', 'Field', 'ListField', 'DateTimeField', 'TextField', 'ListOfLists', 'ConstField')
@ -168,8 +169,13 @@ class DateTimeField(Field):
out: datetime
"""
def serialize(self, value: datetime.datetime):
return round(value.timestamp())
if sys.platform == "win32":
def serialize(self, value: datetime.datetime):
return round((value - datetime.datetime(1970, 1, 1)).total_seconds())
else:
def serialize(self, value: datetime.datetime):
return round(value.timestamp())
def deserialize(self, value, parent=None):
return datetime.datetime.fromtimestamp(value)

View file

@ -4,6 +4,7 @@ from . import base
from . import fields
from .callback_game import CallbackGame
from .login_url import LoginUrl
from .web_app_info import WebAppInfo
class InlineKeyboardMarkup(base.TelegramObject):
@ -95,6 +96,7 @@ class InlineKeyboardButton(base.TelegramObject):
switch_inline_query_current_chat: base.String = fields.Field()
callback_game: CallbackGame = fields.Field(base=CallbackGame)
pay: base.Boolean = fields.Field()
web_app: WebAppInfo = fields.Field(base=WebAppInfo)
def __init__(self, text: base.String,
url: base.String = None,
@ -103,7 +105,9 @@ class InlineKeyboardButton(base.TelegramObject):
switch_inline_query: base.String = None,
switch_inline_query_current_chat: base.String = None,
callback_game: CallbackGame = None,
pay: base.Boolean = None, **kwargs):
pay: base.Boolean = None,
web_app: WebAppInfo = None,
**kwargs):
super(InlineKeyboardButton, self).__init__(text=text,
url=url,
login_url=login_url,
@ -111,4 +115,6 @@ class InlineKeyboardButton(base.TelegramObject):
switch_inline_query=switch_inline_query,
switch_inline_query_current_chat=switch_inline_query_current_chat,
callback_game=callback_game,
pay=pay, **kwargs)
pay=pay,
web_app=web_app,
**kwargs)

View file

@ -0,0 +1,86 @@
import typing
from . import base
from . import fields
from .web_app_info import WebAppInfo
from ..utils import helper
from ..utils.helper import Item
class MenuButton(base.TelegramObject):
"""
This object describes the bot's menu button in a private chat. It should be one of
- MenuButtonCommands
- MenuButtonWebApp
- MenuButtonDefault
If a menu button other than MenuButtonDefault is set for a private chat,
then it is applied in the chat.
Otherwise the default menu button is applied.
By default, the menu button opens the list of bot commands.
"""
type: base.String = fields.Field(default='default')
@classmethod
def resolve(cls, **kwargs) -> typing.Union[
"MenuButtonCommands",
"MenuButtonDefault",
"MenuButtonWebApp",
]:
type_ = kwargs.get('type')
mapping = {
MenuButtonType.DEFAULT: MenuButtonDefault,
MenuButtonType.COMMANDS: MenuButtonCommands,
MenuButtonType.WEB_APP: MenuButtonWebApp,
}
class_ = mapping.get(type_)
if not class_:
raise ValueError(f'Unknown MenuButton type: {type_}')
return class_(**kwargs)
class MenuButtonCommands(MenuButton):
"""
Represents a menu button, which opens the bot's list of commands.
Source: https://core.telegram.org/bots/api#menubuttoncommands
"""
type: base.String = fields.Field(default='commands')
def __init__(self, **kwargs):
super().__init__(type='commands', **kwargs)
class MenuButtonWebApp(MenuButton):
"""
Represents a menu button, which launches a Web App.
Source: https://core.telegram.org/bots/api#menubuttonwebapp
"""
type: base.String = fields.Field(default='web_app')
text: base.String = fields.Field()
web_app: WebAppInfo = fields.Field(base=WebAppInfo)
def __init__(self, text: base.String, web_app: WebAppInfo, **kwargs):
super().__init__(type='web_app', text=text, web_app=web_app, **kwargs)
class MenuButtonDefault(MenuButton):
"""
Describes that no specific value for the menu button was set.
Source: https://core.telegram.org/bots/api#menubuttondefault
"""
type: base.String = fields.Field(default='default')
def __init__(self, **kwargs):
super().__init__(type='default', **kwargs)
class MenuButtonType(helper.Helper):
mode = helper.HelperMode.lowercase
DEFAULT = Item()
COMMANDS = Item()
WEB_APP = Item()

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,9 @@
import sys
from ..utils import helper, markdown
from ..utils.deprecated import deprecated
from . import base, fields
from .user import User
from ..utils import helper, markdown
from ..utils.deprecated import deprecated
class MessageEntity(base.TelegramObject):
@ -19,16 +19,18 @@ class MessageEntity(base.TelegramObject):
url: base.String = fields.Field()
user: User = fields.Field(base=User)
language: base.String = fields.Field()
custom_emoji_id: base.String = fields.Field()
def __init__(
self,
type: base.String,
offset: base.Integer,
length: base.Integer,
url: base.String = None,
user: User = None,
language: base.String = None,
**kwargs
self,
type: base.String,
offset: base.Integer,
length: base.Integer,
url: base.String = None,
user: User = None,
language: base.String = None,
custom_emoji_id: base.String = None,
**kwargs
):
super().__init__(
type=type,
@ -37,6 +39,7 @@ class MessageEntity(base.TelegramObject):
url=url,
user=user,
language=language,
custom_emoji_id=custom_emoji_id,
**kwargs
)
@ -94,6 +97,8 @@ class MessageEntity(base.TelegramObject):
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)
if self.type == MessageEntityType.CUSTOM_EMOJI and self.user:
return entity_text
return entity_text
@ -118,6 +123,7 @@ class MessageEntityType(helper.Helper):
:key: PRE
:key: TEXT_LINK
:key: TEXT_MENTION
:key: CUSTOM_EMOJI
"""
mode = helper.HelperMode.snake_case
@ -138,3 +144,4 @@ class MessageEntityType(helper.Helper):
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
CUSTOM_EMOJI = helper.Item() # custom_emoji

View file

@ -2,6 +2,7 @@ import typing
from . import base
from . import fields
from .web_app_info import WebAppInfo
class KeyboardButtonPollType(base.TelegramObject):
@ -117,16 +118,19 @@ class KeyboardButton(base.TelegramObject):
request_contact: base.Boolean = fields.Field()
request_location: base.Boolean = fields.Field()
request_poll: KeyboardButtonPollType = fields.Field()
web_app: WebAppInfo = fields.Field(base=WebAppInfo)
def __init__(self, text: base.String,
request_contact: base.Boolean = None,
request_location: base.Boolean = None,
request_poll: KeyboardButtonPollType = None,
web_app: WebAppInfo = None,
**kwargs):
super(KeyboardButton, self).__init__(text=text,
request_contact=request_contact,
request_location=request_location,
request_poll=request_poll,
web_app=web_app,
**kwargs)

View file

@ -0,0 +1,11 @@
from . import base
from . import fields
class SentWebAppMessage(base.TelegramObject):
"""
Contains information about an inline message sent by a Web App on behalf of a user.
Source: https://core.telegram.org/bots/api#sentwebappmessage
"""
inline_message_id: base.String = fields.Field()

View file

@ -3,6 +3,7 @@ from . import fields
from . import mixins
from .mask_position import MaskPosition
from .photo_size import PhotoSize
from .file import File
class Sticker(base.TelegramObject, mixins.Downloadable):
@ -13,6 +14,7 @@ class Sticker(base.TelegramObject, mixins.Downloadable):
"""
file_id: base.String = fields.Field()
file_unique_id: base.String = fields.Field()
type: base.String = fields.Field()
width: base.Integer = fields.Field()
height: base.Integer = fields.Field()
is_animated: base.Boolean = fields.Field()
@ -20,7 +22,9 @@ class Sticker(base.TelegramObject, mixins.Downloadable):
thumb: PhotoSize = fields.Field(base=PhotoSize)
emoji: base.String = fields.Field()
set_name: base.String = fields.Field()
premium_animation: File = fields.Field(base=File)
mask_position: MaskPosition = fields.Field(base=MaskPosition)
custom_emoji_id: base.String = fields.Field()
file_size: base.Integer = fields.Field()
async def set_position_in_set(self, position: base.Integer) -> base.Boolean:

View file

@ -14,8 +14,9 @@ class StickerSet(base.TelegramObject):
"""
name: base.String = fields.Field()
title: base.String = fields.Field()
sticker_type: base.String = fields.Field()
is_animated: base.Boolean = fields.Field()
is_video: base.Boolean = fields.Field()
contains_masks: base.Boolean = fields.Field()
contains_masks: base.Boolean = fields.Field() # Deprecated
stickers: typing.List[Sticker] = fields.ListField(base=Sticker)
thumb: PhotoSize = fields.Field(base=PhotoSize)

View file

@ -22,6 +22,8 @@ class User(base.TelegramObject):
last_name: base.String = fields.Field()
username: base.String = fields.Field()
language_code: base.String = fields.Field()
is_premium: base.Boolean = fields.Field()
added_to_attachment_menu: base.Boolean = fields.Field()
can_join_groups: base.Boolean = fields.Field()
can_read_all_group_messages: base.Boolean = fields.Field()
supports_inline_queries: base.Boolean = fields.Field()

View file

@ -0,0 +1,13 @@
from . import base
from . import fields
from . import mixins
class VideoChatEnded(base.TelegramObject, mixins.Downloadable):
"""
This object represents a service message about a video chat scheduled in the chat.
https://core.telegram.org/bots/api#videochatended
"""
duration: base.Integer = fields.Field()

View file

@ -0,0 +1,16 @@
import typing
from . import base
from . import fields
from . import mixins
from .user import User
class VideoChatParticipantsInvited(base.TelegramObject, mixins.Downloadable):
"""
This object represents a service message about new members invited to a video chat.
https://core.telegram.org/bots/api#videochatparticipantsinvited
"""
users: typing.List[User] = fields.ListField(base=User)

View file

@ -0,0 +1,14 @@
from datetime import datetime
from . import base
from . import fields
class VideoChatScheduled(base.TelegramObject):
"""
This object represents a service message about a video chat scheduled in the chat.
https://core.telegram.org/bots/api#videochatscheduled
"""
start_date: datetime = fields.DateTimeField()

View file

@ -0,0 +1,11 @@
from . import base
from . import mixins
class VideoChatStarted(base.TelegramObject, mixins.Downloadable):
"""
his object represents a service message about a video chat started in the chat. Currently holds no information.
https://core.telegram.org/bots/api#videochatstarted
"""
pass

View file

@ -0,0 +1,12 @@
from . import base
from . import fields
class WebAppData(base.TelegramObject):
"""
Contains data sent from a Web App to the bot.
Source: https://core.telegram.org/bots/api#webappdata
"""
data: str = fields.Field()
button_text: str = fields.Field()

View file

@ -0,0 +1,11 @@
from . import base
from . import fields
class WebAppInfo(base.TelegramObject):
"""
Contains information about a Web App.
Source: https://core.telegram.org/bots/api#webappinfo
"""
url: base.String = fields.Field()

View file

@ -18,3 +18,4 @@ class WebhookInfo(base.TelegramObject):
last_error_message: base.String = fields.Field()
max_connections: base.Integer = fields.Field()
allowed_updates: typing.List[base.String] = fields.ListField()
last_synchronization_error_date: base.Integer = fields.DateTimeField()

View file

@ -1,3 +1,5 @@
import warnings
try:
import emoji
except ImportError:
@ -5,8 +7,14 @@ except ImportError:
def emojize(text):
warnings.warn(message="'aiogram.utils.emoji' module deprecated, use emoji symbols directly instead,"
"this function will be removed in aiogram v2.22",
category=DeprecationWarning, stacklevel=2)
return emoji.emojize(text, use_aliases=True)
def demojize(text):
warnings.warn(message="'aiogram.utils.emoji' module deprecated, use emoji symbols directly instead,"
"this function will be removed in aiogram v2.22",
category=DeprecationWarning, stacklevel=2)
return emoji.demojize(text)

View file

@ -44,6 +44,8 @@ class TextDecoration(ABC):
return self.link(value=text, link=f"tg://user?id={user.id}")
if entity.type == "text_link":
return self.link(value=text, link=cast(str, entity.url))
if entity.type == "custom_emoji":
return self.custom_emoji(value=text, custom_emoji_id=entity.custom_emoji_id)
return self.quote(text)
@ -143,6 +145,10 @@ class TextDecoration(ABC):
def quote(self, value: str) -> str: # pragma: no cover
pass
@abstractmethod
def custom_emoji(self, value: str, custom_emoji_id: str) -> str: # pragma: no cover
pass
class HtmlDecoration(TextDecoration):
def link(self, value: str, link: str) -> str:
@ -175,6 +181,9 @@ class HtmlDecoration(TextDecoration):
def quote(self, value: str) -> str:
return html.escape(value, quote=False)
def custom_emoji(self, value: str, custom_emoji_id: str) -> str:
return f'<tg-emoji emoji-id="{custom_emoji_id}">{value}</tg-emoji>'
class MarkdownDecoration(TextDecoration):
MARKDOWN_QUOTE_PATTERN: Pattern[str] = re.compile(r"([_*\[\]()~`>#+\-=|{}.!\\])")
@ -209,6 +218,9 @@ class MarkdownDecoration(TextDecoration):
def quote(self, value: str) -> str:
return re.sub(pattern=self.MARKDOWN_QUOTE_PATTERN, repl=r"\\\1", string=value)
def custom_emoji(self, value: str, custom_emoji_id: str) -> str:
return self.link(value=value, link=f"tg://emoji?id={custom_emoji_id}")
html_decoration = HtmlDecoration()
markdown_decoration = MarkdownDecoration()

67
aiogram/utils/web_app.py Normal file
View file

@ -0,0 +1,67 @@
import hashlib
import hmac
from operator import itemgetter
from typing import Callable, Any, Dict
from urllib.parse import parse_qsl
def check_webapp_signature(token: str, init_data: str) -> bool:
"""
Check incoming WebApp init data signature
Source: https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app
:param token:
:param init_data:
:return:
"""
try:
parsed_data = dict(parse_qsl(init_data))
except ValueError:
# Init data is not a valid query string
return False
if "hash" not in parsed_data:
# Hash is not present in init data
return False
hash_ = parsed_data.pop('hash')
data_check_string = "\n".join(
f"{k}={v}" for k, v in sorted(parsed_data.items(), key=itemgetter(0))
)
secret_key = hmac.new(
key=b"WebAppData", msg=token.encode(), digestmod=hashlib.sha256
)
calculated_hash = hmac.new(
key=secret_key.digest(), msg=data_check_string.encode(), digestmod=hashlib.sha256
).hexdigest()
return calculated_hash == hash_
def parse_init_data(init_data: str, _loads: Callable[..., Any]) -> Dict[str, Any]:
"""
Parse WebApp init data and return it as dict
:param init_data:
:param _loads:
:return:
"""
result = {}
for key, value in parse_qsl(init_data):
if (value.startswith('[') and value.endswith(']')) or (value.startswith('{') and value.endswith('}')):
value = _loads(value)
result[key] = value
return result
def safe_parse_webapp_init_data(token: str, init_data: str, _loads: Callable[..., Any]) -> Dict[str, Any]:
"""
Validate WebApp init data and return it as dict
:param token:
:param init_data:
:param _loads:
:return:
"""
if check_webapp_signature(token, init_data):
return parse_init_data(init_data, _loads)
raise ValueError("Invalid init data signature")

View file

@ -22,7 +22,7 @@ Welcome to aiogram's documentation!
:target: https://pypi.python.org/pypi/aiogram
:alt: Supported python versions
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.7-blue.svg?style=flat-square&logo=telegram
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.2-blue.svg?style=flat-square&logo=telegram
:target: https://core.telegram.org/bots/api
:alt: Telegram Bot API

View file

@ -1,4 +1,4 @@
from typing import Set
from typing import Set, Union, Iterable
from datetime import datetime
import pytest
@ -6,9 +6,9 @@ import pytest
from aiogram.dispatcher.filters.builtin import (
Text,
extract_chat_ids,
ChatIDArgumentType, ForwardedMessageFilter, IDFilter,
ChatIDArgumentType, ForwardedMessageFilter, IDFilter, Command,
)
from aiogram.types import Message
from aiogram.types import Message, BotCommand
from tests.types.dataset import MESSAGE, MESSAGE_FROM_CHANNEL
@ -108,3 +108,42 @@ class TestIDFilter:
filter = IDFilter(chat_id=message_from_channel.chat.id)
assert await filter.check(message_from_channel)
@pytest.mark.parametrize("command", [
"/start",
"/start some args",
])
@pytest.mark.parametrize("cmd_filter", [
"start",
("start",),
BotCommand(command="start", description="my desc"),
(BotCommand(command="start", description="bar"),),
(BotCommand(command="start", description="foo"), "help"),
])
@pytest.mark.asyncio
async def test_commands_filter(command: str, cmd_filter: Union[Iterable[Union[str, BotCommand]], str, BotCommand]):
message_with_command = Message(**MESSAGE)
message_with_command.text = command
start_filter = Command(commands=cmd_filter)
assert await start_filter.check(message_with_command)
@pytest.mark.asyncio
async def test_commands_filter_not_checked():
message_with_command = Message(**MESSAGE)
message_with_command.text = "/start"
start_filter = Command(commands=["help", BotCommand("about", "my desc")])
assert not await start_filter.check(message_with_command)
def test_commands_filter_raises_error():
with pytest.raises(ValueError):
start_filter = Command(commands=42) # noqa
with pytest.raises(ValueError):
start_filter = Command(commands=[42]) # noqa