mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-26 19:25:16 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df07148e2d | |||
| 2d2cede442 | |||
| 08e223ba90 | |||
| 01d643913e | |||
| 755945172d | |||
| 24b4de9f10 | |||
| 76bfe8ceff | |||
| b498786d7c | |||
| bfe30048e8 |
@@ -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$
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -10,5 +10,4 @@
|
||||
State Diagram
|
||||
-------------
|
||||
|
||||
.. image:: ../../examples/conversationbot.png
|
||||
|
||||
.. mermaid:: ../../examples/conversationbot.mmd
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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
@@ -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`"""
|
||||
|
||||
@@ -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,
|
||||
*,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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>`_.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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,)
|
||||
|
||||
@@ -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
@@ -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."""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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}'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user