Compare commits

...

9 Commits

Author SHA1 Message Date
Hinrich Mahler df07148e2d Bump version to v20.0a2 2022-06-27 19:19:54 +02:00
Bibo-Joshi 2d2cede442 Documentation Improvements (#3103, #3121, #3098)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
Co-authored-by: David <dsb321mp@gmail.com>
Co-authored-by: Harshil Mehta <37377066+harshil21@users.noreply.github.com>
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Alex <53974096+ExalFabu@users.noreply.github.com>
2022-06-27 18:58:51 +02:00
Poolitzer 08e223ba90 API 6.1 (#3112)
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2022-06-27 18:54:11 +02:00
Bibo-Joshi 01d643913e Stabilize CI (#3119) 2022-06-27 18:46:52 +02:00
Aditya Yadav 755945172d Add Additional Shortcut Methods to Chat (#3115) 2022-06-27 18:45:30 +02:00
dependabot[bot] 24b4de9f10 Bump pyupgrade from 2.32.1 to 2.34.0 (#3096)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2022-06-25 11:52:39 +02:00
David 76bfe8ceff Mermaid-based Example State Diagrams (#3090)
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2022-06-24 18:15:10 +02:00
dependabot[bot] b498786d7c Bump furo from 2022.6.4 to 2022.6.4.1 (#3095)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-19 17:12:54 +02:00
dependabot[bot] bfe30048e8 Bump mypy from 0.960 to 0.961 (#3093)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2022-06-19 17:12:26 +02:00
45 changed files with 1182 additions and 108 deletions
+2 -2
View File
@@ -39,7 +39,7 @@ repos:
- cachetools~=5.2.0
- . # this basically does `pip install -e .`
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.960
rev: v0.961
hooks:
- id: mypy
name: mypy-ptb
@@ -66,7 +66,7 @@ repos:
- cachetools~=5.2.0
- . # this basically does `pip install -e .`
- repo: https://github.com/asottile/pyupgrade
rev: v2.32.1
rev: v2.34.0
hooks:
- id: pyupgrade
files: ^(telegram|examples|tests)/.*\.py$
+2
View File
@@ -29,6 +29,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Balduro <https://github.com/Balduro>`_
- `Bibo-Joshi <https://github.com/Bibo-Joshi>`_
- `bimmlerd <https://github.com/bimmlerd>`_
- `cyc8 <https://github.com/cyc8>`_
- `d-qoi <https://github.com/d-qoi>`_
- `daimajia <https://github.com/daimajia>`_
- `Daniel Reed <https://github.com/nmlorg>`_
@@ -45,6 +46,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Evan Haberecht <https://github.com/habereet>`_
- `Evgeny Denisov <https://github.com/eIGato>`_
- `evgfilim1 <https://github.com/evgfilim1>`_
- `ExalFabu <https://github.com/ExalFabu>`_
- `franciscod <https://github.com/franciscod>`_
- `gamgi <https://github.com/gamgi>`_
- `Gauthamram Ravichandran <https://github.com/GauthamramRavichandran>`_
+37
View File
@@ -2,6 +2,43 @@
Changelog
=========
Version 20.0a2
==============
*Released 2022-06-27*
This is the technical changelog for version 20.0a2. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
Major Changes
-------------
- Full Support for API 6.1 (`#3112`_)
New Features
------------
- Add Additional Shortcut Methods to ``Chat`` (`#3115`_)
- Mermaid-based Example State Diagrams (`#3090`_)
Minor Changes, Documentation Improvements and CI
------------------------------------------------
- Documentation Improvements (`#3103`_, `#3121`_, `#3098`_)
- Stabilize CI (`#3119`_)
- Bump ``pyupgrade`` from 2.32.1 to 2.34.0 (`#3096`_)
- Bump ``furo`` from 2022.6.4 to 2022.6.4.1 (`#3095`_)
- Bump ``mypy`` from 0.960 to 0.961 (`#3093`_)
.. _`#3112`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3112
.. _`#3115`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3115
.. _`#3090`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3090
.. _`#3103`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3103
.. _`#3121`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3121
.. _`#3098`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3098
.. _`#3119`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3119
.. _`#3096`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3096
.. _`#3095`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3095
.. _`#3093`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3093
Version 20.0a1
==============
*Released 2022-06-09*
+4 -4
View File
@@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.0-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -34,7 +34,7 @@
:target: https://github.com/python-telegram-bot/python-telegram-bot/
:alt: Github Actions workflow
.. image:: https://app.codecov.io/gh/python-telegram-bot/python-telegram-bot/branch/master/graph/badge.svg
.. image:: https://codecov.io/gh/python-telegram-bot/python-telegram-bot/branch/master/graph/badge.svg
:target: https://app.codecov.io/gh/python-telegram-bot/python-telegram-bot
:alt: Code coverage
@@ -93,7 +93,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
All types and methods of the Telegram Bot API **6.0** are supported.
All types and methods of the Telegram Bot API **6.1** are supported.
Installing
==========
@@ -149,7 +149,7 @@ Resources
=========
- The `package documentation <https://docs.python-telegram-bot.org/>`_ is the technical reference for ``python-telegram-bot``.
It contains descriptions of all available classes, modules, methods and arguments.
It contains descriptions of all available classes, modules, methods and arguments as well as the `changelog <https://docs.python-telegram-bot.org/changelog.html>`_.
- The `wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ is home to number of more elaborate introductions of the different features of ``python-telegram-bot`` and other useful resources that go beyond the technical documentation.
- Our `examples section <https://docs.python-telegram-bot.org/examples.html>`_ contains several examples that showcase the different features of both the Bot API and ``python-telegram-bot``.
Even if it is not your approach for learning, please take a look at ``echobot.py``. It is the de facto base for most of the bots out there.
+4 -4
View File
@@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.0-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -34,7 +34,7 @@
:target: https://github.com/python-telegram-bot/python-telegram-bot/
:alt: Github Actions workflow
.. image:: https://app.codecov.io/gh/python-telegram-bot/python-telegram-bot/branch/master/graph/badge.svg
.. image:: https://app.codecov.io/gh/python-telegram-bot/python-telegram-bot
:target: https://app.codecov.io/gh/python-telegram-bot/python-telegram-bot
:alt: Code coverage
@@ -89,7 +89,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
All types and methods of the Telegram Bot API **6.0** are supported.
All types and methods of the Telegram Bot API **6.1** are supported.
Installing
==========
@@ -146,7 +146,7 @@ Resources
=========
- The `package documentation <https://docs.python-telegram-bot.org/>`_ is the technical reference for ``python-telegram-bot``.
It contains descriptions of all available classes, modules, methods and arguments.
It contains descriptions of all available classes, modules, methods and arguments as well as the `changelog <https://docs.python-telegram-bot.org/changelog.html>`_.
- The `wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ is home to number of more elaborate introductions of the different features of ``python-telegram-bot`` and other useful resources that go beyond the technical documentation.
- Our `examples section <https://docs.python-telegram-bot.org/examples.html>`_ contains several examples that showcase the different features of both the Bot API and ``python-telegram-bot``.
Even if it is not your approach for learning, please take a look at ``echobot.py``. It is the de facto base for most of the bots out there.
+4 -3
View File
@@ -1,4 +1,5 @@
sphinx==5.0.1
sphinx==5.0.2
sphinx-pypi-upload
furo==2022.6.4
sphinx-paramlinks==0.5.4
furo==2022.6.21
sphinx-paramlinks==0.5.4
sphinxcontrib-mermaid==0.7.1
@@ -0,0 +1,3 @@
.mermaid svg {
height: auto;
}
+2
View File
@@ -263,6 +263,8 @@
:align: left
:widths: 1 4
* - :meth:`~telegram.Bot.create_invoice_link`
- Used to generate an HTTP link for an invoice
* - :meth:`~telegram.Bot.close`
- Used for closing server instance when switching to another local server
* - :meth:`~telegram.Bot.log_out`
+7 -3
View File
@@ -29,9 +29,9 @@ author = "Leandro Toledo"
# built documents.
#
# The short X.Y version.
version = "20.0a1" # telegram.__version__[:3]
version = "20.0a2" # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = "20.0a1" # telegram.__version__
release = "20.0a2" # telegram.__version__
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = "4.5.0"
@@ -45,6 +45,7 @@ extensions = [
"sphinx.ext.intersphinx",
"sphinx.ext.linkcode",
"sphinx_paramlinks",
"sphinxcontrib.mermaid",
]
# Use intersphinx to reference the python builtin library docs
@@ -211,7 +212,10 @@ html_favicon = "ptb-logo_1024.ico"
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_css_files = ["style_external_link.css"]
html_css_files = [
"style_external_link.css",
"style_mermaid_diagrams.css",
]
html_permalinks_icon = "" # Furo's default permalink icon is `#`` which doesn't look great imo.
# Output file base name for HTML help builder.
+1 -2
View File
@@ -10,5 +10,4 @@
State Diagram
-------------
.. image:: ../../examples/conversationbot.png
.. mermaid:: ../../examples/conversationbot.mmd
+1 -2
View File
@@ -10,5 +10,4 @@
State Diagram
-------------
.. image:: ../../examples/conversationbot2.png
.. mermaid:: ../../examples/conversationbot2.mmd
@@ -10,5 +10,4 @@
State Diagram
-------------
.. image:: ../../examples/nestedconversationbot.png
.. mermaid:: ../../examples/nestedconversationbot.mmd
+21
View File
@@ -0,0 +1,21 @@
flowchart TB
%% Documentation: https://mermaid-js.github.io/mermaid/#/flowchart
A(("/start")):::entryPoint -->|Hi! My name is Professor Bot...| B((GENDER)):::state
B --> |"- Boy <br /> - Girl <br /> - Other"|C("(choice)"):::userInput
C --> |I see! Please send me a photo...| D((PHOTO)):::state
D --> E("/skip"):::userInput
D --> F("(photo)"):::userInput
E --> |I bet you look great!| G[\ /]:::userInput
F --> |Gorgeous!| G[\ /]
G --> |"Now, send me your location .."| H((LOCATION)):::state
H --> I("/skip"):::userInput
H --> J("(location)"):::userInput
I --> |You seem a bit paranoid!| K[\" "/]:::userInput
J --> |Maybe I can visit...| K
K --> |"Tell me about yourself..."| L(("BIO")):::state
L --> M("(text)"):::userInput
M --> |"Thanks and bye!"| End(("END")):::termination
classDef userInput fill:#2a5279, color:#ffffff, stroke:#ffffff
classDef state fill:#222222, color:#ffffff, stroke:#ffffff
classDef entryPoint fill:#009c11, stroke:#42FF57, color:#ffffff
classDef termination fill:#bb0007, stroke:#E60109, color:#ffffff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

+17
View File
@@ -0,0 +1,17 @@
flowchart TB
%% Documentation: https://mermaid-js.github.io/mermaid/#/flowchart
A(("/start")):::entryPoint -->|Hi! My name is Doctor Botter...| B((CHOOSING)):::state
B --> C("Something else..."):::userInput
C --> |What category?| D((TYPING_CHOICE)):::state
D --> E("(text)"):::userInput
E --> |"[save choice] <br /> I'd love to hear about that!"| F((TYPING_REPLY)):::state
F --> G("(text)"):::userInput
G --> |"[save choice: text] <br /> Neat! <br /> (List of facts) <br /> More?"| B
B --> H("- Age <br /> - Favourite colour <br /> - Number of siblings"):::userInput
H --> |"[save choice] <br /> I'd love to hear about that!"| F
B --> I("Done"):::userInput
I --> |"I learned these facts about you: <br /> ..."| End(("END")):::termination
classDef userInput fill:#2a5279, color:#ffffff, stroke:#ffffff
classDef state fill:#222222, color:#ffffff, stroke:#ffffff
classDef entryPoint fill:#009c11, stroke:#42FF57, color:#ffffff
classDef termination fill:#bb0007, stroke:#E60109, color:#ffffff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

+43
View File
@@ -0,0 +1,43 @@
flowchart TB
%% Documentation: https://mermaid-js.github.io/mermaid/#/flowchart
A(("/start")):::entryPoint -->|Hi! I'm FamilyBot...| B((SELECTING_ACTION)):::state
B --> C("Show Data"):::userInput
C --> |"(List of gathered data)"| D((SHOWING)):::state
D --> E("Back"):::userInput
E --> B
B --> F("Add Yourself"):::userInput
F --> G(("DESCRIBING_SELF")):::state
G --> H("Add info"):::userInput
H --> I((SELECT_FEATURE)):::state
I --> |"Please select a feature to update. <br /> - Name <br /> - Age <br /> - Done"|J("(choice)"):::userInput
J --> |"Okay, tell me."| K((TYPING)):::state
K --> L("(text)"):::userInput
L --> |"[saving]"|I
I --> M("Done"):::userInput
M --> B
B --> N("Add family member"):::userInput
R --> I
W --> |"See you around!"|End(("END")):::termination
Y(("ANY STATE")):::state --> Z("/stop"):::userInput
Z -->|"Okay, bye."| End
B --> W("Done"):::userInput
subgraph nestedConversation[Nested Conversation: Add Family Member]
direction BT
N --> O(("SELECT_LEVEL")):::state
O --> |"Add... <br /> - Add Parent <br /> - Add Child <br />"|P("(choice)"):::userInput
P --> Q(("SELECT_GENDER")):::state
Q --> |"- Mother <br /> - Father <br /> / <br /> - Sister <br /> - Brother"| R("(choice)"):::userInput
Q --> V("Show Data"):::userInput
Q --> T(("SELECTING_ACTION")):::state
Q --> U("Back"):::userInput
U --> T
O --> U
O --> V
V --> S(("SHOWING")):::state
V --> T
end
classDef userInput fill:#2a5279, color:#ffffff, stroke:#ffffff
classDef state fill:#222222, color:#ffffff, stroke:#ffffff
classDef entryPoint fill:#009c11, stroke:#42FF57, color:#ffffff
classDef termination fill:#bb0007, stroke:#E60109, color:#ffffff
style nestedConversation fill:#999999, stroke-width:2px, stroke:#333333
Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 KiB

+2 -2
View File
@@ -6,8 +6,8 @@ pre-commit
black==22.3.0
flake8==4.0.1
pylint==2.13.9
mypy==0.960
pyupgrade==2.32.1
mypy==0.961
pyupgrade==2.34.0
isort==5.10.1
pytest==7.1.2
+226 -32
View File
@@ -1612,7 +1612,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
changed in the future.
Note:
``thumb`` will be ignored for small files, for which Telegram can easily
:paramref:`thumb` will be ignored for small files, for which Telegram can easily
generate thumb nails. However, this behaviour is undocumented and might be changed
by Telegram.
@@ -3025,7 +3025,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
seconds from the current time they are considered to be banned forever. Applied
for supergroups and channels only.
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
bot will be used.
bot will be used, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is
used.
revoke_messages (:obj:`bool`, optional): Pass :obj:`True` to delete all messages from
the chat for the user that is being removed. If :obj:`False`, the user will be able
to see messages in the group that were sent before the user was removed.
@@ -3373,6 +3374,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
Use this method to edit text and game messages.
Note:
It is currently only possible to edit messages without
:attr:`telegram.Message.reply_markup` or with inline keyboards.
Args:
chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not
specified. Unique identifier for the target chat or username of the target channel
@@ -3465,6 +3470,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
Use this method to edit captions of messages.
Note:
It is currently only possible to edit messages without
:attr:`telegram.Message.reply_markup` or with inline keyboards
Args:
chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not
specified. Unique identifier for the target chat or username of the target channel
@@ -3553,7 +3562,11 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
is part of a message album, then it can be edited only to an audio for audio albums, only
to a document for document albums and to a photo or a video otherwise. When an inline
message is edited, a new file can't be uploaded; use a previously uploaded file via its
``file_id`` or specify a URL.
:attr:`~telegram.File.file_id` or specify a URL.
Note:
It is currently only possible to edit messages without
:attr:`telegram.Message.reply_markup` or with inline keyboards
Args:
media (:class:`telegram.InputMedia`): An object for a new media content
@@ -3629,6 +3642,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
Use this method to edit only the reply markup of messages sent by the bot or via the bot
(for inline bots).
Note:
It is currently only possible to edit messages without
:attr:`telegram.Message.reply_markup` or with inline keyboards
Args:
chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not
specified. Unique identifier for the target chat or username of the target channel
@@ -3795,6 +3812,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
allowed_updates: List[str] = None,
ip_address: str = None,
drop_pending_updates: bool = None,
secret_token: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3808,9 +3826,9 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
specified url, containing An Update. In case of an unsuccessful request,
Telegram will give up after a reasonable amount of attempts.
If you'd like to make sure that the Webhook request comes from Telegram, Telegram
recommends using a secret path in the URL, e.g. https://www.example.com/<token>. Since
nobody else knows your bot's token, you can be pretty sure it's them.
If you'd like to make sure that the Webhook was set by you, you can specify secret data in
the parameter :paramref:`secret_token`. If specified, the request will contain a header
``X-Telegram-Bot-Api-Secret-Token`` with the secret token as content.
Note:
The certificate argument should be a file from disk ``open(filename, 'rb')``.
@@ -3839,6 +3857,14 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
a short period of time.
drop_pending_updates (:obj:`bool`, optional): Pass :obj:`True` to drop all pending
updates.
secret_token (:obj:`str`, optional): A secret token to be sent in a header
``X-Telegram-Bot-Api-Secret-Token`` in every webhook request,
:tg-const:`telegram.constants.WebhookLimit.MIN_SECRET_TOKEN_LENGTH`-
:tg-const:`telegram.constants.WebhookLimit.MAX_SECRET_TOKEN_LENGTH` 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.
.. versionadded:: 20.0
Keyword Args:
read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
@@ -3889,6 +3915,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
data["ip_address"] = ip_address
if drop_pending_updates:
data["drop_pending_updates"] = drop_pending_updates
if secret_token is not None:
data["secret_token"] = secret_token
result = await self._post(
"setWebhook",
@@ -4593,29 +4621,35 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format ``@channelusername``).
title (:obj:`str`): Product name, 1-32 characters.
description (:obj:`str`): Product description, 1-255 characters.
payload (:obj:`str`): Bot-defined invoice payload, 1-128 bytes. This will not be
title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`-
:tg-const:`telegram.Invoice.MAX_TITLE_LENGTH` characters.
description (:obj:`str`): Product description.
:tg-const:`telegram.Invoice.MIN_DESCRIPTION_LENGTH`-
:tg-const:`telegram.Invoice.MAX_DESCRIPTION_LENGTH` characters.
payload (:obj:`str`): Bot-defined invoice payload.
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be
displayed to the user, use for your internal processes.
provider_token (:obj:`str`): Payments provider token, obtained via
`@BotFather <https://t.me/BotFather>`_.
currency (:obj:`str`): Three-letter ISO 4217 currency code.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see `more on currencies
<https://core.telegram.org/bots/payments#supported-currencies>`_.
prices (List[:class:`telegram.LabeledPrice`)]: Price breakdown, a list
of components (e.g. product price, tax, discount, delivery cost, delivery tax,
bonus, etc.).
max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the
smallest units of the currency (integer, not float/double). For example, for a
maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the exp parameter in
*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 <https://core.telegram.org/bots/payments/currencies.json>`_, it
shows the number of digits past the decimal point for each currency (2 for the
majority of currencies). Defaults to ``0``.
.. versionadded:: 13.5
suggested_tip_amounts (List[:obj:`int`], optional): An array of
suggested amounts of tips in the smallest units of the currency (integer, not
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``.
:paramref:`max_tip_amount`.
.. versionadded:: 13.5
start_parameter (:obj:`str`, optional): Unique deep-linking parameter. If left empty,
@@ -4759,10 +4793,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
api_kwargs: JSONDict = None,
) -> bool:
"""
If you sent an invoice requesting a shipping address and the parameter ``is_flexible`` was
specified, the Bot API will send an :class:`telegram.Update` with a
:attr:`telegram.Update.shipping_query` field to the bot. Use this method to reply to
shipping queries.
If you sent an invoice requesting a shipping address and the parameter
:paramref:`send_invoice.is_flexible` was specified, the Bot API will send an
:class:`telegram.Update` with a :attr:`telegram.Update.shipping_query` field to the bot.
Use this method to reply to shipping queries.
Args:
shipping_query_id (:obj:`str`): Unique identifier for the query to be answered.
@@ -4982,7 +5016,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
days or less than 30 seconds from the current time, they are considered to be
restricted forever.
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
bot will be used.
bot will be used, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is
used.
permissions (:class:`telegram.ChatPermissions`): An object for new user
permissions.
@@ -5366,7 +5401,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
expire_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the link will
expire. Integer input will be interpreted as Unix timestamp.
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
bot will be used.
bot will be used, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is
used.
member_limit (:obj:`int`, optional): Maximum number of users that can be members of
the chat simultaneously after joining the chat via this invite link;
1-:tg-const:`telegram.constants.ChatInviteLinkLimit.MEMBER_LIMIT`.
@@ -5376,7 +5412,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
.. versionadded:: 13.8
creates_join_request (:obj:`bool`, optional): :obj:`True`, if users joining the chat
via the link need to be approved by chat administrators.
If :obj:`True`, ``member_limit`` can't be specified.
If :obj:`True`, :paramref:`member_limit` can't be specified.
.. versionadded:: 13.8
@@ -5467,7 +5503,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
expire_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the link will
expire.
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
bot will be used.
bot will be used, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is
used.
member_limit (:obj:`int`, optional): Maximum number of users that can be members of
the chat simultaneously after joining the chat via this invite link;
1-:tg-const:`telegram.constants.ChatInviteLinkLimit.MEMBER_LIMIT`.
@@ -5477,7 +5514,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
.. versionadded:: 13.8
creates_join_request (:obj:`bool`, optional): :obj:`True`, if users joining the chat
via the link need to be approved by chat administrators.
If :obj:`True`, ``member_limit`` can't be specified.
If :obj:`True`, :paramref:`member_limit` can't be specified.
.. versionadded:: 13.8
@@ -5964,8 +6001,9 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
Use this method to add a message to the list of pinned messages in a chat. If the
chat is not a private chat, the bot must be an administrator in the chat for this to work
and must have the ``can_pin_messages`` admin right in a supergroup
or :attr:`telegram.ChatMemberAdministrator.can_edit_messages` admin right in a channel.
and must have the :paramref:`~telegram.ChatAdministratorRights.can_pin_messages` admin
right in a supergroup or :attr:`~telegram.ChatMemberAdministrator.can_edit_messages` admin
right in a channel.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
@@ -6029,9 +6067,9 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
Use this method to remove a message from the list of pinned messages in a chat. If the
chat is not a private chat, the bot must be an administrator in the chat for this to work
and must have the ``can_pin_messages`` admin right in a
supergroup or :attr:`telegram.ChatMemberAdministrator.can_edit_messages` admin right in a
channel.
and must have the :paramref:`~telegram.ChatAdministratorRights.can_pin_messages` admin
right in a supergroup or :attr:`~telegram.ChatMemberAdministrator.can_edit_messages` admin
right in a channel.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
@@ -6091,9 +6129,9 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
Use this method to clear the list of pinned messages in a chat. If the
chat is not a private chat, the bot must be an administrator in the chat for this
to work and must have the ``can_pin_messages`` admin right in a
supergroup or :attr:`telegram.ChatMemberAdministrator.can_edit_messages` admin right in a
channel.
to work and must have the :paramref:`~telegram.ChatAdministratorRights.can_pin_messages`
admin right in a supergroup or :attr:`~telegram.ChatMemberAdministrator.can_edit_messages`
admin right in a channel.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
@@ -6787,7 +6825,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
more than 600 seconds in the future. Can't be used together with
:paramref:`open_period`.
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
bot will be used.
bot will be used, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is
used.
is_closed (:obj:`bool`, optional): Pass :obj:`True`, if the poll needs to be
immediately closed. This can be useful for poll preview.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
@@ -7693,6 +7732,159 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
)
return MenuButton.de_json(result, bot=self) # type: ignore[return-value, arg-type]
@_log
async def create_invoice_link(
self,
title: str,
description: str,
payload: str,
provider_token: str,
currency: str,
prices: List["LabeledPrice"],
max_tip_amount: int = None,
suggested_tip_amounts: List[int] = None,
provider_data: Union[str, object] = None,
photo_url: str = None,
photo_size: int = None,
photo_width: int = None,
photo_height: int = None,
need_name: bool = None,
need_phone_number: bool = None,
need_email: bool = None,
need_shipping_address: bool = None,
send_phone_number_to_provider: bool = None,
send_email_to_provider: bool = None,
is_flexible: bool = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> str:
"""Use this method to create a link for an invoice.
.. versionadded:: 20.0
Args:
title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`-
:tg-const:`telegram.Invoice.MAX_TITLE_LENGTH` characters.
description (:obj:`str`): Product description.
:tg-const:`telegram.Invoice.MIN_DESCRIPTION_LENGTH`-
:tg-const:`telegram.Invoice.MAX_DESCRIPTION_LENGTH` characters.
payload (:obj:`str`): Bot-defined invoice payload.
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be
displayed to the user, use for your internal processes.
provider_token (:obj:`str`): Payments provider token, obtained via
`@BotFather <https://t.me/BotFather>`_.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see `more on currencies
<https://core.telegram.org/bots/payments#supported-currencies>`_.
prices (List[:class:`telegram.LabeledPrice`)]: Price breakdown, a list
of components (e.g. product price, tax, discount, delivery cost, delivery tax,
bonus, etc.).
max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the
*smallest* units of the currency (integer, **not** float/double). For example, for
a maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the exp parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, it
shows the number of digits past the decimal point for each currency (2 for the
majority of currencies). Defaults to ``0``.
suggested_tip_amounts (List[:obj:`int`], optional): An 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
:paramref:`max_tip_amount`.
provider_data (:obj:`str` | :obj:`object`, optional): 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. When an object is
passed, it will be encoded as JSON.
photo_url (:obj:`str`, optional): URL of the product photo for the invoice. Can be a
photo of the goods or a marketing image for a service.
photo_size (:obj:`int`, optional): Photo size in bytes.
photo_width (:obj:`int`, optional): Photo width.
photo_height (:obj:`int`, optional): Photo height.
need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full
name to complete the order.
need_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's
phone number to complete the order.
need_email (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's email
address to complete the order.
need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the
user's shipping address to complete the order.
send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's
phone number should be sent to provider.
send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email
address should be sent to provider.
is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on
the shipping method.
Keyword Args:
read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
write_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.request.BaseRequest.post.write_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
connect_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.request.BaseRequest.post.connect_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
pool_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.request.BaseRequest.post.pool_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:class:`str`: On success, the created invoice link is returned.
"""
data: JSONDict = {
"title": title,
"description": description,
"payload": payload,
"provider_token": provider_token,
"currency": currency,
"prices": prices,
}
if max_tip_amount is not None:
data["max_tip_amount"] = max_tip_amount
if suggested_tip_amounts is not None:
data["suggested_tip_amounts"] = suggested_tip_amounts
if provider_data is not None:
data["provider_data"] = provider_data
if photo_url is not None:
data["photo_url"] = photo_url
if photo_size is not None:
data["photo_size"] = photo_size
if photo_width is not None:
data["photo_width"] = photo_width
if photo_height is not None:
data["photo_height"] = photo_height
if need_name is not None:
data["need_name"] = need_name
if need_phone_number is not None:
data["need_phone_number"] = need_phone_number
if need_email is not None:
data["need_email"] = need_email
if need_shipping_address is not None:
data["need_shipping_address"] = need_shipping_address
if is_flexible is not None:
data["is_flexible"] = is_flexible
if send_phone_number_to_provider is not None:
data["send_phone_number_to_provider"] = send_phone_number_to_provider
if send_email_to_provider is not None:
data["send_email_to_provider"] = send_email_to_provider
return await self._post( # type: ignore[return-value]
"createInvoiceLink",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
@@ -7881,3 +8073,5 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""Alias for :meth:`get_my_default_administrator_rights`"""
setMyDefaultAdministratorRights = set_my_default_administrator_rights
"""Alias for :meth:`set_my_default_administrator_rights`"""
createInvoiceLink = create_invoice_link
"""Alias for :meth:`create_invoice_link`"""
+244
View File
@@ -124,6 +124,16 @@ class Chat(TelegramObject):
chats. Returned only in :meth:`telegram.Bot.get_chat`.
location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
join_to_send_messages (:obj:`bool`, optional): :obj:`True`, if users need to join the
supergroup before they can send messages. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
join_by_request (:obj:`bool`, optional): :obj:`True`, if all users directly joining the
supergroup need to be approved by supergroup administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
@@ -168,6 +178,16 @@ class Chat(TelegramObject):
chats. Returned only in :meth:`telegram.Bot.get_chat`.
location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
join_to_send_messages (:obj:`bool`): Optional. :obj:`True`, if users need to join
the supergroup before they can send messages. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
join_by_request (:obj:`bool`): Optional. :obj:`True`, if all users directly
joining the supergroup need to be approved by supergroup administrators. Returned only
in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
"""
@@ -193,6 +213,8 @@ class Chat(TelegramObject):
"message_auto_delete_time",
"has_protected_content",
"has_private_forwards",
"join_to_send_messages",
"join_by_request",
)
SENDER: ClassVar[str] = constants.ChatType.SENDER
@@ -232,6 +254,8 @@ class Chat(TelegramObject):
message_auto_delete_time: int = None,
has_private_forwards: bool = None,
has_protected_content: bool = None,
join_to_send_messages: bool = None,
join_by_request: bool = None,
**_kwargs: Any,
):
# Required
@@ -260,6 +284,8 @@ class Chat(TelegramObject):
self.can_set_sticker_set = can_set_sticker_set
self.linked_chat_id = linked_chat_id
self.location = location
self.join_to_send_messages = join_to_send_messages
self.join_by_request = join_by_request
self.set_bot(bot)
self._id_attrs = (self.id,)
@@ -790,6 +816,144 @@ class Chat(TelegramObject):
api_kwargs=api_kwargs,
)
async def set_photo(
self,
photo: FileInput,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Shortcut for::
await bot.set_chat_photo(
chat_id=update.effective_chat.id, *args, **kwargs
)
For the documentation of the arguments, please see
:meth:`telegram.Bot.set_chat_photo`.
.. versionadded:: 20.0
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().set_chat_photo(
chat_id=self.id,
photo=photo,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def delete_photo(
self,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Shortcut for::
await bot.delete_chat_photo(
chat_id=update.effective_chat.id, *args, **kwargs
)
For the documentation of the arguments, please see
:meth:`telegram.Bot.delete_chat_photo`.
.. versionadded:: 20.0
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().delete_chat_photo(
chat_id=self.id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def set_title(
self,
title: str,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Shortcut for::
await bot.set_chat_title(
chat_id=update.effective_chat.id, *args, **kwargs
)
For the documentation of the arguments, please see
:meth:`telegram.Bot.set_chat_title`.
.. versionadded:: 20.0
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().set_chat_title(
chat_id=self.id,
title=title,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def set_description(
self,
description: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Shortcut for::
await bot.set_chat_description(
chat_id=update.effective_chat.id, *args, **kwargs
)
For the documentation of the arguments, please see
:meth:`telegram.Bot.set_chat_description`.
.. versionadded:: 20.0
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().set_chat_description(
chat_id=self.id,
description=description,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def pin_message(
self,
message_id: int,
@@ -1894,6 +2058,86 @@ class Chat(TelegramObject):
protect_content=protect_content,
)
async def forward_from(
self,
from_chat_id: Union[str, int],
message_id: int,
disable_notification: DVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> "Message":
"""Shortcut for::
await bot.forward_message(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see :meth:`telegram.Bot.forward_message`.
.. seealso:: :meth:`forward_to`
.. versionadded:: 20.0
Returns:
:class:`telegram.Message`: On success, instance representing the message posted.
"""
return await self.get_bot().forward_message(
chat_id=self.id,
from_chat_id=from_chat_id,
message_id=message_id,
disable_notification=disable_notification,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
protect_content=protect_content,
)
async def forward_to(
self,
chat_id: Union[int, str],
message_id: int,
disable_notification: DVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> "Message":
"""Shortcut for::
await bot.forward_message(from_chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see :meth:`telegram.Bot.forward_message`.
.. seealso:: :meth:`forward_from`
.. versionadded:: 20.0
Returns:
:class:`telegram.Message`: On success, instance representing the message posted.
"""
return await self.get_bot().forward_message(
from_chat_id=self.id,
chat_id=chat_id,
message_id=message_id,
disable_notification=disable_notification,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
protect_content=protect_content,
)
async def export_invite_link(
self,
*,
+13
View File
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, List, Optional
from telegram import constants
from telegram._files._basethumbedmedium import _BaseThumbedMedium
from telegram._files.file import File
from telegram._files.photosize import PhotoSize
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
@@ -62,6 +63,10 @@ class Sticker(_BaseThumbedMedium):
position where the mask should be placed.
file_size (:obj:`int`, optional): File size in bytes.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
premium_animation (:class:`telegram.File`, optional): Premium animation for the sticker,
if the sticker is premium.
.. versionadded:: 20.0
_kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
@@ -83,6 +88,10 @@ class Sticker(_BaseThumbedMedium):
where the mask should be placed.
file_size (:obj:`int`): Optional. File size in bytes.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
premium_animation (:class:`telegram.File`): Optional. Premium animation for the
sticker, if the sticker is premium.
.. versionadded:: 20.0
"""
@@ -94,6 +103,7 @@ class Sticker(_BaseThumbedMedium):
"mask_position",
"set_name",
"width",
"premium_animation",
)
def __init__(
@@ -110,6 +120,7 @@ class Sticker(_BaseThumbedMedium):
set_name: str = None,
mask_position: "MaskPosition" = None,
bot: "Bot" = None,
premium_animation: "File" = None,
**_kwargs: Any,
):
super().__init__(
@@ -128,6 +139,7 @@ class Sticker(_BaseThumbedMedium):
self.emoji = emoji
self.set_name = set_name
self.mask_position = mask_position
self.premium_animation = premium_animation
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Sticker"]:
@@ -139,6 +151,7 @@ class Sticker(_BaseThumbedMedium):
data["thumb"] = PhotoSize.de_json(data.get("thumb"), bot)
data["mask_position"] = MaskPosition.de_json(data.get("mask_position"), bot)
data["premium_animation"] = File.de_json(data.get("premium_animation"), bot)
return cls(bot=bot, **data)
+3 -3
View File
@@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram InlineKeyboardButton."""
from typing import TYPE_CHECKING, Any, Optional
from typing import TYPE_CHECKING, Any, Optional, Union
from telegram._games.callbackgame import CallbackGame
from telegram._loginurl import LoginUrl
@@ -163,7 +163,7 @@ class InlineKeyboardButton(TelegramObject):
self,
text: str,
url: str = None,
callback_data: object = None,
callback_data: Union[str, object] = None,
switch_inline_query: str = None,
switch_inline_query_current_chat: str = None,
callback_game: CallbackGame = None,
@@ -214,7 +214,7 @@ class InlineKeyboardButton(TelegramObject):
return cls(**data)
def update_callback_data(self, callback_data: object) -> None:
def update_callback_data(self, callback_data: Union[str, object]) -> None:
"""
Sets :attr:`callback_data` to the passed object. Intended to be used by
:class:`telegram.ext.CallbackDataCache`.
+21 -11
View File
@@ -39,9 +39,14 @@ class InputInvoiceMessageContent(InputMessageContent):
.. versionadded:: 13.5
Args:
title (:obj:`str`): Product name, 1-32 characters
description (:obj:`str`): Product description, 1-255 characters
payload (:obj:`str`):Bot-defined invoice payload, 1-128 bytes. This will not be displayed
title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`-
:tg-const:`telegram.Invoice.MAX_TITLE_LENGTH` characters.
description (:obj:`str`): Product description.
:tg-const:`telegram.Invoice.MIN_DESCRIPTION_LENGTH`-
:tg-const:`telegram.Invoice.MAX_DESCRIPTION_LENGTH` characters.
payload (:obj:`str`): Bot-defined invoice payload.
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed
to the user, use for your internal processes.
provider_token (:obj:`str`): Payment provider token, obtained via
`@Botfather <https://t.me/Botfather>`_.
@@ -50,15 +55,15 @@ class InputInvoiceMessageContent(InputMessageContent):
prices (List[:class:`telegram.LabeledPrice`]): Price breakdown, a list of
components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus,
etc.)
max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the smallest
units of the currency (integer, not float/double). For example, for a maximum tip of
US$ 1.45 pass ``max_tip_amount = 145``. See the ``exp`` parameter in
max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the
*smallest* units of the currency (integer, **not** float/double). For example, for a
maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, it
shows the number of digits past the decimal point for each currency (2 for the majority
of currencies). Defaults to ``0``.
suggested_tip_amounts (List[:obj:`int`], optional): An array of suggested
amounts of tip in the smallest units of the currency (integer, not float/double). At
most 4 suggested tip amounts can be specified. The suggested tip amounts must be
amounts of tip in the *smallest* units of the currency (integer, **not** float/double).
At most 4 suggested tip amounts can be specified. The suggested tip amounts must be
positive, passed in a strictly increased order and must not exceed
:attr:`max_tip_amount`.
provider_data (:obj:`str`, optional): An object for data about the invoice,
@@ -87,9 +92,14 @@ class InputInvoiceMessageContent(InputMessageContent):
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
title (:obj:`str`): Product name, 1-32 characters
description (:obj:`str`): Product description, 1-255 characters
payload (:obj:`str`):Bot-defined invoice payload, 1-128 bytes. This will not be displayed
title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`-
:tg-const:`telegram.Invoice.MAX_TITLE_LENGTH` characters.
description (:obj:`str`): Product description.
:tg-const:`telegram.Invoice.MIN_DESCRIPTION_LENGTH`-
:tg-const:`telegram.Invoice.MAX_DESCRIPTION_LENGTH` characters.
payload (:obj:`str`): Bot-defined invoice payload.
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed
to the user, use for your internal processes.
provider_token (:obj:`str`): Payment provider token, obtained via
`@Botfather <https://t.me/Botfather>`_.
+2 -2
View File
@@ -39,7 +39,7 @@ class LoginUrl(TelegramObject):
`Checking authorization <https://core.telegram.org/widgets/login#checking-authorization>`_
Args:
url (:obj:`str`): An HTTP URL to be opened with user authorization data added to the query
url (:obj:`str`): An HTTPS URL to be opened with user authorization data added to the query
string when the button is pressed. If the user refuses to provide authorization data,
the original URL without information about the user will be opened. The data added is
the same as described in
@@ -59,7 +59,7 @@ class LoginUrl(TelegramObject):
for your bot to send messages to the user.
Attributes:
url (:obj:`str`): An HTTP URL to be opened with user authorization data.
url (:obj:`str`): An HTTPS URL to be opened with user authorization data.
forward_text (:obj:`str`): Optional. New text of the button in forwarded messages.
bot_username (:obj:`str`): Optional. Username of a bot, which will be used for user
authorization.
+33 -1
View File
@@ -18,8 +18,9 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Invoice."""
from typing import Any
from typing import Any, ClassVar
from telegram import constants
from telegram._telegramobject import TelegramObject
@@ -83,3 +84,34 @@ class Invoice(TelegramObject):
self.currency,
self.total_amount,
)
MIN_TITLE_LENGTH: ClassVar[int] = constants.InvoiceLimit.MIN_TITLE_LENGTH
""":const:`telegram.constants.InvoiceLimit.MIN_TITLE_LENGTH`
.. versionadded:: 20.0
"""
MAX_TITLE_LENGTH: ClassVar[int] = constants.InvoiceLimit.MAX_TITLE_LENGTH
""":const:`telegram.constants.InvoiceLimit.MAX_TITLE_LENGTH`
.. versionadded:: 20.0
"""
MIN_DESCRIPTION_LENGTH: ClassVar[int] = constants.InvoiceLimit.MIN_DESCRIPTION_LENGTH
""":const:`telegram.constants.InvoiceLimit.MIN_DESCRIPTION_LENGTH`
.. versionadded:: 20.0
"""
MAX_DESCRIPTION_LENGTH: ClassVar[int] = constants.InvoiceLimit.MAX_DESCRIPTION_LENGTH
""":const:`telegram.constants.InvoiceLimit.MAX_DESCRIPTION_LENGTH`
.. versionadded:: 20.0
"""
MIN_PAYLOAD_LENGTH: ClassVar[int] = constants.InvoiceLimit.MIN_PAYLOAD_LENGTH
""":const:`telegram.constants.InvoiceLimit.MIN_PAYLOAD_LENGTH`
.. versionadded:: 20.0
"""
MAX_PAYLOAD_LENGTH: ClassVar[int] = constants.InvoiceLimit.MAX_PAYLOAD_LENGTH
""":const:`telegram.constants.InvoiceLimit.MAX_PAYLOAD_LENGTH`
.. versionadded:: 20.0
"""
+20
View File
@@ -83,6 +83,13 @@ class User(TelegramObject):
supports_inline_queries (:obj:`str`, optional): :obj:`True`, if the bot supports inline
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
is_premium (:obj:`bool`, optional): :obj:`True`, if this user is a Telegram Premium user.
.. versionadded:: 20.0
added_to_attachment_menu (:obj:`bool`, optional): :obj:`True`, if this user added
the bot to the attachment menu.
.. versionadded:: 20.0
Attributes:
id (:obj:`int`): Unique identifier for this user or bot.
@@ -98,7 +105,14 @@ class User(TelegramObject):
supports_inline_queries (:obj:`str`): Optional. :obj:`True`, if the bot supports inline
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
is_premium (:obj:`bool`): Optional. :obj:`True`, if this user is a Telegram
Premium user.
.. versionadded:: 20.0
added_to_attachment_menu (:obj:`bool`): Optional. :obj:`True`, if this user added
the bot to the attachment menu.
.. versionadded:: 20.0
"""
__slots__ = (
@@ -111,6 +125,8 @@ class User(TelegramObject):
"supports_inline_queries",
"id",
"language_code",
"is_premium",
"added_to_attachment_menu",
)
def __init__(
@@ -125,6 +141,8 @@ class User(TelegramObject):
can_read_all_group_messages: bool = None,
supports_inline_queries: bool = None,
bot: "Bot" = None,
is_premium: bool = None,
added_to_attachment_menu: bool = None,
**_kwargs: Any,
):
# Required
@@ -138,6 +156,8 @@ class User(TelegramObject):
self.can_join_groups = can_join_groups
self.can_read_all_group_messages = can_read_all_group_messages
self.supports_inline_queries = supports_inline_queries
self.is_premium = is_premium
self.added_to_attachment_menu = added_to_attachment_menu
self.set_bot(bot)
self._id_attrs = (self.id,)
+1 -1
View File
@@ -50,7 +50,7 @@ class Version(NamedTuple):
return version
__version_info__ = Version(major=20, minor=0, micro=0, releaselevel="alpha", serial=1)
__version_info__ = Version(major=20, minor=0, micro=0, releaselevel="alpha", serial=2)
__version__ = str(__version_info__)
# # SETUP.PY MARKER
+41 -1
View File
@@ -45,6 +45,7 @@ __all__ = [
"InlineQueryLimit",
"InlineQueryResultType",
"InputMediaType",
"InvoiceLimit",
"LocationLimit",
"MaskPosition",
"MenuButtonType",
@@ -56,6 +57,7 @@ __all__ = [
"PollLimit",
"PollType",
"SUPPORTED_WEBHOOK_PORTS",
"WebhookLimit",
"UpdateType",
]
@@ -90,7 +92,7 @@ class _BotAPIVersion(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=0)
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=1)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@@ -809,3 +811,41 @@ class UpdateType(StringEnum):
""":obj:`str`: Updates with :attr:`telegram.Update.chat_member`."""
CHAT_JOIN_REQUEST = "chat_join_request"
""":obj:`str`: Updates with :attr:`telegram.Update.chat_join_request`."""
class InvoiceLimit(IntEnum):
"""This enum contains limitations for :meth:`telegram.Bot.create_invoice_link`. The enum
members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 20.0
"""
__slots__ = ()
MIN_TITLE_LENGTH = 1
""":obj:`int`: Minimum number of characters of the invoice title."""
MAX_TITLE_LENGTH = 32
""":obj:`int`: Maximum number of characters of the invoice title."""
MIN_DESCRIPTION_LENGTH = 1
""":obj:`int`: Minimum number of characters of the invoice description."""
MAX_DESCRIPTION_LENGTH = 255
""":obj:`int`: Maximum number of characters of the invoice description."""
MIN_PAYLOAD_LENGTH = 1
""":obj:`int`: Minimum amount of bytes for the internal payload."""
MAX_PAYLOAD_LENGTH = 128
""":obj:`int`: Maximum amount of bytes for the internal payload."""
class WebhookLimit(IntEnum):
"""This enum contains limitations for :paramref:`telegram.Bot.set_webhook.secret_token`. The
enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 20.0
"""
__slots__ = ()
MIN_SECRET_TOKEN_LENGTH = 1
""":obj:`int`: Minimum length of the secret token."""
MAX_SECRET_TOKEN_LENGTH = 256
""":obj:`int`: Maximum length of the secret token."""
+14 -1
View File
@@ -359,7 +359,8 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager)
* :attr:`bot` by calling :meth:`telegram.Bot.shutdown`
* :attr:`updater` by calling :meth:`telegram.ext.Updater.shutdown`
* :attr:`persistence` by calling :meth:`update_persistence` and :meth`persistence.flush`
* :attr:`persistence` by calling :meth:`update_persistence` and
:meth:`BasePersistence.flush`
.. seealso::
:meth:`initialize`
@@ -662,6 +663,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager)
max_connections: int = 40,
close_loop: bool = True,
stop_signals: ODVInput[Sequence[int]] = DEFAULT_NONE,
secret_token: str = None,
) -> None:
"""Convenience method that takes care of initializing and starting the app,
polling updates from Telegram using :meth:`telegram.ext.Updater.start_webhook` and
@@ -724,6 +726,16 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager)
:meth:`asyncio.loop.add_signal_handler`. Most notably, the standard event loop
on Windows, :class:`asyncio.ProactorEventLoop`, does not implement this method.
If this method is not available, stop signals can not be set.
secret_token (:obj:`str`, optional): Secret token to ensure webhook requests originate
from Telegram. See :paramref:`telegram.Bot.set_webhook.secret_token` for more
details.
When added, the web server started by this call will expect the token to be set in
the ``X-Telegram-Bot-Api-Secret-Token`` header of an incoming request and will
raise a :class:`http.HTTPStatus.FORBIDDEN <http.HTTPStatus>` error if either the
header isn't set or it is set to a wrong token.
.. versionadded:: 20.0
"""
if not self.updater:
raise RuntimeError(
@@ -743,6 +755,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager)
allowed_updates=allowed_updates,
ip_address=ip_address,
max_connections=max_connections,
secret_token=secret_token,
),
close_loop=close_loop,
stop_signals=stop_signals,
+2 -1
View File
@@ -369,7 +369,8 @@ class CallbackDataCache:
time_cutoff (:obj:`float` | :obj:`datetime.datetime`, optional): Pass a UNIX timestamp
or a :obj:`datetime.datetime` to clear only entries which are older.
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
bot will be used.
bot will be used, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is
used.
"""
self.__clear(self._keyboard_data, time_cutoff=time_cutoff)
+11 -6
View File
@@ -148,11 +148,13 @@ class JobQueue:
job should run.
* :obj:`datetime.datetime` will be interpreted as a specific date and time at
which the job should run. If the timezone (:attr:`datetime.datetime.tzinfo`) is
:obj:`None`, the default timezone of the bot will be used.
:obj:`None`, the default timezone of the bot will be used, which is UTC unless
:attr:`telegram.ext.Defaults.tzinfo` is used.
* :obj:`datetime.time` will be interpreted as a specific time of day at which the
job should run. This could be either today or, if the time has already passed,
tomorrow. If the timezone (:attr:`datetime.time.tzinfo`) is :obj:`None`, the
default timezone of the bot will be used.
default timezone of the bot will be used, which is UTC unless
:attr:`telegram.ext.Defaults.tzinfo` is used.
chat_id (:obj:`int`, optional): Chat id of the chat associated with this job. If
passed, the corresponding :attr:`~telegram.ext.CallbackContext.chat_data` will
@@ -246,7 +248,8 @@ class JobQueue:
* :obj:`datetime.time` will be interpreted as a specific time of day at which the
job should run. This could be either today or, if the time has already passed,
tomorrow. If the timezone (:attr:`datetime.time.tzinfo`) is :obj:`None`, the
default timezone of the bot will be used.
default timezone of the bot will be used, which is UTC unless
:attr:`telegram.ext.Defaults.tzinfo` is used.
Defaults to :paramref:`interval`
last (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \
@@ -256,7 +259,7 @@ class JobQueue:
If :paramref:`last` is :obj:`datetime.datetime` or :obj:`datetime.time` type
and ``last.tzinfo`` is :obj:`None`, the default timezone of the bot will be
assumed.
assumed, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is used.
Defaults to :obj:`None`.
data (:obj:`object`, optional): Additional data needed for the callback function.
@@ -339,7 +342,8 @@ class JobQueue:
async def callback(context: CallbackContext)
when (:obj:`datetime.time`): Time of day at which the job should run. If the timezone
(``when.tzinfo``) is :obj:`None`, the default timezone of the bot will be used.
(``when.tzinfo``) is :obj:`None`, the default timezone of the bot will be used,
which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is used.
day (:obj:`int`): Defines the day of the month whereby the job would run. It should
be within the range of ``1`` and ``31``, inclusive. If a month has fewer days than
this number, the job will not run in this month. Passing ``-1`` leads to the job
@@ -419,7 +423,7 @@ class JobQueue:
time (:obj:`datetime.time`): Time of day at which the job should run. If the timezone
(:obj:`datetime.time.tzinfo`) is :obj:`None`, the default timezone of the bot will
be used.
be used, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is used.
days (Tuple[:obj:`int`], optional): Defines on which days of the week the job should
run (where ``0-6`` correspond to sunday - saturday). By default, the job will run
every day.
@@ -591,6 +595,7 @@ class Job:
this :class:`telegram.ext.Job` to be useful.
.. versionchanged:: 20.0
* Removed argument and attribute ``job_queue``.
* Renamed ``Job.context`` to :attr:`Job.data`.
+19 -1
View File
@@ -369,6 +369,7 @@ class Updater(AbstractAsyncContextManager):
drop_pending_updates: bool = None,
ip_address: str = None,
max_connections: int = 40,
secret_token: str = None,
) -> asyncio.Queue:
"""
Starts a small http server to listen for updates via webhook. If :paramref:`cert`
@@ -395,6 +396,7 @@ class Updater(AbstractAsyncContextManager):
key (:class:`pathlib.Path` | :obj:`str`, optional): Path to the SSL key file.
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
Telegram servers before actually starting to poll. Default is :obj:`False`.
.. versionadded :: 13.4
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
@@ -407,12 +409,23 @@ class Updater(AbstractAsyncContextManager):
:paramref:`port`, :paramref:`url_path`, :paramref:`cert`, and :paramref:`key`.
ip_address (:obj:`str`, optional): Passed to :meth:`telegram.Bot.set_webhook`.
Defaults to :obj:`None`.
.. versionadded :: 13.4
allowed_updates (List[:obj:`str`], optional): Passed to
:meth:`telegram.Bot.set_webhook`. Defaults to :obj:`None`.
max_connections (:obj:`int`, optional): Passed to
:meth:`telegram.Bot.set_webhook`. Defaults to ``40``.
.. versionadded:: 13.6
secret_token (:obj:`str`, optional): Passed to :meth:`telegram.Bot.set_webhook`.
Defaults to :obj:`None`.
When added, the web server started by this call will expect the token to be set in
the ``X-Telegram-Bot-Api-Secret-Token`` header of an incoming request and will
raise a :class:`http.HTTPStatus.FORBIDDEN <http.HTTPStatus>` error if either the
header isn't set or it is set to a wrong token.
.. versionadded:: 20.0
Returns:
:class:`queue.Queue`: The update queue that can be filled from the main thread.
@@ -444,6 +457,7 @@ class Updater(AbstractAsyncContextManager):
ready=webhook_ready,
ip_address=ip_address,
max_connections=max_connections,
secret_token=secret_token,
)
self._logger.debug("Waiting for webhook server to start")
@@ -470,6 +484,7 @@ class Updater(AbstractAsyncContextManager):
ready: asyncio.Event = None,
ip_address: str = None,
max_connections: int = 40,
secret_token: str = None,
) -> None:
self._logger.debug("Updater thread started (webhook)")
@@ -477,7 +492,7 @@ class Updater(AbstractAsyncContextManager):
url_path = f"/{url_path}"
# Create Tornado app instance
app = WebhookAppClass(url_path, self.bot, self.update_queue)
app = WebhookAppClass(url_path, self.bot, self.update_queue, secret_token)
# Form SSL Context
# An SSLError is raised if the private key does not match with the certificate
@@ -517,6 +532,7 @@ class Updater(AbstractAsyncContextManager):
allowed_updates=allowed_updates,
ip_address=ip_address,
max_connections=max_connections,
secret_token=secret_token,
)
await self._httpd.serve_forever(ready=ready)
@@ -591,6 +607,7 @@ class Updater(AbstractAsyncContextManager):
bootstrap_interval: float = 1,
ip_address: str = None,
max_connections: int = 40,
secret_token: str = None,
) -> None:
"""Prepares the setup for fetching updates: delete or set the webhook and drop pending
updates if appropriate. If there are unsuccessful attempts, this will retry as specified by
@@ -616,6 +633,7 @@ class Updater(AbstractAsyncContextManager):
ip_address=ip_address,
drop_pending_updates=drop_pending_updates,
max_connections=max_connections,
secret_token=secret_token,
)
return False
+28 -4
View File
@@ -83,8 +83,14 @@ class WebhookServer:
class WebhookAppClass(tornado.web.Application):
"""Application used in the Webserver"""
def __init__(self, webhook_path: str, bot: "Bot", update_queue: asyncio.Queue):
self.shared_objects = {"bot": bot, "update_queue": update_queue}
def __init__(
self, webhook_path: str, bot: "Bot", update_queue: asyncio.Queue, secret_token: str = None
):
self.shared_objects = {
"bot": bot,
"update_queue": update_queue,
"secret_token": secret_token,
}
handlers = [(rf"{webhook_path}/?", TelegramHandler, self.shared_objects)] # noqa
tornado.web.Application.__init__(self, handlers) # type: ignore
@@ -96,16 +102,21 @@ class WebhookAppClass(tornado.web.Application):
class TelegramHandler(tornado.web.RequestHandler):
"""BaseHandler that processes incoming requests from Telegram"""
__slots__ = ("bot", "update_queue", "_logger")
__slots__ = ("bot", "update_queue", "_logger", "secret_token")
SUPPORTED_METHODS = ("POST",) # type: ignore[assignment]
def initialize(self, bot: "Bot", update_queue: asyncio.Queue) -> None:
def initialize(self, bot: "Bot", update_queue: asyncio.Queue, secret_token: str) -> None:
"""Initialize for each request - that's the interface provided by tornado"""
# pylint: disable=attribute-defined-outside-init
self.bot = bot
self.update_queue = update_queue
self._logger = logging.getLogger(__name__)
self.secret_token = secret_token
if secret_token:
self._logger.debug(
"The webhook server has a secret token, " "expecting it in incoming requests now"
)
def set_default_headers(self) -> None:
"""Sets default headers"""
@@ -144,6 +155,19 @@ class TelegramHandler(tornado.web.RequestHandler):
ct_header = self.request.headers.get("Content-Type", None)
if ct_header != "application/json":
raise tornado.web.HTTPError(HTTPStatus.FORBIDDEN)
# verifying that the secret token is the one the user set when the user set one
if self.secret_token is not None:
token = self.request.headers.get("X-Telegram-Bot-Api-Secret-Token")
if not token:
self._logger.debug("Request did not include the secret token")
raise tornado.web.HTTPError(
HTTPStatus.FORBIDDEN, reason="Request did not include the secret token"
)
if token != self.secret_token:
self._logger.debug("Request had the wrong secret token: %s", token)
raise tornado.web.HTTPError(
HTTPStatus.FORBIDDEN, reason="Request had the wrong secret token"
)
def log_exception(
self,
+52
View File
@@ -76,6 +76,8 @@ __all__ = (
"TEXT",
"Text",
"USER",
"USER_ATTACHMENT",
"PREMIUM_USER",
"UpdateFilter",
"UpdateType",
"User",
@@ -1949,6 +1951,21 @@ class Sticker:
.. versionadded:: 20.0
"""
class _Premium(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.sticker) and bool(
message.sticker.premium_animation # type: ignore
)
PREMIUM = _Premium(name="filters.Sticker.PREMIUM")
"""Messages that contain :attr:`telegram.Message.sticker` and have a
:attr:`premium animation <telegram.Sticker.premium_animation>`.
.. versionadded:: 20.0
"""
class _SuccessfulPayment(MessageFilter):
__slots__ = ()
@@ -2185,6 +2202,41 @@ USER = _User(name="filters.USER")
"""This filter filters *any* message that has a :attr:`telegram.Message.from_user`."""
class _UserAttachment(UpdateFilter):
__slots__ = ()
def filter(self, update: Update) -> bool:
return bool(update.effective_user) and bool(
update.effective_user.added_to_attachment_menu # type: ignore
)
USER_ATTACHMENT = _UserAttachment(name="filters.USER_ATTACHMENT")
"""This filter filters *any* message that have a user who added the bot to their
:attr:`attachment menu <telegram.User.added_to_attachment_menu>` as
:attr:`telegram.Update.effective_user`.
.. versionadded:: 20.0
"""
class _UserPremium(UpdateFilter):
__slots__ = ()
def filter(self, update: Update) -> bool:
return bool(update.effective_user) and bool(
update.effective_user.is_premium # type: ignore
)
PREMIUM_USER = _UserPremium(name="filters.PREMIUM_USER")
"""This filter filters *any* message from a
:attr:`Telegram Premium user <telegram.User.is_premium>` as :attr:`telegram.Update.effective_user`.
.. versionadded:: 20.0
"""
class _Venue(MessageFilter):
__slots__ = ()
+9 -3
View File
@@ -250,13 +250,16 @@ def class_thumb_file():
f.close()
def make_bot(bot_info, **kwargs):
def make_bot(bot_info=None, **kwargs):
"""
Tests are executed on tg.ext.ExtBot, as that class only extends the functionality of tg.bot
"""
token = kwargs.pop("token", (bot_info or {}).get("token"))
private_key = kwargs.pop("private_key", PRIVATE_KEY)
kwargs.pop("token", None)
_bot = DictExtBot(
bot_info["token"],
private_key=PRIVATE_KEY,
token=token,
private_key=private_key,
request=TestHttpxRequest(8),
get_updates_request=TestHttpxRequest(1),
**kwargs,
@@ -827,10 +830,13 @@ async def send_webhook_message(
content_len: int = -1,
content_type: str = "application/json",
get_method: str = None,
secret_token: str = None,
) -> Response:
headers = {
"content-type": content_type,
}
if secret_token:
headers["X-Telegram-Bot-Api-Secret-Token"] = secret_token
if not payload_str:
content_len = None
+35 -5
View File
@@ -75,6 +75,7 @@ from tests.conftest import (
check_defaults_handling,
data_file,
expect_bad_request,
make_bot,
)
@@ -282,7 +283,7 @@ class TestBot:
async def test_invalid_token_server_response(self, monkeypatch):
monkeypatch.setattr("telegram.Bot._validate_token", lambda x, y: "")
with pytest.raises(InvalidToken):
async with Bot("12") as bot:
async with make_bot(token="12") as bot:
await bot.get_me()
async def test_unknown_kwargs(self, bot, monkeypatch):
@@ -335,9 +336,9 @@ class TestBot:
await bot.shutdown()
async def test_equality(self):
async with Bot(FALLBACKS[0]["token"]) as a, Bot(FALLBACKS[0]["token"]) as b, Bot(
FALLBACKS[1]["token"]
) as c:
async with make_bot(token=FALLBACKS[0]["token"]) as a, make_bot(
token=FALLBACKS[0]["token"]
) as b, make_bot(token=FALLBACKS[1]["token"]) as c:
d = Update(123456789)
assert a == b
@@ -1633,6 +1634,35 @@ class TestBot:
assert await bot.set_webhook("", drop_pending_updates=drop_pending_updates)
assert await bot.delete_webhook(drop_pending_updates=drop_pending_updates)
async def test_set_webhook_params(self, bot, monkeypatch):
# actually making calls to TG is done in
# test_set_webhook_get_webhook_info_and_delete_webhook. Sadly secret_token can't be tested
# there so we have this function \o/
async def make_assertion(*args, **_):
kwargs = args[1]
return (
kwargs["url"] == "example.com"
and kwargs["certificate"].input_file_content
== data_file("sslcert.pem").read_bytes()
and kwargs["max_connections"] == 7
and kwargs["allowed_updates"] == ["messages"]
and kwargs["ip_address"] == "127.0.0.1"
and kwargs["drop_pending_updates"]
and kwargs["secret_token"] == "SoSecretToken"
)
monkeypatch.setattr(bot, "_post", make_assertion)
assert await bot.set_webhook(
"example.com",
data_file("sslcert.pem").read_bytes(),
7,
["messages"],
"127.0.0.1",
True,
"SoSecretToken",
)
@flaky(3, 1)
async def test_leave_chat(self, bot):
with pytest.raises(BadRequest, match="Chat not found"):
@@ -1832,7 +1862,7 @@ class TestBot:
# We assume that the other game score tests ran within 20 sec
assert high_scores[0].score == BASE_GAME_SCORE - 10
# send_invoice is tested in test_invoice
# send_invoice and create_invoice_link is tested in test_invoice
# TODO: Needs improvement. Need incoming shipping queries to test
async def test_answer_shipping_query_ok(self, monkeypatch, bot):
+93
View File
@@ -42,6 +42,8 @@ def chat(bot):
location=TestChat.location,
has_private_forwards=True,
has_protected_content=True,
join_to_send_messages=True,
join_by_request=True,
)
@@ -64,6 +66,8 @@ class TestChat:
location = ChatLocation(Location(123, 456), "Barbie World")
has_protected_content = True
has_private_forwards = True
join_to_send_messages = True
join_by_request = True
def test_slot_behaviour(self, chat, mro_slots):
for attr in chat.__slots__:
@@ -86,6 +90,8 @@ class TestChat:
"has_private_forwards": self.has_private_forwards,
"linked_chat_id": self.linked_chat_id,
"location": self.location.to_dict(),
"join_to_send_messages": self.join_to_send_messages,
"join_by_request": self.join_by_request,
}
chat = Chat.de_json(json_dict, bot)
@@ -104,6 +110,8 @@ class TestChat:
assert chat.linked_chat_id == self.linked_chat_id
assert chat.location.location == self.location.location
assert chat.location.address == self.location.address
assert chat.join_to_send_messages == self.join_to_send_messages
assert chat.join_by_request == self.join_by_request
def test_to_dict(self, chat):
chat_dict = chat.to_dict()
@@ -121,6 +129,8 @@ class TestChat:
assert chat_dict["has_protected_content"] == chat.has_protected_content
assert chat_dict["linked_chat_id"] == chat.linked_chat_id
assert chat_dict["location"] == chat.location.to_dict()
assert chat_dict["join_to_send_messages"] == chat.join_to_send_messages
assert chat_dict["join_by_request"] == chat.join_by_request
def test_enum_init(self):
chat = Chat(id=1, type="foo")
@@ -373,6 +383,61 @@ class TestChat:
monkeypatch.setattr("telegram.Bot.set_chat_administrator_custom_title", make_assertion)
assert await chat.set_administrator_custom_title(user_id=42, custom_title="custom_title")
async def test_set_photo(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
chat_id = kwargs["chat_id"] == chat.id
photo = kwargs["photo"] == "test_photo"
return chat_id, photo
assert check_shortcut_signature(Chat.set_photo, Bot.set_chat_photo, ["chat_id"], [])
assert await check_shortcut_call(chat.set_photo, chat.get_bot(), "set_chat_photo")
assert await check_defaults_handling(chat.set_photo, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "set_chat_photo", make_assertion)
assert await chat.set_photo(photo="test_photo")
async def test_delete_photo(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
chat_id = kwargs["chat_id"] == chat.id
return chat_id
assert check_shortcut_signature(Chat.delete_photo, Bot.delete_chat_photo, ["chat_id"], [])
assert await check_shortcut_call(chat.delete_photo, chat.get_bot(), "delete_chat_photo")
assert await check_defaults_handling(chat.delete_photo, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "delete_chat_photo", make_assertion)
assert await chat.delete_photo()
async def test_set_title(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
chat_id = kwargs["chat_id"] == chat.id
title = kwargs["title"] == "test_title"
return chat_id, title
assert check_shortcut_signature(Chat.set_title, Bot.set_chat_title, ["chat_id"], [])
assert await check_shortcut_call(chat.set_title, chat.get_bot(), "set_chat_title")
assert await check_defaults_handling(chat.set_title, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "set_chat_title", make_assertion)
assert await chat.set_title(title="test_title")
async def test_set_description(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
chat_id = kwargs["chat_id"] == chat.id
description = kwargs["description"] == "test_descripton"
return chat_id, description
assert check_shortcut_signature(
Chat.set_description, Bot.set_chat_description, ["chat_id"], []
)
assert await check_shortcut_call(
chat.set_description, chat.get_bot(), "set_chat_description"
)
assert await check_defaults_handling(chat.set_description, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "set_chat_description", make_assertion)
assert await chat.set_description(description="test_description")
async def test_pin_message(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id and kwargs["message_id"] == 42
@@ -643,6 +708,34 @@ class TestChat:
monkeypatch.setattr(chat.get_bot(), "copy_message", make_assertion)
assert await chat.copy_message(chat_id="test_copy", message_id=42)
async def test_instance_method_forward_from(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
chat_id = kwargs["chat_id"] == chat.id
message_id = kwargs["message_id"] == 42
from_chat_id = kwargs["from_chat_id"] == "test_forward"
return from_chat_id and message_id and chat_id
assert check_shortcut_signature(Chat.forward_from, Bot.forward_message, ["chat_id"], [])
assert await check_shortcut_call(chat.forward_from, chat.get_bot(), "forward_message")
assert await check_defaults_handling(chat.forward_from, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "forward_message", make_assertion)
assert await chat.forward_from(from_chat_id="test_forward", message_id=42)
async def test_instance_method_forward_to(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
from_chat_id = kwargs["from_chat_id"] == chat.id
message_id = kwargs["message_id"] == 42
chat_id = kwargs["chat_id"] == "test_forward"
return from_chat_id and message_id and chat_id
assert check_shortcut_signature(Chat.forward_to, Bot.forward_message, ["from_chat_id"], [])
assert await check_shortcut_call(chat.forward_to, chat.get_bot(), "forward_message")
assert await check_defaults_handling(chat.forward_to, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "forward_message", make_assertion)
assert await chat.forward_to(chat_id="test_forward", message_id=42)
async def test_export_invite_link(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id
+25
View File
@@ -27,6 +27,7 @@ from telegram import (
Chat,
Dice,
Document,
File,
Message,
MessageEntity,
Sticker,
@@ -830,15 +831,26 @@ class TestFilters:
update.message.sticker = Sticker("1", "uniq", 1, 2, False, False)
assert filters.Sticker.ALL.check_update(update)
assert filters.Sticker.STATIC.check_update(update)
assert not filters.Sticker.VIDEO.check_update(update)
assert not filters.Sticker.PREMIUM.check_update(update)
update.message.sticker.is_animated = True
assert filters.Sticker.ANIMATED.check_update(update)
assert not filters.Sticker.VIDEO.check_update(update)
assert not filters.Sticker.STATIC.check_update(update)
assert not filters.Sticker.PREMIUM.check_update(update)
update.message.sticker.is_animated = False
update.message.sticker.is_video = True
assert not filters.Sticker.ANIMATED.check_update(update)
assert not filters.Sticker.STATIC.check_update(update)
assert filters.Sticker.VIDEO.check_update(update)
assert not filters.Sticker.PREMIUM.check_update(update)
update.message.sticker.premium_animation = File("string", "uniqueString")
assert not filters.Sticker.ANIMATED.check_update(update)
# premium stickers can be animated, video, or probably also static,
# it doesn't really matter for the test
assert not filters.Sticker.STATIC.check_update(update)
assert filters.Sticker.VIDEO.check_update(update)
assert filters.Sticker.PREMIUM.check_update(update)
def test_filters_video(self, update):
assert not filters.VIDEO.check_update(update)
@@ -1168,6 +1180,19 @@ class TestFilters:
with pytest.raises(RuntimeError, match="Cannot set name"):
f.name = "foo"
def test_filters_user_attributes(self, update):
assert not filters.USER_ATTACHMENT.check_update(update)
assert not filters.PREMIUM_USER.check_update(update)
update.message.from_user.added_to_attachment_menu = True
assert filters.USER_ATTACHMENT.check_update(update)
assert not filters.PREMIUM_USER.check_update(update)
update.message.from_user.is_premium = True
assert filters.USER_ATTACHMENT.check_update(update)
assert filters.PREMIUM_USER.check_update(update)
update.message.from_user.added_to_attachment_menu = False
assert not filters.USER_ATTACHMENT.check_update(update)
assert filters.PREMIUM_USER.check_update(update)
def test_filters_chat_init(self):
with pytest.raises(RuntimeError, match="in conjunction with"):
filters.Chat(chat_id=1, username="chat")
+64 -2
View File
@@ -98,8 +98,18 @@ class TestInvoice:
assert message.invoice.title == self.title
assert message.invoice.total_amount == self.total_amount
@flaky(3, 1)
async def test_send_all_args(self, bot, chat_id, provider_token, monkeypatch):
link = await bot.create_invoice_link(
title=self.title,
description=self.description,
payload=self.payload,
provider_token=provider_token,
currency=self.currency,
prices=self.prices,
)
assert isinstance(link, str)
assert link != ""
async def test_send_all_args_send_invoice(self, bot, chat_id, provider_token, monkeypatch):
message = await bot.send_invoice(
chat_id,
self.title,
@@ -193,6 +203,58 @@ class TestInvoice:
protect_content=True,
)
async def test_send_all_args_create_invoice_link(
self, bot, chat_id, provider_token, monkeypatch
):
async def make_assertion(*args, **_):
kwargs = args[1]
return (
kwargs["title"] == "title"
and kwargs["description"] == "description"
and kwargs["payload"] == "payload"
and kwargs["provider_token"] == "provider_token"
and kwargs["currency"] == "currency"
and kwargs["prices"] == self.prices
and kwargs["max_tip_amount"] == "max_tip_amount"
and kwargs["suggested_tip_amounts"] == "suggested_tip_amounts"
and kwargs["provider_data"] == "provider_data"
and kwargs["photo_url"] == "photo_url"
and kwargs["photo_size"] == "photo_size"
and kwargs["photo_width"] == "photo_width"
and kwargs["photo_height"] == "photo_height"
and kwargs["need_name"] == "need_name"
and kwargs["need_phone_number"] == "need_phone_number"
and kwargs["need_email"] == "need_email"
and kwargs["need_shipping_address"] == "need_shipping_address"
and kwargs["send_phone_number_to_provider"] == "send_phone_number_to_provider"
and kwargs["send_email_to_provider"] == "send_email_to_provider"
and kwargs["is_flexible"] == "is_flexible"
)
monkeypatch.setattr(bot, "_post", make_assertion)
assert await bot.create_invoice_link(
title="title",
description="description",
payload="payload",
provider_token="provider_token",
currency="currency",
prices=self.prices,
max_tip_amount="max_tip_amount",
suggested_tip_amounts="suggested_tip_amounts",
provider_data="provider_data",
photo_url="photo_url",
photo_size="photo_size",
photo_width="photo_width",
photo_height="photo_height",
need_name="need_name",
need_phone_number="need_phone_number",
need_email="need_email",
need_shipping_address="need_shipping_address",
send_phone_number_to_provider="send_phone_number_to_provider",
send_email_to_provider="send_email_to_provider",
is_flexible="is_flexible",
)
async def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["provider_data"] == '{"test_data": 123456789}'
+4 -3
View File
@@ -35,6 +35,7 @@ from telegram.error import PassportDecryptionError
# here, although they are implicitly tested. Testing for those classes was too much work and not
# worth it.
from telegram.request import RequestData
from tests.conftest import make_bot
RAW_PASSPORT_DATA = {
"credentials": {
@@ -427,7 +428,7 @@ class TestPassport:
Bot(bot.token, private_key=b"Invalid key!")
async def test_passport_data_okay_with_non_crypto_bot(self, bot):
async with Bot(bot.token) as b:
async with make_bot(token=bot.token) as b:
assert PassportData.de_json(RAW_PASSPORT_DATA, bot=b)
def test_wrong_hash(self, bot):
@@ -439,13 +440,13 @@ class TestPassport:
async def test_wrong_key(self, bot):
short_key = b"-----BEGIN RSA PRIVATE KEY-----\r\nMIIBOQIBAAJBAKU+OZ2jJm7sCA/ec4gngNZhXYPu+DZ/TAwSMl0W7vAPXAsLplBk\r\nO8l6IBHx8N0ZC4Bc65mO3b2G8YAzqndyqH8CAwEAAQJAWOx3jQFzeVXDsOaBPdAk\r\nYTncXVeIc6tlfUl9mOLyinSbRNCy1XicOiOZFgH1rRKOGIC1235QmqxFvdecySoY\r\nwQIhAOFeGgeX9CrEPuSsd9+kqUcA2avCwqdQgSdy2qggRFyJAiEAu7QHT8JQSkHU\r\nDELfzrzc24AhjyG0z1DpGZArM8COascCIDK42SboXj3Z2UXiQ0CEcMzYNiVgOisq\r\nBUd5pBi+2mPxAiAM5Z7G/Sv1HjbKrOGh29o0/sXPhtpckEuj5QMC6E0gywIgFY6S\r\nNjwrAA+cMmsgY0O2fAzEKkDc5YiFsiXaGaSS4eA=\r\n-----END RSA PRIVATE KEY-----"
async with Bot(bot.token, private_key=short_key) as b:
async with make_bot(token=bot.token, private_key=short_key) as b:
passport_data = PassportData.de_json(RAW_PASSPORT_DATA, bot=b)
with pytest.raises(PassportDecryptionError):
assert passport_data.decrypted_data
wrong_key = b"-----BEGIN RSA PRIVATE KEY-----\r\nMIIEogIBAAKCAQB4qCFltuvHakZze86TUweU7E/SB3VLGEHAe7GJlBmrou9SSWsL\r\nH7E++157X6UqWFl54LOE9MeHZnoW7rZ+DxLKhk6NwAHTxXPnvw4CZlvUPC3OFxg3\r\nhEmNen6ojSM4sl4kYUIa7F+Q5uMEYaboxoBen9mbj4zzMGsG4aY/xBOb2ewrXQyL\r\nRh//tk1Px4ago+lUPisAvQVecz7/6KU4Xj4Lpv2z20f3cHlZX6bb7HlE1vixCMOf\r\nxvfC5SkWEGZMR/ZoWQUsoDkrDSITF/S3GtLfg083TgtCKaOF3mCT27sJ1og77npP\r\n0cH/qdlbdoFtdrRj3PvBpaj/TtXRhmdGcJBxAgMBAAECggEAYSq1Sp6XHo8dkV8B\r\nK2/QSURNu8y5zvIH8aUrgqo8Shb7OH9bryekrB3vJtgNwR5JYHdu2wHttcL3S4SO\r\nftJQxbyHgmxAjHUVNGqOM6yPA0o7cR70J7FnMoKVgdO3q68pVY7ll50IET9/T0X9\r\nDrTdKFb+/eILFsXFS1NpeSzExdsKq3zM0sP/vlJHHYVTmZDGaGEvny/eLAS+KAfG\r\nrKP96DeO4C/peXEJzALZ/mG1ReBB05Qp9Dx1xEC20yreRk5MnnBA5oiHVG5ZLOl9\r\nEEHINidqN+TMNSkxv67xMfQ6utNu5IpbklKv/4wqQOJOO50HZ+qBtSurTN573dky\r\nzslbCQKBgQDHDUBYyKN/v69VLmvNVcxTgrOcrdbqAfefJXb9C3dVXhS8/oRkCRU/\r\ndzxYWNT7hmQyWUKor/izh68rZ/M+bsTnlaa7IdAgyChzTfcZL/2pxG9pq05GF1Q4\r\nBSJ896ZEe3jEhbpJXRlWYvz7455svlxR0H8FooCTddTmkU3nsQSx0wKBgQCbLSa4\r\nyZs2QVstQQerNjxAtLi0IvV8cJkuvFoNC2Q21oqQc7BYU7NJL7uwriprZr5nwkCQ\r\nOFQXi4N3uqimNxuSng31ETfjFZPp+pjb8jf7Sce7cqU66xxR+anUzVZqBG1CJShx\r\nVxN7cWN33UZvIH34gA2Ax6AXNnJG42B5Gn1GKwKBgQCZ/oh/p4nGNXfiAK3qB6yy\r\nFvX6CwuvsqHt/8AUeKBz7PtCU+38roI/vXF0MBVmGky+HwxREQLpcdl1TVCERpIT\r\nUFXThI9OLUwOGI1IcTZf9tby+1LtKvM++8n4wGdjp9qAv6ylQV9u09pAzZItMwCd\r\nUx5SL6wlaQ2y60tIKk0lfQKBgBJS+56YmA6JGzY11qz+I5FUhfcnpauDNGOTdGLT\r\n9IqRPR2fu7RCdgpva4+KkZHLOTLReoRNUojRPb4WubGfEk93AJju5pWXR7c6k3Bt\r\novS2mrJk8GQLvXVksQxjDxBH44sLDkKMEM3j7uYJqDaZNKbyoCWT7TCwikAau5qx\r\naRevAoGAAKZV705dvrpJuyoHFZ66luANlrAwG/vNf6Q4mBEXB7guqMkokCsSkjqR\r\nhsD79E6q06zA0QzkLCavbCn5kMmDS/AbA80+B7El92iIN6d3jRdiNZiewkhlWhEG\r\nm4N0gQRfIu+rUjsS/4xk8UuQUT/Ossjn/hExi7ejpKdCc7N++bc=\r\n-----END RSA PRIVATE KEY-----"
async with Bot(bot.token, private_key=wrong_key) as b:
async with make_bot(token=bot.token, private_key=short_key) as b:
passport_data = PassportData.de_json(RAW_PASSPORT_DATA, bot=b)
with pytest.raises(PassportDecryptionError):
assert passport_data.decrypted_data
+3 -2
View File
@@ -24,9 +24,10 @@ from pathlib import Path
import pytest
from telegram import Bot, Chat, Message, TelegramObject, Update, User
from telegram import Chat, Message, TelegramObject, Update, User
from telegram.ext import ContextTypes, PersistenceInput, PicklePersistence
from telegram.warnings import PTBUserWarning
from tests.conftest import make_bot
@pytest.fixture(autouse=True)
@@ -875,7 +876,7 @@ class TestPicklePersistence:
# Now test that pickling of unknown bots in TelegramObjects will be replaced by None-
assert not len(recwarn)
data_with_bot = {}
async with Bot(bot.token) as other_bot:
async with make_bot(token=bot.token) as other_bot:
data_with_bot["unknown_bot_in_user"] = User(1, "Dev", False, bot=other_bot)
await pickle_persistence.update_chat_data(12345, data_with_bot)
assert len(recwarn) == 1
+27 -1
View File
@@ -23,7 +23,7 @@ from pathlib import Path
import pytest
from flaky import flaky
from telegram import Audio, Bot, MaskPosition, PhotoSize, Sticker, StickerSet
from telegram import Audio, Bot, File, MaskPosition, PhotoSize, Sticker, StickerSet
from telegram.error import BadRequest, TelegramError
from telegram.request import RequestData
from tests.conftest import (
@@ -91,6 +91,8 @@ class TestSticker:
sticker_file_id = "5a3128a4d2a04750b5b58397f3b5e812"
sticker_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e"
premium_animation = File("this_is_an_id", "this_is_an_unique_id")
def test_slot_behaviour(self, sticker, mro_slots, recwarn):
for attr in sticker.__slots__:
assert getattr(sticker, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -118,6 +120,8 @@ class TestSticker:
assert sticker.thumb.width == self.thumb_width
assert sticker.thumb.height == self.thumb_height
assert sticker.thumb.file_size == self.thumb_file_size
# we need to be a premium TG user to send a premium sticker, so the below is not tested
# assert sticker.premium_animation == self.premium_animation
@flaky(3, 1)
async def test_send_all_args(self, bot, chat_id, sticker_file, sticker):
@@ -135,6 +139,8 @@ class TestSticker:
assert message.sticker.is_animated == sticker.is_animated
assert message.sticker.is_video == sticker.is_video
assert message.sticker.file_size == sticker.file_size
# we need to be a premium TG user to send a premium sticker, so the below is not tested
# assert message.sticker.premium_animation == sticker.premium_animation
assert isinstance(message.sticker.thumb, PhotoSize)
assert isinstance(message.sticker.thumb.file_id, str)
@@ -212,6 +218,7 @@ class TestSticker:
"thumb": sticker.thumb.to_dict(),
"emoji": self.emoji,
"file_size": self.file_size,
"premium_animation": self.premium_animation.to_dict(),
}
json_sticker = Sticker.de_json(json_dict, bot)
@@ -224,6 +231,7 @@ class TestSticker:
assert json_sticker.emoji == self.emoji
assert json_sticker.file_size == self.file_size
assert json_sticker.thumb == sticker.thumb
assert json_sticker.premium_animation == self.premium_animation
async def test_send_with_sticker(self, monkeypatch, bot, chat_id, sticker):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
@@ -317,6 +325,24 @@ class TestSticker:
with pytest.raises(TypeError):
await bot.send_sticker(chat_id)
@flaky(3, 1)
async def test_premium_animation(self, bot):
# testing animation sucks a bit since we can't create a premium sticker. What we can do is
# get a sticker set which includes a premium sticker and check that specific one.
premium_sticker_set = await bot.get_sticker_set("Flame")
# the first one to appear here is a sticker with unique file id of AQADOBwAAifPOElr
# this could change in the future ofc.
premium_sticker = premium_sticker_set.stickers[20]
assert premium_sticker.premium_animation.file_unique_id == "AQADOBwAAifPOElr"
assert isinstance(premium_sticker.premium_animation.file_id, str)
assert premium_sticker.premium_animation.file_id != ""
premium_sticker_dict = {
"file_unique_id": "AQADOBwAAifPOElr",
"file_id": premium_sticker.premium_animation.file_id,
"file_size": premium_sticker.premium_animation.file_size,
}
assert premium_sticker.premium_animation.to_dict() == premium_sticker_dict
def test_equality(self, sticker):
a = Sticker(
sticker.file_id,
+29 -4
View File
@@ -34,6 +34,7 @@ from telegram.request import HTTPXRequest
from tests.conftest import (
DictBot,
data_file,
make_bot,
make_message,
make_message_update,
send_webhook_message,
@@ -83,7 +84,7 @@ class TestUpdater:
async def initialize_bot(*args, **kwargs):
self.test_flag = True
async with Bot(bot.token) as test_bot:
async with make_bot(token=bot.token) as test_bot:
monkeypatch.setattr(test_bot, "initialize", initialize_bot)
updater = Updater(bot=test_bot, update_queue=asyncio.Queue())
@@ -95,7 +96,7 @@ class TestUpdater:
async def shutdown_bot(*args, **kwargs):
self.test_flag = True
async with Bot(bot.token) as test_bot:
async with make_bot(token=bot.token) as test_bot:
monkeypatch.setattr(test_bot, "shutdown", shutdown_bot)
updater = Updater(bot=test_bot, update_queue=asyncio.Queue())
@@ -503,7 +504,10 @@ class TestUpdater:
@pytest.mark.parametrize("ext_bot", [True, False])
@pytest.mark.parametrize("drop_pending_updates", (True, False))
async def test_webhook_basic(self, monkeypatch, updater, drop_pending_updates, ext_bot):
@pytest.mark.parametrize("secret_token", ["SecretToken", None])
async def test_webhook_basic(
self, monkeypatch, updater, drop_pending_updates, ext_bot, secret_token
):
# Testing with both ExtBot and Bot to make sure any logic in WebhookHandler
# that depends on this distinction works
if ext_bot and not isinstance(updater.bot, ExtBot):
@@ -532,13 +536,16 @@ class TestUpdater:
ip_address=ip,
port=port,
url_path="TOKEN",
secret_token=secret_token,
)
assert return_value is updater.update_queue
assert updater.running
# Now, we send an update to the server
update = make_message_update("Webhook")
await send_webhook_message(ip, port, update.to_json(), "TOKEN")
await send_webhook_message(
ip, port, update.to_json(), "TOKEN", secret_token=secret_token
)
assert (await updater.update_queue.get()).to_dict() == update.to_dict()
# Returns Not Found if path is incorrect
@@ -549,6 +556,22 @@ class TestUpdater:
response = await send_webhook_message(ip, port, None, "TOKEN", get_method="HEAD")
assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED
if secret_token:
# Returns Forbidden if no secret token is set
response_text = "<html><title>403: {0}</title><body>403: {0}</body></html>"
response = await send_webhook_message(ip, port, update.to_json(), "TOKEN")
assert response.status_code == HTTPStatus.FORBIDDEN
assert response.text == response_text.format(
"Request did not include the secret token"
)
# Returns Forbidden if the secret token is wrong
response = await send_webhook_message(
ip, port, update.to_json(), "TOKEN", secret_token="NotTheSecretToken"
)
assert response.status_code == HTTPStatus.FORBIDDEN
assert response.text == response_text.format("Request had the wrong secret token")
await updater.stop()
assert not updater.running
@@ -599,6 +622,7 @@ class TestUpdater:
max_connections=40,
allowed_updates=None,
ip_address=None,
secret_token=None,
**expected_delete_webhook,
)
@@ -640,6 +664,7 @@ class TestUpdater:
max_connections=47,
allowed_updates=["message"],
ip_address="123.456.789",
secret_token=None,
**expected_delete_webhook,
)
+12
View File
@@ -35,6 +35,8 @@ def json_dict():
"can_join_groups": TestUser.can_join_groups,
"can_read_all_group_messages": TestUser.can_read_all_group_messages,
"supports_inline_queries": TestUser.supports_inline_queries,
"is_premium": TestUser.is_premium,
"added_to_attachment_menu": TestUser.added_to_attachment_menu,
}
@@ -51,6 +53,8 @@ def user(bot):
can_read_all_group_messages=TestUser.can_read_all_group_messages,
supports_inline_queries=TestUser.supports_inline_queries,
bot=bot,
is_premium=TestUser.is_premium,
added_to_attachment_menu=TestUser.added_to_attachment_menu,
)
@@ -64,6 +68,8 @@ class TestUser:
can_join_groups = True
can_read_all_group_messages = True
supports_inline_queries = False
is_premium = True
added_to_attachment_menu = False
def test_slot_behaviour(self, user, mro_slots):
for attr in user.__slots__:
@@ -82,6 +88,8 @@ class TestUser:
assert user.can_join_groups == self.can_join_groups
assert user.can_read_all_group_messages == self.can_read_all_group_messages
assert user.supports_inline_queries == self.supports_inline_queries
assert user.is_premium == self.is_premium
assert user.added_to_attachment_menu == self.added_to_attachment_menu
def test_de_json_without_username(self, json_dict, bot):
del json_dict["username"]
@@ -97,6 +105,8 @@ class TestUser:
assert user.can_join_groups == self.can_join_groups
assert user.can_read_all_group_messages == self.can_read_all_group_messages
assert user.supports_inline_queries == self.supports_inline_queries
assert user.is_premium == self.is_premium
assert user.added_to_attachment_menu == self.added_to_attachment_menu
def test_de_json_without_username_and_last_name(self, json_dict, bot):
del json_dict["username"]
@@ -113,6 +123,8 @@ class TestUser:
assert user.can_join_groups == self.can_join_groups
assert user.can_read_all_group_messages == self.can_read_all_group_messages
assert user.supports_inline_queries == self.supports_inline_queries
assert user.is_premium == self.is_premium
assert user.added_to_attachment_menu == self.added_to_attachment_menu
def test_name(self, user):
assert user.name == "@username"