mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-21 08:35:28 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9dd2b1746 | |||
| cb95868c4a | |||
| 2f6c4075c8 | |||
| c53dc7dae4 | |||
| 8b3ea5ce56 | |||
| 51a4a6664c | |||
| e4dc80f41d | |||
| bc7c422a11 | |||
| c3e3bb77e5 | |||
| a25c76e6a3 | |||
| 0c5085022c | |||
| 1fdaaac809 |
@@ -0,0 +1,76 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: ["bug :bug:"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting issues of python-telegram-bot!
|
||||
|
||||
Use this template to notify us if you found a bug.
|
||||
|
||||
To make it easier for us to help you please enter detailed information below.
|
||||
|
||||
Please note, we only support the latest version of python-telegram-bot and master branch. Please make sure to upgrade & recreate the issue on the latest version prior to opening an issue.
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behaviour
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: Tell us what should happen
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual-behaviour
|
||||
attributes:
|
||||
label: Actual behaviour
|
||||
description: Tell us what happens instead
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Configuration"
|
||||
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: Operating System
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: versions
|
||||
attributes:
|
||||
label: Version of Python, python-telegram-bot & dependencies
|
||||
description: Paste the output of `$ python -m telegram` here. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Insert logs here (if necessary). This will be automatically formatted into code, so no need for backticks.
|
||||
render: python
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: You may provide any other additional context to the bug here.
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: 'bug :bug:'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for reporting issues of python-telegram-bot!
|
||||
|
||||
Use this template to notify us if you found a bug.
|
||||
|
||||
To make it easier for us to help you please enter detailed information below.
|
||||
|
||||
Please note, we only support the latest version of python-telegram-bot and
|
||||
master branch. Please make sure to upgrade & recreate the issue on the latest
|
||||
version prior to opening an issue.
|
||||
-->
|
||||
### Steps to reproduce
|
||||
1.
|
||||
|
||||
2.
|
||||
|
||||
3.
|
||||
|
||||
### Expected behaviour
|
||||
Tell us what should happen
|
||||
|
||||
### Actual behaviour
|
||||
Tell us what happens instead
|
||||
|
||||
### Configuration
|
||||
**Operating System:**
|
||||
|
||||
|
||||
**Version of Python, python-telegram-bot & dependencies:**
|
||||
|
||||
``$ python -m telegram``
|
||||
|
||||
### Logs
|
||||
Insert logs here (if necessary)
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: ["enhancement"]
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
id: related-problem
|
||||
attributes:
|
||||
label: "What kind of feature are you missing? Where do you notice a shortcoming of PTB?"
|
||||
description: "A clear and concise description of what the problem is."
|
||||
placeholder: "Example: I want to do X, but there is no way to do it."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: "Describe the solution you'd like"
|
||||
description: "A clear and concise description of what you want to happen."
|
||||
placeholder: "Example: I think it would be nice if you would add feature Y so I can do X."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: "Describe alternatives you've considered"
|
||||
description: "A clear and concise description of any alternative solutions or features you've considered."
|
||||
placeholder: "Example: I considered Z to be able to do X, but that didn't work because..."
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: "Additional context"
|
||||
description: "Add any other context or screenshots about the feature request here."
|
||||
placeholder: "Example: Here's a photo of my cat!"
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
#### Is your feature request related to a problem? Please describe.
|
||||
A clear and concise description of what the problem is.
|
||||
Ex. *I want to do X, but there is no way to do it.*
|
||||
|
||||
#### Describe the solution you'd like
|
||||
A clear and concise description of what you want to happen.
|
||||
Ex. *I think it would be nice if you would add feature Y so it will make it easier.*
|
||||
|
||||
#### Describe alternatives you've considered
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
Ex. *I considered Z, but that didn't work because...*
|
||||
|
||||
#### Additional context
|
||||
Add any other context or screenshots about the feature request here.
|
||||
Ex. *Here's a photo of my cat!*
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Get help with errors or general questions
|
||||
title: "[QUESTION]"
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Hey there, you have a question? We are happy to answer. Please make sure no similar question was opened already.
|
||||
|
||||
To make it easier for us to help you, please read this article https://git.io/JURJO and try to follow the template below as closely as possible.
|
||||
|
||||
Please mind that there is also a users' Telegram group at https://t.me/pythontelegrambotgroup for questions about the library. Questions asked there might be answered quicker than here. Moreover, GitHub Discussions at https://git.io/JG3rk offer a slightly better format to discuss usage questions.
|
||||
-->
|
||||
|
||||
### Issue I am facing
|
||||
Please describe the issue here in as much detail as possible
|
||||
|
||||
### Traceback to the issue
|
||||
```
|
||||
put it here
|
||||
```
|
||||
|
||||
### Related part of your code
|
||||
```python
|
||||
put it here
|
||||
```
|
||||
@@ -0,0 +1,68 @@
|
||||
name: Question
|
||||
description: Get help with errors or general questions
|
||||
title: "[QUESTION]"
|
||||
labels: ["question"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Hey there, you have a question? We are happy to answer. Please make sure no similar question was opened already.
|
||||
|
||||
To make it easier for us to help you, please read this [article](https://git.io/JURJO).
|
||||
|
||||
Please mind that there is also a users' [Telegram group](https://t.me/pythontelegrambotgroup) for questions about the library. Questions asked there might be answered quicker than here. Moreover, [GitHub Discussions](https://git.io/JG3rk) offer a slightly better format to discuss usage questions.
|
||||
|
||||
- type: textarea
|
||||
id: issue-faced
|
||||
attributes:
|
||||
label: "Issue I am facing"
|
||||
description: "Please describe the issue here in as much detail as possible"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: traceback
|
||||
attributes:
|
||||
label: "Traceback to the issue"
|
||||
description: "If you are facing a specific error message, please paste the traceback here. This will be automatically formatted into python code, so no need for backticks."
|
||||
placeholder: |
|
||||
Traceback (most recent call last):
|
||||
File "/home/bot.py", line 1, in main
|
||||
foo = bar()
|
||||
...
|
||||
telegram.error.BadRequest: Traceback not found
|
||||
render: python
|
||||
|
||||
- type: textarea
|
||||
id: related-code
|
||||
attributes:
|
||||
label: "Related part of your code"
|
||||
description: "This will be automatically formatted into code (python), so no need for backticks."
|
||||
placeholder: |
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
render: python
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Configuration"
|
||||
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: Operating System
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: versions
|
||||
attributes:
|
||||
label: Version of Python, python-telegram-bot & dependencies
|
||||
description: Paste the output of `$ python -m telegram` here. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
@@ -7,6 +7,7 @@ Hey! You're PRing? Cool! Please have a look at the below checklist. It's here to
|
||||
- [ ] Added `.. versionadded:: version`, `.. versionchanged:: version` or `.. deprecated:: version` to the docstrings for user facing changes (for methods/class descriptions, arguments and attributes)
|
||||
- [ ] Created new or adapted existing unit tests
|
||||
- [ ] Added myself alphabetically to `AUTHORS.rst` (optional)
|
||||
- [ ] Added new classes & modules to the docs
|
||||
|
||||
|
||||
### If the PR contains API changes (otherwise, you can delete this passage)
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
- uses: dessant/lock-threads@v2.0.1
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: '1'
|
||||
issue-lock-inactive-days: '7'
|
||||
issue-lock-reason: ''
|
||||
pr-lock-inactive-days: '1'
|
||||
pr-lock-inactive-days: '7'
|
||||
pr-lock-reason: ''
|
||||
|
||||
@@ -26,6 +26,7 @@ Contributors
|
||||
|
||||
The following wonderful people contributed directly or indirectly to this project:
|
||||
|
||||
- `Abshar <https://github.com/abxhr>`_
|
||||
- `Alateas <https://github.com/alateas>`_
|
||||
- `Ales Dokshanin <https://github.com/alesdokshanin>`_
|
||||
- `Ambro17 <https://github.com/Ambro17>`_
|
||||
@@ -39,6 +40,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `daimajia <https://github.com/daimajia>`_
|
||||
- `Daniel Reed <https://github.com/nmlorg>`_
|
||||
- `D David Livingston <https://github.com/daviddl9>`_
|
||||
- `DonalDuck004 <https://github.com/DonalDuck004>`_
|
||||
- `Eana Hufwe <https://github.com/blueset>`_
|
||||
- `Ehsan Online <https://github.com/ehsanonline>`_
|
||||
- `Eli Gao <https://github.com/eligao>`_
|
||||
@@ -84,6 +86,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Oleg Sushchenko <https://github.com/feuillemorte>`_
|
||||
- `Or Bin <https://github.com/OrBin>`_
|
||||
- `overquota <https://github.com/overquota>`_
|
||||
- `Paradox <https://github.com/paradox70>`_
|
||||
- `Patrick Hofmann <https://github.com/PH89>`_
|
||||
- `Paul Larsen <https://github.com/PaulSonOfLars>`_
|
||||
- `Pieter Schutz <https://github.com/eldinnie>`_
|
||||
|
||||
+54
@@ -2,6 +2,60 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Version 13.9
|
||||
============
|
||||
*Released 2021-12-11*
|
||||
|
||||
This is the technical changelog for version 13.9. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Full Support for Api 5.5 (`#2809`_)
|
||||
|
||||
**Minor Changes**
|
||||
|
||||
- Adjust Automated Locking of Inactive Issues (`#2775`_)
|
||||
|
||||
.. _`#2809`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2809
|
||||
.. _`#2775`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2775
|
||||
|
||||
Version 13.8.1
|
||||
==============
|
||||
*Released 2021-11-08*
|
||||
|
||||
This is the technical changelog for version 13.8.1. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
|
||||
|
||||
**Doc fixes:**
|
||||
|
||||
- Add ``ChatJoinRequest(Handler)`` to Docs (`#2771`_)
|
||||
|
||||
.. _`#2771`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2771
|
||||
|
||||
Version 13.8
|
||||
============
|
||||
*Released 2021-11-08*
|
||||
|
||||
This is the technical changelog for version 13.8. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Full support for API 5.4 (`#2767`_)
|
||||
|
||||
**Minor changes, CI improvements, Doc fixes and Type hinting:**
|
||||
|
||||
- Create Issue Template Forms (`#2689`_)
|
||||
- Fix ``camelCase`` Functions in ``ExtBot`` (`#2659`_)
|
||||
- Fix Empty Captions not Being Passed by ``Bot.copy_message`` (`#2651`_)
|
||||
- Fix Setting Thumbs When Uploading A Single File (`#2583`_)
|
||||
- Fix Bug in ``BasePersistence.insert``/``replace_bot`` for Objects with ``__dict__`` not in ``__slots__`` (`#2603`_)
|
||||
|
||||
.. _`#2767`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2767
|
||||
.. _`#2689`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2689
|
||||
.. _`#2659`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2659
|
||||
.. _`#2651`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2651
|
||||
.. _`#2583`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2583
|
||||
.. _`#2603`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2603
|
||||
|
||||
Version 13.7
|
||||
============
|
||||
*Released 2021-07-01*
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
..
|
||||
Make user to apply any changes to this file to README_RAW.rst as well!
|
||||
Make sure to apply any changes to this file to README_RAW.rst as well!
|
||||
|
||||
.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-logo-text_768.png?raw=true
|
||||
:align: center
|
||||
@@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
|
||||
:target: https://pypi.org/project/python-telegram-bot/
|
||||
:alt: Supported Python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-5.3-blue?logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-5.5-blue?logo=telegram
|
||||
:target: https://core.telegram.org/bots/api-changelog
|
||||
:alt: Supported Bot API versions
|
||||
|
||||
@@ -111,7 +111,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 **5.3** are supported.
|
||||
All types and methods of the Telegram Bot API **5.5** are supported.
|
||||
|
||||
==========
|
||||
Installing
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
..
|
||||
Make user to apply any changes to this file to README.rst as well!
|
||||
Make sure to apply any changes to this file to README.rst as well!
|
||||
|
||||
.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-raw-logo-text_768.png?raw=true
|
||||
:align: center
|
||||
@@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
|
||||
:target: https://pypi.org/project/python-telegram-bot-raw/
|
||||
:alt: Supported Python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-5.3-blue?logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-5.5-blue?logo=telegram
|
||||
:target: https://core.telegram.org/bots/api-changelog
|
||||
:alt: Supported Bot API versions
|
||||
|
||||
@@ -105,7 +105,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 **5.3** are supported.
|
||||
All types and methods of the Telegram Bot API **5.5** are supported.
|
||||
|
||||
==========
|
||||
Installing
|
||||
|
||||
+2
-2
@@ -60,9 +60,9 @@ author = u'Leandro Toledo'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '13.7' # telegram.__version__[:3]
|
||||
version = '13.9' # telegram.__version__[:3]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '13.7' # telegram.__version__
|
||||
release = '13.9' # telegram.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/chatjoinrequest.py
|
||||
|
||||
telegram.ChatJoinRequest
|
||||
========================
|
||||
|
||||
.. autoclass:: telegram.ChatJoinRequest
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/chatjoinrequesthandler.py
|
||||
|
||||
telegram.ext.ChatJoinRequestHandler
|
||||
===================================
|
||||
|
||||
.. autoclass:: telegram.ext.ChatJoinRequestHandler
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -22,8 +22,9 @@ Handlers
|
||||
|
||||
telegram.ext.handler
|
||||
telegram.ext.callbackqueryhandler
|
||||
telegram.ext.choseninlineresulthandler
|
||||
telegram.ext.chatjoinrequesthandler
|
||||
telegram.ext.chatmemberhandler
|
||||
telegram.ext.choseninlineresulthandler
|
||||
telegram.ext.commandhandler
|
||||
telegram.ext.conversationhandler
|
||||
telegram.ext.inlinequeryhandler
|
||||
|
||||
@@ -19,6 +19,7 @@ telegram package
|
||||
telegram.chat
|
||||
telegram.chataction
|
||||
telegram.chatinvitelink
|
||||
telegram.chatjoinrequest
|
||||
telegram.chatlocation
|
||||
telegram.chatmember
|
||||
telegram.chatmemberowner
|
||||
|
||||
@@ -60,7 +60,7 @@ ignore_errors = True
|
||||
|
||||
# Disable strict optional for telegram objects with class methods
|
||||
# We don't want to clutter the code with 'if self.bot is None: raise RuntimeError()'
|
||||
[mypy-telegram.callbackquery,telegram.chat,telegram.message,telegram.user,telegram.files.*,telegram.inline.inlinequery,telegram.payment.precheckoutquery,telegram.payment.shippingquery,telegram.passport.passportdata,telegram.passport.credentials,telegram.passport.passportfile,telegram.ext.filters]
|
||||
[mypy-telegram.callbackquery,telegram.chat,telegram.message,telegram.user,telegram.files.*,telegram.inline.inlinequery,telegram.payment.precheckoutquery,telegram.payment.shippingquery,telegram.passport.passportdata,telegram.passport.credentials,telegram.passport.passportfile,telegram.ext.filters,telegram.chatjoinrequest]
|
||||
strict_optional = False
|
||||
|
||||
# type hinting for asyncio in webhookhandler is a bit tricky because it depends on the OS
|
||||
|
||||
@@ -25,6 +25,7 @@ from .files.chatphoto import ChatPhoto
|
||||
from .chat import Chat
|
||||
from .chatlocation import ChatLocation
|
||||
from .chatinvitelink import ChatInviteLink
|
||||
from .chatjoinrequest import ChatJoinRequest
|
||||
from .chatmember import (
|
||||
ChatMember,
|
||||
ChatMemberOwner,
|
||||
@@ -194,6 +195,7 @@ __all__ = ( # Keep this alphabetically ordered
|
||||
'Chat',
|
||||
'ChatAction',
|
||||
'ChatInviteLink',
|
||||
'ChatJoinRequest',
|
||||
'ChatLocation',
|
||||
'ChatMember',
|
||||
'ChatMemberOwner',
|
||||
|
||||
+219
-5
@@ -579,6 +579,14 @@ class Bot(TelegramObject):
|
||||
) -> Message:
|
||||
"""Use this method to forward messages of any kind. Service messages can't be forwarded.
|
||||
|
||||
Note:
|
||||
Since the release of Bot API 5.5 it can be impossible to forward messages from
|
||||
some chats. Use the attributes :attr:`telegram.Message.has_protected_content` and
|
||||
:attr:`telegram.Chat.has_protected_content` to check this.
|
||||
|
||||
As a workaround, it is still possible to use :meth:`copy_message`. However, this
|
||||
behaviour is undocumented and might be changed by Telegram.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target channel (in the format ``@channelusername``).
|
||||
@@ -2408,6 +2416,45 @@ class Bot(TelegramObject):
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@log
|
||||
def ban_chat_sender_chat(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
sender_chat_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Use this method to ban a channel chat in a supergroup or a channel. Until the chat is
|
||||
unbanned, the owner of the banned chat won't be able to send messages on behalf of **any of
|
||||
their channels**. The bot must be an administrator in the supergroup or channel for this
|
||||
to work and must have the appropriate administrator rights.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target group or username
|
||||
of the target supergroup or channel (in the format ``@channelusername``).
|
||||
sender_chat_id (:obj:`int`): Unique identifier of the target sender chat.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
data: JSONDict = {'chat_id': chat_id, 'sender_chat_id': sender_chat_id}
|
||||
|
||||
result = self._post('banChatSenderChat', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@log
|
||||
def unban_chat_member(
|
||||
self,
|
||||
@@ -2437,7 +2484,7 @@ class Bot(TelegramObject):
|
||||
Telegram API.
|
||||
|
||||
Returns:
|
||||
:obj:`bool` On success, :obj:`True` is returned.
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
@@ -2452,6 +2499,43 @@ class Bot(TelegramObject):
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@log
|
||||
def unban_chat_sender_chat(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
sender_chat_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Use this method to unban a previously banned channel in a supergroup or channel.
|
||||
The bot must be an administrator for this to work and must have the
|
||||
appropriate administrator rights.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target supergroup or channel (in the format ``@channelusername``).
|
||||
sender_chat_id (:obj:`int`): Unique identifier of the target sender chat.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
data: JSONDict = {'chat_id': chat_id, 'sender_chat_id': sender_chat_id}
|
||||
|
||||
result = self._post('unbanChatSenderChat', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@log
|
||||
def answer_callback_query(
|
||||
self,
|
||||
@@ -3985,6 +4069,8 @@ class Bot(TelegramObject):
|
||||
member_limit: int = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
name: str = None,
|
||||
creates_join_request: bool = None,
|
||||
) -> ChatInviteLink:
|
||||
"""
|
||||
Use this method to create an additional invite link for a chat. The bot must be an
|
||||
@@ -4007,6 +4093,14 @@ class Bot(TelegramObject):
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
name (:obj:`str`, optional): Invite link name; 0-32 characters.
|
||||
|
||||
.. 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.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ChatInviteLink`
|
||||
@@ -4015,6 +4109,11 @@ class Bot(TelegramObject):
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
if creates_join_request and member_limit:
|
||||
raise ValueError(
|
||||
"If `creates_join_request` is `True`, `member_limit` can't be specified."
|
||||
)
|
||||
|
||||
data: JSONDict = {
|
||||
'chat_id': chat_id,
|
||||
}
|
||||
@@ -4029,6 +4128,12 @@ class Bot(TelegramObject):
|
||||
if member_limit is not None:
|
||||
data['member_limit'] = member_limit
|
||||
|
||||
if name is not None:
|
||||
data['name'] = name
|
||||
|
||||
if creates_join_request is not None:
|
||||
data['creates_join_request'] = creates_join_request
|
||||
|
||||
result = self._post('createChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type]
|
||||
@@ -4042,11 +4147,19 @@ class Bot(TelegramObject):
|
||||
member_limit: int = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
name: str = None,
|
||||
creates_join_request: bool = None,
|
||||
) -> ChatInviteLink:
|
||||
"""
|
||||
Use this method to edit a non-primary invite link created by the bot. The bot must be an
|
||||
administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
|
||||
Note:
|
||||
Though not stated explicitly in the official docs, Telegram changes not only the
|
||||
optional parameters that are explicitly passed, but also replaces all other optional
|
||||
parameters to the default values. However, since not documented, this behaviour may
|
||||
change unbeknown to PTB.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
|
||||
Args:
|
||||
@@ -4064,6 +4177,14 @@ class Bot(TelegramObject):
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
name (:obj:`str`, optional): Invite link name; 0-32 characters.
|
||||
|
||||
.. 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.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ChatInviteLink`
|
||||
@@ -4072,6 +4193,11 @@ class Bot(TelegramObject):
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
if creates_join_request and member_limit:
|
||||
raise ValueError(
|
||||
"If `creates_join_request` is `True`, `member_limit` can't be specified."
|
||||
)
|
||||
|
||||
data: JSONDict = {'chat_id': chat_id, 'invite_link': invite_link}
|
||||
|
||||
if expire_date is not None:
|
||||
@@ -4084,6 +4210,12 @@ class Bot(TelegramObject):
|
||||
if member_limit is not None:
|
||||
data['member_limit'] = member_limit
|
||||
|
||||
if name is not None:
|
||||
data['name'] = name
|
||||
|
||||
if creates_join_request is not None:
|
||||
data['creates_join_request'] = creates_join_request
|
||||
|
||||
result = self._post('editChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type]
|
||||
@@ -4126,6 +4258,80 @@ class Bot(TelegramObject):
|
||||
|
||||
return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type]
|
||||
|
||||
@log
|
||||
def approve_chat_join_request(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
user_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Use this method to approve a chat join request.
|
||||
|
||||
The bot must be an administrator in the chat for this to work and must have the
|
||||
:attr:`telegram.ChatPermissions.can_invite_users` administrator right.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target channel (in the format ``@channelusername``).
|
||||
user_id (:obj:`int`): Unique identifier of the target user.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
"""
|
||||
data: JSONDict = {'chat_id': chat_id, 'user_id': user_id}
|
||||
|
||||
result = self._post('approveChatJoinRequest', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@log
|
||||
def decline_chat_join_request(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
user_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Use this method to decline a chat join request.
|
||||
|
||||
The bot must be an administrator in the chat for this to work and must have the
|
||||
:attr:`telegram.ChatPermissions.can_invite_users` administrator right.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target channel (in the format ``@channelusername``).
|
||||
user_id (:obj:`int`): Unique identifier of the target user.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
"""
|
||||
data: JSONDict = {'chat_id': chat_id, 'user_id': user_id}
|
||||
|
||||
result = self._post('declineChatJoinRequest', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@log
|
||||
def set_chat_photo(
|
||||
self,
|
||||
@@ -5296,7 +5502,7 @@ class Bot(TelegramObject):
|
||||
'disable_notification': disable_notification,
|
||||
'allow_sending_without_reply': allow_sending_without_reply,
|
||||
}
|
||||
if caption:
|
||||
if caption is not None:
|
||||
data['caption'] = caption
|
||||
if caption_entities:
|
||||
data['caption_entities'] = caption_entities
|
||||
@@ -5377,10 +5583,14 @@ class Bot(TelegramObject):
|
||||
"""Alias for :meth:`get_file`"""
|
||||
banChatMember = ban_chat_member
|
||||
"""Alias for :meth:`ban_chat_member`"""
|
||||
banChatSenderChat = ban_chat_sender_chat
|
||||
"""Alias for :meth:`ban_chat_sender_chat`"""
|
||||
kickChatMember = kick_chat_member
|
||||
"""Alias for :meth:`kick_chat_member`"""
|
||||
unbanChatMember = unban_chat_member
|
||||
"""Alias for :meth:`unban_chat_member`"""
|
||||
unbanChatSenderChat = unban_chat_sender_chat
|
||||
"""Alias for :meth:`unban_chat_sender_chat`"""
|
||||
answerCallbackQuery = answer_callback_query
|
||||
"""Alias for :meth:`answer_callback_query`"""
|
||||
editMessageText = edit_message_text
|
||||
@@ -5436,11 +5646,15 @@ class Bot(TelegramObject):
|
||||
exportChatInviteLink = export_chat_invite_link
|
||||
"""Alias for :meth:`export_chat_invite_link`"""
|
||||
createChatInviteLink = create_chat_invite_link
|
||||
"""Alias for :attr:`create_chat_invite_link`"""
|
||||
"""Alias for :meth:`create_chat_invite_link`"""
|
||||
editChatInviteLink = edit_chat_invite_link
|
||||
"""Alias for :attr:`edit_chat_invite_link`"""
|
||||
"""Alias for :meth:`edit_chat_invite_link`"""
|
||||
revokeChatInviteLink = revoke_chat_invite_link
|
||||
"""Alias for :attr:`revoke_chat_invite_link`"""
|
||||
"""Alias for :meth:`revoke_chat_invite_link`"""
|
||||
approveChatJoinRequest = approve_chat_join_request
|
||||
"""Alias for :meth:`approve_chat_join_request`"""
|
||||
declineChatJoinRequest = decline_chat_join_request
|
||||
"""Alias for :meth:`decline_chat_join_request`"""
|
||||
setChatPhoto = set_chat_photo
|
||||
"""Alias for :meth:`set_chat_photo`"""
|
||||
deleteChatPhoto = delete_chat_photo
|
||||
|
||||
@@ -81,6 +81,11 @@ class Chat(TelegramObject):
|
||||
Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
bio (:obj:`str`, optional): Bio of the other party in a private chat. Returned only in
|
||||
:meth:`telegram.Bot.get_chat`.
|
||||
has_private_forwards (:obj:`bool`, optional): :obj:`True`, if privacy settings of the other
|
||||
party in the private chat allows to use ``tg://user?id=<user_id>`` links only in chats
|
||||
with the user. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
description (:obj:`str`, optional): Description, for groups, supergroups and channel chats.
|
||||
Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and
|
||||
@@ -97,6 +102,10 @@ class Chat(TelegramObject):
|
||||
:meth:`telegram.Bot.get_chat`.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
has_protected_content (:obj:`bool`, optional): :obj:`True`, if messages from the chat can't
|
||||
be forwarded to other chats. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set.
|
||||
Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
@@ -119,6 +128,11 @@ class Chat(TelegramObject):
|
||||
photo (:class:`telegram.ChatPhoto`): Optional. Chat photo.
|
||||
bio (:obj:`str`): Optional. Bio of the other party in a private chat. Returned only in
|
||||
:meth:`telegram.Bot.get_chat`.
|
||||
has_private_forwards (:obj:`bool`): Optional. :obj:`True`, if privacy settings of the other
|
||||
party in the private chat allows to use ``tg://user?id=<user_id>`` links only in chats
|
||||
with the user.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats.
|
||||
invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and
|
||||
channel. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
@@ -134,6 +148,10 @@ class Chat(TelegramObject):
|
||||
:meth:`telegram.Bot.get_chat`.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if messages from the chat can't
|
||||
be forwarded to other chats.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
|
||||
can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the
|
||||
sticker set.
|
||||
@@ -166,6 +184,8 @@ class Chat(TelegramObject):
|
||||
'linked_chat_id',
|
||||
'all_members_are_administrators',
|
||||
'message_auto_delete_time',
|
||||
'has_protected_content',
|
||||
'has_private_forwards',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
@@ -204,6 +224,8 @@ class Chat(TelegramObject):
|
||||
linked_chat_id: int = None,
|
||||
location: ChatLocation = None,
|
||||
message_auto_delete_time: int = None,
|
||||
has_private_forwards: bool = None,
|
||||
has_protected_content: bool = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
@@ -218,6 +240,7 @@ class Chat(TelegramObject):
|
||||
self.all_members_are_administrators = _kwargs.get('all_members_are_administrators')
|
||||
self.photo = photo
|
||||
self.bio = bio
|
||||
self.has_private_forwards = has_private_forwards
|
||||
self.description = description
|
||||
self.invite_link = invite_link
|
||||
self.pinned_message = pinned_message
|
||||
@@ -226,6 +249,7 @@ class Chat(TelegramObject):
|
||||
self.message_auto_delete_time = (
|
||||
int(message_auto_delete_time) if message_auto_delete_time is not None else None
|
||||
)
|
||||
self.has_protected_content = has_protected_content
|
||||
self.sticker_set_name = sticker_set_name
|
||||
self.can_set_sticker_set = can_set_sticker_set
|
||||
self.linked_chat_id = linked_chat_id
|
||||
@@ -433,6 +457,98 @@ class Chat(TelegramObject):
|
||||
revoke_messages=revoke_messages,
|
||||
)
|
||||
|
||||
def ban_sender_chat(
|
||||
self,
|
||||
sender_chat_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.ban_chat_sender_chat(chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.ban_chat_sender_chat`.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.ban_chat_sender_chat(
|
||||
chat_id=self.id, sender_chat_id=sender_chat_id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def ban_chat(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.ban_chat_sender_chat(sender_chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.ban_chat_sender_chat`.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.ban_chat_sender_chat(
|
||||
chat_id=chat_id, sender_chat_id=self.id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def unban_sender_chat(
|
||||
self,
|
||||
sender_chat_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.unban_chat_sender_chat(chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.unban_chat_sender_chat`.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.unban_chat_sender_chat(
|
||||
chat_id=self.id, sender_chat_id=sender_chat_id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def unban_chat(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.unban_chat_sender_chat(sender_chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.unban_chat_sender_chat`.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.unban_chat_sender_chat(
|
||||
chat_id=chat_id, sender_chat_id=self.id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def unban_member(
|
||||
self,
|
||||
user_id: Union[str, int],
|
||||
@@ -1524,6 +1640,8 @@ class Chat(TelegramObject):
|
||||
member_limit: int = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
name: str = None,
|
||||
creates_join_request: bool = None,
|
||||
) -> 'ChatInviteLink':
|
||||
"""Shortcut for::
|
||||
|
||||
@@ -1534,6 +1652,10 @@ class Chat(TelegramObject):
|
||||
|
||||
.. versionadded:: 13.4
|
||||
|
||||
.. versionchanged:: 13.8
|
||||
Edited signature according to the changes of
|
||||
:meth:`telegram.Bot.create_chat_invite_link`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ChatInviteLink`
|
||||
|
||||
@@ -1544,6 +1666,8 @@ class Chat(TelegramObject):
|
||||
member_limit=member_limit,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
name=name,
|
||||
creates_join_request=creates_join_request,
|
||||
)
|
||||
|
||||
def edit_invite_link(
|
||||
@@ -1553,6 +1677,8 @@ class Chat(TelegramObject):
|
||||
member_limit: int = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
name: str = None,
|
||||
creates_join_request: bool = None,
|
||||
) -> 'ChatInviteLink':
|
||||
"""Shortcut for::
|
||||
|
||||
@@ -1563,6 +1689,9 @@ class Chat(TelegramObject):
|
||||
|
||||
.. versionadded:: 13.4
|
||||
|
||||
.. versionchanged:: 13.8
|
||||
Edited signature according to the changes of :meth:`telegram.Bot.edit_chat_invite_link`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ChatInviteLink`
|
||||
|
||||
@@ -1574,6 +1703,8 @@ class Chat(TelegramObject):
|
||||
member_limit=member_limit,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
name=name,
|
||||
creates_join_request=creates_join_request,
|
||||
)
|
||||
|
||||
def revoke_invite_link(
|
||||
@@ -1598,3 +1729,49 @@ class Chat(TelegramObject):
|
||||
return self.bot.revoke_chat_invite_link(
|
||||
chat_id=self.id, invite_link=invite_link, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def approve_join_request(
|
||||
self,
|
||||
user_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.approve_chat_join_request(chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.approve_chat_join_request`.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.approve_chat_join_request(
|
||||
chat_id=self.id, user_id=user_id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def decline_join_request(
|
||||
self,
|
||||
user_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.decline_chat_join_request(chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.decline_chat_join_request`.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.decline_chat_join_request(
|
||||
chat_id=self.id, user_id=user_id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
@@ -59,6 +59,10 @@ class ChatAction:
|
||||
"""
|
||||
UPLOAD_DOCUMENT: ClassVar[str] = constants.CHATACTION_UPLOAD_DOCUMENT
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_DOCUMENT`"""
|
||||
CHOOSE_STICKER: ClassVar[str] = constants.CHATACTION_CHOOSE_STICKER
|
||||
""":const:`telegram.constants.CHOOSE_STICKER`
|
||||
|
||||
.. versionadded:: 13.8"""
|
||||
UPLOAD_PHOTO: ClassVar[str] = constants.CHATACTION_UPLOAD_PHOTO
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_PHOTO`"""
|
||||
UPLOAD_VIDEO: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO
|
||||
|
||||
@@ -46,6 +46,17 @@ class ChatInviteLink(TelegramObject):
|
||||
has been expired.
|
||||
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-99999.
|
||||
name (:obj:`str`, optional): Invite link name.
|
||||
|
||||
.. 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.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
pending_join_request_count (:obj:`int`, optional): Number of pending join requests
|
||||
created using this link.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Attributes:
|
||||
invite_link (:obj:`str`): The invite link. If the link was created by another chat
|
||||
@@ -57,6 +68,17 @@ class ChatInviteLink(TelegramObject):
|
||||
has been expired.
|
||||
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-99999.
|
||||
name (:obj:`str`): Optional. Invite link name.
|
||||
|
||||
.. 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.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
pending_join_request_count (:obj:`int`): Optional. Number of pending join requests
|
||||
created using this link.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
"""
|
||||
|
||||
@@ -67,6 +89,9 @@ class ChatInviteLink(TelegramObject):
|
||||
'is_revoked',
|
||||
'expire_date',
|
||||
'member_limit',
|
||||
'name',
|
||||
'creates_join_request',
|
||||
'pending_join_request_count',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
@@ -78,6 +103,9 @@ class ChatInviteLink(TelegramObject):
|
||||
is_revoked: bool,
|
||||
expire_date: datetime.datetime = None,
|
||||
member_limit: int = None,
|
||||
name: str = None,
|
||||
creates_join_request: bool = None,
|
||||
pending_join_request_count: int = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
@@ -89,7 +117,11 @@ class ChatInviteLink(TelegramObject):
|
||||
# Optionals
|
||||
self.expire_date = expire_date
|
||||
self.member_limit = int(member_limit) if member_limit is not None else None
|
||||
|
||||
self.name = name
|
||||
self.creates_join_request = creates_join_request
|
||||
self.pending_join_request_count = (
|
||||
int(pending_join_request_count) if pending_join_request_count is not None else None
|
||||
)
|
||||
self._id_attrs = (self.invite_link, self.creator, self.is_primary, self.is_revoked)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ChatJoinRequest."""
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import TelegramObject, User, Chat, ChatInviteLink
|
||||
from telegram.utils.helpers import from_timestamp, to_timestamp, DEFAULT_NONE
|
||||
from telegram.utils.types import JSONDict, ODVInput
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
class ChatJoinRequest(TelegramObject):
|
||||
"""This object represents a join request sent to a chat.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`chat`, :attr:`from_user` and :attr:`date` are equal.
|
||||
|
||||
Note:
|
||||
Since Bot API 5.5, bots are allowed to contact users who sent a join request to a chat
|
||||
where the bot is an administrator with the
|
||||
:attr:`~telegram.ChatMemberAdministrator.can_invite_users` administrator right – even if
|
||||
the user never interacted with the bot before.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Args:
|
||||
chat (:class:`telegram.Chat`): Chat to which the request was sent.
|
||||
from_user (:class:`telegram.User`): User that sent the join request.
|
||||
date (:class:`datetime.datetime`): Date the request was sent.
|
||||
bio (:obj:`str`, optional): Bio of the user.
|
||||
invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link that was used
|
||||
by the user to send the join request.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
|
||||
Attributes:
|
||||
chat (:class:`telegram.Chat`): Chat to which the request was sent.
|
||||
from_user (:class:`telegram.User`): User that sent the join request.
|
||||
date (:class:`datetime.datetime`): Date the request was sent.
|
||||
bio (:obj:`str`): Optional. Bio of the user.
|
||||
invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link that was used
|
||||
by the user to send the join request.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'chat',
|
||||
'from_user',
|
||||
'date',
|
||||
'bio',
|
||||
'invite_link',
|
||||
'bot',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chat: Chat,
|
||||
from_user: User,
|
||||
date: datetime.datetime,
|
||||
bio: str = None,
|
||||
invite_link: ChatInviteLink = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.chat = chat
|
||||
self.from_user = from_user
|
||||
self.date = date
|
||||
|
||||
# Optionals
|
||||
self.bio = bio
|
||||
self.invite_link = invite_link
|
||||
|
||||
self.bot = bot
|
||||
self._id_attrs = (self.chat, self.from_user, self.date)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatJoinRequest']:
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['chat'] = Chat.de_json(data.get('chat'), bot)
|
||||
data['from_user'] = User.de_json(data.get('from'), bot)
|
||||
data['date'] = from_timestamp(data.get('date', None))
|
||||
data['invite_link'] = ChatInviteLink.de_json(data.get('invite_link'), bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
"""See :meth:`telegram.TelegramObject.to_dict`."""
|
||||
data = super().to_dict()
|
||||
|
||||
data['date'] = to_timestamp(self.date)
|
||||
|
||||
return data
|
||||
|
||||
def approve(
|
||||
self,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.approve_chat_join_request(chat_id=update.effective_chat.id,
|
||||
user_id=update.effective_user.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.approve_chat_join_request`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.approve_chat_join_request(
|
||||
chat_id=self.chat.id, user_id=self.from_user.id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def decline(
|
||||
self,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.decline_chat_join_request(chat_id=update.effective_chat.id,
|
||||
user_id=update.effective_user.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.decline_chat_join_request`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.decline_chat_join_request(
|
||||
chat_id=self.chat.id, user_id=self.from_user.id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
+17
-2
@@ -21,7 +21,7 @@ The following constants were extracted from the
|
||||
`Telegram Bots API <https://core.telegram.org/bots/api>`_.
|
||||
|
||||
Attributes:
|
||||
BOT_API_VERSION (:obj:`str`): `5.3`. Telegram Bot API version supported by this
|
||||
BOT_API_VERSION (:obj:`str`): `5.5`. Telegram Bot API version supported by this
|
||||
version of `python-telegram-bot`. Also available as ``telegram.bot_api_version``.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
@@ -48,6 +48,10 @@ Attributes:
|
||||
ANONYMOUS_ADMIN_ID (:obj:`int`): ``1087968824`` (User id in groups for anonymous admin)
|
||||
SERVICE_CHAT_ID (:obj:`int`): ``777000`` (Telegram service chat, that also acts as sender of
|
||||
channel posts forwarded to discussion groups)
|
||||
FAKE_CHANNEL_ID (:obj:`int`): ``136817688`` (User id in groups when message is sent on behalf
|
||||
of a channel).
|
||||
|
||||
.. versionadded:: 13.9
|
||||
|
||||
The following constants are related to specific classes and are also available
|
||||
as attributes of those classes:
|
||||
@@ -86,6 +90,9 @@ Attributes:
|
||||
|
||||
.. versionadded:: 13.5
|
||||
CHATACTION_UPLOAD_DOCUMENT (:obj:`str`): ``'upload_document'``
|
||||
CHATACTION_CHOOSE_STICKER (:obj:`str`): ``'choose_sticker'``
|
||||
|
||||
.. versionadded:: 13.8
|
||||
CHATACTION_UPLOAD_PHOTO (:obj:`str`): ``'upload_photo'``
|
||||
CHATACTION_UPLOAD_VIDEO (:obj:`str`): ``'upload_video'``
|
||||
CHATACTION_UPLOAD_VIDEO_NOTE (:obj:`str`): ``'upload_video_note'``
|
||||
@@ -201,9 +208,13 @@ Attributes:
|
||||
UPDATE_CHAT_MEMBER (:obj:`str`): ``'chat_member'``
|
||||
|
||||
.. versionadded:: 13.5
|
||||
UPDATE_CHAT_JOIN_REQUEST (:obj:`str`): ``'chat_join_request'``
|
||||
|
||||
.. versionadded:: 13.8
|
||||
UPDATE_ALL_TYPES (List[:obj:`str`]): List of all update types.
|
||||
|
||||
.. versionadded:: 13.5
|
||||
.. versionchanged:: 13.8
|
||||
|
||||
:class:`telegram.BotCommandScope`:
|
||||
|
||||
@@ -233,11 +244,12 @@ Attributes:
|
||||
"""
|
||||
from typing import List
|
||||
|
||||
BOT_API_VERSION: str = '5.3'
|
||||
BOT_API_VERSION: str = '5.5'
|
||||
MAX_MESSAGE_LENGTH: int = 4096
|
||||
MAX_CAPTION_LENGTH: int = 1024
|
||||
ANONYMOUS_ADMIN_ID: int = 1087968824
|
||||
SERVICE_CHAT_ID: int = 777000
|
||||
FAKE_CHANNEL_ID: int = 136817688
|
||||
|
||||
# constants above this line are tested
|
||||
|
||||
@@ -267,6 +279,7 @@ CHATACTION_TYPING: str = 'typing'
|
||||
CHATACTION_UPLOAD_AUDIO: str = 'upload_audio'
|
||||
CHATACTION_UPLOAD_VOICE: str = 'upload_voice'
|
||||
CHATACTION_UPLOAD_DOCUMENT: str = 'upload_document'
|
||||
CHATACTION_CHOOSE_STICKER: str = 'choose_sticker'
|
||||
CHATACTION_UPLOAD_PHOTO: str = 'upload_photo'
|
||||
CHATACTION_UPLOAD_VIDEO: str = 'upload_video'
|
||||
CHATACTION_UPLOAD_VIDEO_NOTE: str = 'upload_video_note'
|
||||
@@ -353,6 +366,7 @@ UPDATE_POLL = 'poll'
|
||||
UPDATE_POLL_ANSWER = 'poll_answer'
|
||||
UPDATE_MY_CHAT_MEMBER = 'my_chat_member'
|
||||
UPDATE_CHAT_MEMBER = 'chat_member'
|
||||
UPDATE_CHAT_JOIN_REQUEST = 'chat_join_request'
|
||||
UPDATE_ALL_TYPES = [
|
||||
UPDATE_MESSAGE,
|
||||
UPDATE_EDITED_MESSAGE,
|
||||
@@ -367,6 +381,7 @@ UPDATE_ALL_TYPES = [
|
||||
UPDATE_POLL_ANSWER,
|
||||
UPDATE_MY_CHAT_MEMBER,
|
||||
UPDATE_CHAT_MEMBER,
|
||||
UPDATE_CHAT_JOIN_REQUEST,
|
||||
]
|
||||
|
||||
BOT_COMMAND_SCOPE_DEFAULT = 'default'
|
||||
|
||||
@@ -59,6 +59,7 @@ from .messagequeue import DelayQueue
|
||||
from .pollanswerhandler import PollAnswerHandler
|
||||
from .pollhandler import PollHandler
|
||||
from .chatmemberhandler import ChatMemberHandler
|
||||
from .chatjoinrequesthandler import ChatJoinRequestHandler
|
||||
from .defaults import Defaults
|
||||
from .callbackdatacache import CallbackDataCache, InvalidCallbackData
|
||||
|
||||
@@ -68,6 +69,7 @@ __all__ = (
|
||||
'CallbackContext',
|
||||
'CallbackDataCache',
|
||||
'CallbackQueryHandler',
|
||||
'ChatJoinRequestHandler',
|
||||
'ChatMemberHandler',
|
||||
'ChosenInlineResultHandler',
|
||||
'CommandHandler',
|
||||
|
||||
@@ -277,8 +277,6 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
|
||||
new_obj[cls._replace_bot(k, memo)] = cls._replace_bot(val, memo)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
# if '__dict__' in obj.__slots__, we already cover this here, that's why the
|
||||
# __dict__ case comes below
|
||||
try:
|
||||
if hasattr(obj, '__slots__'):
|
||||
for attr_name in new_obj.__slots__:
|
||||
@@ -289,8 +287,11 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
|
||||
cls._replace_bot(getattr(new_obj, attr_name), memo), memo
|
||||
),
|
||||
)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if '__dict__' in obj.__slots__:
|
||||
# In this case, we have already covered the case that obj has __dict__
|
||||
# Note that obj may have a __dict__ even if it's not in __slots__!
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__dict__'):
|
||||
for attr_name, attr in new_obj.__dict__.items():
|
||||
setattr(new_obj, attr_name, cls._replace_bot(attr, memo))
|
||||
@@ -302,9 +303,8 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
|
||||
f'See the docs of BasePersistence.replace_bot for more information.',
|
||||
RuntimeWarning,
|
||||
)
|
||||
memo[obj_id] = obj
|
||||
return obj
|
||||
|
||||
memo[obj_id] = obj
|
||||
return obj
|
||||
|
||||
def insert_bot(self, obj: object) -> object:
|
||||
@@ -379,8 +379,6 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
|
||||
new_obj[self._insert_bot(k, memo)] = self._insert_bot(val, memo)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
# if '__dict__' in obj.__slots__, we already cover this here, that's why the
|
||||
# __dict__ case comes below
|
||||
try:
|
||||
if hasattr(obj, '__slots__'):
|
||||
for attr_name in obj.__slots__:
|
||||
@@ -391,8 +389,11 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
|
||||
self._insert_bot(getattr(new_obj, attr_name), memo), memo
|
||||
),
|
||||
)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if '__dict__' in obj.__slots__:
|
||||
# In this case, we have already covered the case that obj has __dict__
|
||||
# Note that obj may have a __dict__ even if it's not in __slots__!
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__dict__'):
|
||||
for attr_name, attr in new_obj.__dict__.items():
|
||||
setattr(new_obj, attr_name, self._insert_bot(attr, memo))
|
||||
@@ -404,9 +405,8 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
|
||||
f'See the docs of BasePersistence.insert_bot for more information.',
|
||||
RuntimeWarning,
|
||||
)
|
||||
memo[obj_id] = obj
|
||||
return obj
|
||||
|
||||
memo[obj_id] = obj
|
||||
return obj
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the ChatJoinRequestHandler class."""
|
||||
|
||||
|
||||
from telegram import Update
|
||||
|
||||
from .handler import Handler
|
||||
from .utils.types import CCT
|
||||
|
||||
|
||||
class ChatJoinRequestHandler(Handler[Update, CCT]):
|
||||
"""Handler class to handle Telegram updates that contain a chat join request.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
passed to the callback function.
|
||||
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
|
||||
the callback function.
|
||||
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def check_update(self, update: object) -> bool:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
return isinstance(update, Update) and bool(update.chat_join_request)
|
||||
@@ -325,3 +325,13 @@ class ExtBot(telegram.bot.Bot):
|
||||
# We override this method to call self._insert_callback_data
|
||||
result = super().get_chat(chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs)
|
||||
return self._insert_callback_data(result)
|
||||
|
||||
# updated camelCase aliases
|
||||
getChat = get_chat
|
||||
"""Alias for :meth:`get_chat`"""
|
||||
copyMessage = copy_message
|
||||
"""Alias for :meth:`copy_message`"""
|
||||
getUpdates = get_updates
|
||||
"""Alias for :meth:`get_updates`"""
|
||||
stopPoll = stop_poll
|
||||
"""Alias for :meth:`stop_poll`"""
|
||||
|
||||
+33
-4
@@ -1965,16 +1965,16 @@ officedocument.wordprocessingml.document")``.
|
||||
|
||||
class sender_chat(_ChatUserBaseFilter):
|
||||
# pylint: disable=W0235
|
||||
"""Filters messages to allow only those which are from a specified sender chats chat ID or
|
||||
"""Filters messages to allow only those which are from a specified sender chat's chat ID or
|
||||
username.
|
||||
|
||||
Examples:
|
||||
* To filter for messages forwarded to a discussion group from a channel with ID
|
||||
* To filter for messages sent to a group by a channel with ID
|
||||
``-1234``, use ``MessageHandler(Filters.sender_chat(-1234), callback_method)``.
|
||||
* To filter for messages of anonymous admins in a super group with username
|
||||
``@anonymous``, use
|
||||
``MessageHandler(Filters.sender_chat(username='anonymous'), callback_method)``.
|
||||
* To filter for messages forwarded to a discussion group from *any* channel, use
|
||||
* To filter for messages sent to a group by *any* channel, use
|
||||
``MessageHandler(Filters.sender_chat.channel, callback_method)``.
|
||||
* To filter for messages of anonymous admins in *any* super group, use
|
||||
``MessageHandler(Filters.sender_chat.super_group, callback_method)``.
|
||||
@@ -1983,7 +1983,10 @@ officedocument.wordprocessingml.document")``.
|
||||
Remember, ``sender_chat`` is also set for messages in a channel as the channel itself,
|
||||
so when your bot is an admin in a channel and the linked discussion group, you would
|
||||
receive the message twice (once from inside the channel, once inside the discussion
|
||||
group).
|
||||
group). Since v13.9, the field :attr:`telegram.Message.is_automatic_forward` will be
|
||||
:obj:`True` for the discussion group message.
|
||||
|
||||
.. seealso:: :attr:`Filters.is_automatic_forward`
|
||||
|
||||
Warning:
|
||||
:attr:`chat_ids` will return a *copy* of the saved chat ids as :class:`frozenset`. This
|
||||
@@ -2089,6 +2092,32 @@ officedocument.wordprocessingml.document")``.
|
||||
super_group = _SuperGroup()
|
||||
channel = _Channel()
|
||||
|
||||
class _IsAutomaticForward(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.is_automatic_forward'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
return bool(message.is_automatic_forward)
|
||||
|
||||
is_automatic_forward = _IsAutomaticForward()
|
||||
"""Messages that contain :attr:`telegram.Message.is_automatic_forward`.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
"""
|
||||
|
||||
class _HasProtectedContent(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.has_protected_content'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
return bool(message.has_protected_content)
|
||||
|
||||
has_protected_content = _HasProtectedContent()
|
||||
"""Messages that contain :attr:`telegram.Message.has_protected_content`.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
"""
|
||||
|
||||
class _Invoice(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.invoice'
|
||||
|
||||
@@ -45,16 +45,25 @@ class InlineKeyboardButton(TelegramObject):
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
* Since Bot API 5.5, it's now allowed to mention users by their ID in inline keyboards.
|
||||
This will only work in Telegram versions released after December 7, 2021.
|
||||
Older clients will display *unsupported message*.
|
||||
|
||||
Warning:
|
||||
If your bot allows your arbitrary callback data, buttons whose callback data is a
|
||||
non-hashable object will be come unhashable. Trying to evaluate ``hash(button)`` will
|
||||
non-hashable object will become unhashable. Trying to evaluate ``hash(button)`` will
|
||||
result in a :class:`TypeError`.
|
||||
|
||||
.. versionchanged:: 13.6
|
||||
|
||||
Args:
|
||||
text (:obj:`str`): Label text on the button.
|
||||
url (:obj:`str`, optional): HTTP or tg:// url to be opened when button is pressed.
|
||||
url (:obj:`str`, optional): HTTP or tg:// url to be opened when the button is pressed.
|
||||
Links ``tg://user?id=<user_id>`` can be used to mention a user by
|
||||
their ID without using a username, if this is allowed by their privacy settings.
|
||||
|
||||
.. versionchanged:: 13.9
|
||||
You can now mention a user using ``tg://user?id=<user_id>``.
|
||||
login_url (:class:`telegram.LoginUrl`, optional): An HTTP URL used to automatically
|
||||
authorize the user. Can be used as a replacement for the Telegram Login Widget.
|
||||
callback_data (:obj:`str` | :obj:`Any`, optional): Data to be sent in a callback query to
|
||||
@@ -76,12 +85,18 @@ class InlineKeyboardButton(TelegramObject):
|
||||
be launched when the user presses the button. This type of button must always be
|
||||
the ``first`` button in the first row.
|
||||
pay (:obj:`bool`, optional): Specify :obj:`True`, to send a Pay button. This type of button
|
||||
must always be the ``first`` button in the first row.
|
||||
must always be the `first` button in the first row and can only be used in invoice
|
||||
messages.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Attributes:
|
||||
text (:obj:`str`): Label text on the button.
|
||||
url (:obj:`str`): Optional. HTTP or tg:// url to be opened when button is pressed.
|
||||
url (:obj:`str`): Optional. HTTP or tg:// url to be opened when the button is pressed.
|
||||
Links ``tg://user?id=<user_id>`` can be used to mention a user by
|
||||
their ID without using a username, if this is allowed by their privacy settings.
|
||||
|
||||
.. versionchanged:: 13.9
|
||||
You can now mention a user using ``tg://user?id=<user_id>``.
|
||||
login_url (:class:`telegram.LoginUrl`): Optional. An HTTP URL used to automatically
|
||||
authorize the user. Can be used as a replacement for the Telegram Login Widget.
|
||||
callback_data (:obj:`str` | :obj:`object`): Optional. Data to be sent in a callback query
|
||||
|
||||
+43
-9
@@ -90,12 +90,15 @@ class Message(TelegramObject):
|
||||
|
||||
Args:
|
||||
message_id (:obj:`int`): Unique message identifier inside this chat.
|
||||
from_user (:class:`telegram.User`, optional): Sender, empty for messages sent
|
||||
to channels.
|
||||
from_user (:class:`telegram.User`, optional): Sender of the message; empty for messages
|
||||
sent to channels. For backward compatibility, this will contain a fake sender user in
|
||||
non-channel chats, if the message was sent on behalf of a chat.
|
||||
sender_chat (:class:`telegram.Chat`, optional): Sender of the message, sent on behalf of a
|
||||
chat. The channel itself for channel messages. The supergroup itself for messages from
|
||||
anonymous group administrators. The linked channel for messages automatically forwarded
|
||||
to the discussion group.
|
||||
chat. For example, the channel itself for channel posts, the supergroup itself for
|
||||
messages from anonymous group administrators, the linked channel for messages
|
||||
automatically forwarded to the discussion group. For backward compatibility,
|
||||
:attr:`from_user` contains a fake sender user in non-channel chats, if the message was
|
||||
sent on behalf of a chat.
|
||||
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
|
||||
:class:`datetime.datetime`.
|
||||
chat (:class:`telegram.Chat`): Conversation the message belongs to.
|
||||
@@ -109,9 +112,17 @@ class Message(TelegramObject):
|
||||
who disallow adding a link to their account in forwarded messages.
|
||||
forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the
|
||||
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
|
||||
is_automatic_forward (:obj:`bool`, optional): :obj:`True`, if the message is a channel post
|
||||
that was automatically forwarded to the connected discussion group.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
reply_to_message (:class:`telegram.Message`, optional): For replies, the original message.
|
||||
edit_date (:class:`datetime.datetime`, optional): Date the message was last edited in Unix
|
||||
time. Converted to :class:`datetime.datetime`.
|
||||
has_protected_content (:obj:`bool`, optional): :obj:`True`, if the message can't be
|
||||
forwarded.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
media_group_id (:obj:`str`, optional): The unique identifier of a media message group this
|
||||
message belongs to.
|
||||
text (str, optional): For text messages, the actual UTF-8 text of the message, 0-4096
|
||||
@@ -225,11 +236,12 @@ class Message(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
message_id (:obj:`int`): Unique message identifier inside this chat.
|
||||
from_user (:class:`telegram.User`): Optional. Sender.
|
||||
from_user (:class:`telegram.User`): Optional. Sender of the message; empty for messages
|
||||
sent to channels. For backward compatibility, this will contain a fake sender user in
|
||||
non-channel chats, if the message was sent on behalf of a chat.
|
||||
sender_chat (:class:`telegram.Chat`): Optional. Sender of the message, sent on behalf of a
|
||||
chat. The channel itself for channel messages. The supergroup itself for messages from
|
||||
anonymous group administrators. The linked channel for messages automatically forwarded
|
||||
to the discussion group.
|
||||
chat. For backward compatibility, :attr:`from_user` contains a fake sender user in
|
||||
non-channel chats, if the message was sent on behalf of a chat.
|
||||
date (:class:`datetime.datetime`): Date the message was sent.
|
||||
chat (:class:`telegram.Chat`): Conversation the message belongs to.
|
||||
forward_from (:class:`telegram.User`): Optional. Sender of the original message.
|
||||
@@ -238,10 +250,18 @@ class Message(TelegramObject):
|
||||
forward_from_message_id (:obj:`int`): Optional. Identifier of the original message in the
|
||||
channel.
|
||||
forward_date (:class:`datetime.datetime`): Optional. Date the original message was sent.
|
||||
is_automatic_forward (:obj:`bool`): Optional. :obj:`True`, if the message is a channel post
|
||||
that was automatically forwarded to the connected discussion group.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
reply_to_message (:class:`telegram.Message`): Optional. For replies, the original message.
|
||||
Note that the Message object in this field will not contain further
|
||||
``reply_to_message`` fields even if it itself is a reply.
|
||||
edit_date (:class:`datetime.datetime`): Optional. Date the message was last edited.
|
||||
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if the message can't be
|
||||
forwarded.
|
||||
|
||||
.. versionadded:: 13.9
|
||||
media_group_id (:obj:`str`): Optional. The unique identifier of a media message group this
|
||||
message belongs to.
|
||||
text (:obj:`str`): Optional. The actual UTF-8 text of the message.
|
||||
@@ -390,6 +410,8 @@ class Message(TelegramObject):
|
||||
'voice_chat_participants_invited',
|
||||
'voice_chat_started',
|
||||
'voice_chat_scheduled',
|
||||
'is_automatic_forward',
|
||||
'has_protected_content',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
@@ -492,6 +514,8 @@ class Message(TelegramObject):
|
||||
voice_chat_participants_invited: VoiceChatParticipantsInvited = None,
|
||||
message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged = None,
|
||||
voice_chat_scheduled: VoiceChatScheduled = None,
|
||||
is_automatic_forward: bool = None,
|
||||
has_protected_content: bool = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
@@ -504,8 +528,10 @@ class Message(TelegramObject):
|
||||
self.forward_from = forward_from
|
||||
self.forward_from_chat = forward_from_chat
|
||||
self.forward_date = forward_date
|
||||
self.is_automatic_forward = is_automatic_forward
|
||||
self.reply_to_message = reply_to_message
|
||||
self.edit_date = edit_date
|
||||
self.has_protected_content = has_protected_content
|
||||
self.text = text
|
||||
self.entities = entities or []
|
||||
self.caption_entities = caption_entities or []
|
||||
@@ -1795,6 +1821,14 @@ class Message(TelegramObject):
|
||||
|
||||
For the documentation of the arguments, please see :meth:`telegram.Bot.forward_message`.
|
||||
|
||||
Note:
|
||||
Since the release of Bot API 5.5 it can be impossible to forward messages from
|
||||
some chats. Use the attributes :attr:`telegram.Message.has_protected_content` and
|
||||
:attr:`telegram.Chat.has_protected_content` to check this.
|
||||
|
||||
As a workaround, it is still possible to use :meth:`copy`. However, this
|
||||
behaviour is undocumented and might be changed by Telegram.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, instance representing the message forwarded.
|
||||
|
||||
|
||||
+29
-1
@@ -31,6 +31,7 @@ from telegram import (
|
||||
TelegramObject,
|
||||
ChatMemberUpdated,
|
||||
constants,
|
||||
ChatJoinRequest,
|
||||
)
|
||||
from telegram.poll import PollAnswer
|
||||
from telegram.utils.types import JSONDict
|
||||
@@ -89,6 +90,12 @@ class Update(TelegramObject):
|
||||
:meth:`telegram.ext.Updater.start_webhook`).
|
||||
|
||||
.. versionadded:: 13.4
|
||||
chat_join_request (:class:`telegram.ChatJoinRequest`, optional): A request to join the
|
||||
chat has been sent. The bot must have the
|
||||
:attr:`telegram.ChatPermissions.can_invite_users` administrator right in the chat to
|
||||
receive these updates.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Attributes:
|
||||
@@ -122,6 +129,11 @@ class Update(TelegramObject):
|
||||
:meth:`telegram.ext.Updater.start_webhook`).
|
||||
|
||||
.. versionadded:: 13.4
|
||||
chat_join_request (:class:`telegram.ChatJoinRequest`): Optional. A request to join the
|
||||
chat has been sent. The bot must have the ``'can_invite_users'`` administrator
|
||||
right in the chat to receive these updates.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
"""
|
||||
|
||||
@@ -143,6 +155,7 @@ class Update(TelegramObject):
|
||||
'_effective_message',
|
||||
'my_chat_member',
|
||||
'chat_member',
|
||||
'chat_join_request',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
@@ -198,6 +211,10 @@ class Update(TelegramObject):
|
||||
""":const:`telegram.constants.UPDATE_CHAT_MEMBER`
|
||||
|
||||
.. versionadded:: 13.5"""
|
||||
CHAT_JOIN_REQUEST = constants.UPDATE_CHAT_JOIN_REQUEST
|
||||
""":const:`telegram.constants.UPDATE_CHAT_JOIN_REQUEST`
|
||||
|
||||
.. versionadded:: 13.8"""
|
||||
ALL_TYPES = constants.UPDATE_ALL_TYPES
|
||||
""":const:`telegram.constants.UPDATE_ALL_TYPES`
|
||||
|
||||
@@ -219,6 +236,7 @@ class Update(TelegramObject):
|
||||
poll_answer: PollAnswer = None,
|
||||
my_chat_member: ChatMemberUpdated = None,
|
||||
chat_member: ChatMemberUpdated = None,
|
||||
chat_join_request: ChatJoinRequest = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
@@ -237,6 +255,7 @@ class Update(TelegramObject):
|
||||
self.poll_answer = poll_answer
|
||||
self.my_chat_member = my_chat_member
|
||||
self.chat_member = chat_member
|
||||
self.chat_join_request = chat_join_request
|
||||
|
||||
self._effective_user: Optional['User'] = None
|
||||
self._effective_chat: Optional['Chat'] = None
|
||||
@@ -286,6 +305,9 @@ class Update(TelegramObject):
|
||||
elif self.chat_member:
|
||||
user = self.chat_member.from_user
|
||||
|
||||
elif self.chat_join_request:
|
||||
user = self.chat_join_request.from_user
|
||||
|
||||
self._effective_user = user
|
||||
return user
|
||||
|
||||
@@ -325,6 +347,9 @@ class Update(TelegramObject):
|
||||
elif self.chat_member:
|
||||
chat = self.chat_member.chat
|
||||
|
||||
elif self.chat_join_request:
|
||||
chat = self.chat_join_request.chat
|
||||
|
||||
self._effective_chat = chat
|
||||
return chat
|
||||
|
||||
@@ -335,7 +360,9 @@ class Update(TelegramObject):
|
||||
update this is. Will be :obj:`None` for :attr:`inline_query`,
|
||||
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
|
||||
:attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll`,
|
||||
:attr:`poll_answer`, :attr:`my_chat_member` and :attr:`chat_member`.
|
||||
:attr:`poll_answer`, :attr:`my_chat_member`, :attr:`chat_member` as well as
|
||||
:attr:`chat_join_request` in case the bot is missing the
|
||||
:attr:`telegram.ChatPermissions.can_invite_users` administrator right in the chat.
|
||||
|
||||
"""
|
||||
if self._effective_message:
|
||||
@@ -384,5 +411,6 @@ class Update(TelegramObject):
|
||||
data['poll_answer'] = PollAnswer.de_json(data.get('poll_answer'), bot)
|
||||
data['my_chat_member'] = ChatMemberUpdated.de_json(data.get('my_chat_member'), bot)
|
||||
data['chat_member'] = ChatMemberUpdated.de_json(data.get('chat_member'), bot)
|
||||
data['chat_join_request'] = ChatJoinRequest.de_json(data.get('chat_join_request'), bot)
|
||||
|
||||
return cls(**data)
|
||||
|
||||
@@ -22,6 +22,7 @@ from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Union, Tuple
|
||||
|
||||
from telegram import TelegramObject, constants
|
||||
from telegram.inline.inlinekeyboardbutton import InlineKeyboardButton
|
||||
from telegram.utils.helpers import (
|
||||
mention_html as util_mention_html,
|
||||
DEFAULT_NONE,
|
||||
@@ -233,6 +234,22 @@ class User(TelegramObject):
|
||||
return util_mention_html(self.id, name)
|
||||
return util_mention_html(self.id, self.full_name)
|
||||
|
||||
def mention_button(self, name: str = None) -> InlineKeyboardButton:
|
||||
"""
|
||||
Shortcut for::
|
||||
|
||||
InlineKeyboardButton(text=name, url=f"tg://user?id={update.effective_user.id}")
|
||||
|
||||
.. versionadded:: 13.9
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): The name used as a link for the user. Defaults to :attr:`full_name`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.InlineKeyboardButton`: InlineButton with url set to the user mention
|
||||
"""
|
||||
return InlineKeyboardButton(text=name or self.full_name, url=f"tg://user?id={self.id}")
|
||||
|
||||
def pin_message(
|
||||
self,
|
||||
message_id: int,
|
||||
@@ -1140,3 +1157,49 @@ class User(TelegramObject):
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def approve_join_request(
|
||||
self,
|
||||
chat_id: Union[int, str],
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.approve_chat_join_request(user_id=update.effective_user.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.approve_chat_join_request`.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.approve_chat_join_request(
|
||||
user_id=self.id, chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def decline_join_request(
|
||||
self,
|
||||
chat_id: Union[int, str],
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.decline_chat_join_request(user_id=update.effective_user.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.decline_chat_join_request`.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.decline_chat_join_request(
|
||||
user_id=self.id, chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
@@ -58,7 +58,7 @@ except ImportError: # pragma: no cover
|
||||
raise
|
||||
|
||||
# pylint: disable=C0412
|
||||
from telegram import InputFile, InputMedia, TelegramError
|
||||
from telegram import InputFile, TelegramError
|
||||
from telegram.error import (
|
||||
BadRequest,
|
||||
ChatMigrated,
|
||||
@@ -325,13 +325,9 @@ class Request:
|
||||
# Urllib3 doesn't like floats it seems
|
||||
data[key] = str(val)
|
||||
elif key == 'media':
|
||||
# One media or multiple
|
||||
if isinstance(val, InputMedia):
|
||||
# Attach and set val to attached name
|
||||
data[key] = val.to_json()
|
||||
if isinstance(val.media, InputFile): # type: ignore
|
||||
data[val.media.attach] = val.media.field_tuple # type: ignore
|
||||
else:
|
||||
files = True
|
||||
# List of media
|
||||
if isinstance(val, list):
|
||||
# Attach and set val to attached name for all
|
||||
media = []
|
||||
for med in val:
|
||||
@@ -343,7 +339,16 @@ class Request:
|
||||
if "thumb" in media_dict:
|
||||
data[med.thumb.attach] = med.thumb.field_tuple
|
||||
data[key] = json.dumps(media)
|
||||
files = True
|
||||
# Single media
|
||||
else:
|
||||
# Attach and set val to attached name
|
||||
media_dict = val.to_dict()
|
||||
if isinstance(val.media, InputFile):
|
||||
data[val.media.attach] = val.media.field_tuple
|
||||
# if the file has a thumb, we also need to attach it to the data
|
||||
if "thumb" in media_dict:
|
||||
data[val.thumb.attach] = val.thumb.field_tuple
|
||||
data[key] = json.dumps(media_dict)
|
||||
elif isinstance(val, list):
|
||||
# In case we're sending files, we need to json-dump lists manually
|
||||
# As we can't know if that's the case, we just json-dump here
|
||||
|
||||
+1
-1
@@ -20,5 +20,5 @@
|
||||
|
||||
from telegram import constants
|
||||
|
||||
__version__ = '13.7'
|
||||
__version__ = '13.9'
|
||||
bot_api_version = constants.BOT_API_VERSION # pylint: disable=C0103
|
||||
|
||||
+138
-5
@@ -66,6 +66,14 @@ from tests.conftest import expect_bad_request, check_defaults_handling, GITHUB_A
|
||||
from tests.bots import FALLBACKS
|
||||
|
||||
|
||||
def to_camel_case(snake_str):
|
||||
"""https://stackoverflow.com/a/19053800"""
|
||||
components = snake_str.split('_')
|
||||
# We capitalize the first letter of each component except the first one
|
||||
# with the 'title' method and join them together.
|
||||
return components[0] + ''.join(x.title() for x in components[1:])
|
||||
|
||||
|
||||
class ExtBotSubClass(ExtBot):
|
||||
# used for test_defaults_warning below
|
||||
pass
|
||||
@@ -710,6 +718,7 @@ class TestBot:
|
||||
ChatAction.UPLOAD_VIDEO,
|
||||
ChatAction.UPLOAD_VIDEO_NOTE,
|
||||
ChatAction.UPLOAD_VOICE,
|
||||
ChatAction.CHOOSE_STICKER,
|
||||
],
|
||||
)
|
||||
def test_send_chat_action(self, bot, chat_id, chat_action):
|
||||
@@ -1003,6 +1012,17 @@ class TestBot:
|
||||
assert tz_bot.ban_chat_member(2, 32, until_date=until)
|
||||
assert tz_bot.ban_chat_member(2, 32, until_date=until_timestamp)
|
||||
|
||||
def test_ban_chat_sender_chat(self, monkeypatch, bot):
|
||||
# For now, we just test that we pass the correct data to TG
|
||||
def make_assertion(url, data, *args, **kwargs):
|
||||
chat_id = data['chat_id'] == 2
|
||||
sender_chat_id = data['sender_chat_id'] == 32
|
||||
return chat_id and sender_chat_id
|
||||
|
||||
monkeypatch.setattr(bot.request, 'post', make_assertion)
|
||||
assert bot.ban_chat_sender_chat(2, 32)
|
||||
monkeypatch.delattr(bot.request, 'post')
|
||||
|
||||
def test_kick_chat_member_warning(self, monkeypatch, bot, recwarn):
|
||||
def test(url, data, *args, **kwargs):
|
||||
chat_id = data['chat_id'] == 2
|
||||
@@ -1028,6 +1048,15 @@ class TestBot:
|
||||
|
||||
assert bot.unban_chat_member(2, 32, only_if_banned=only_if_banned)
|
||||
|
||||
def test_unban_chat_sender_chat(self, monkeypatch, bot):
|
||||
def make_assertion(url, data, *args, **kwargs):
|
||||
chat_id = data['chat_id'] == 2
|
||||
sender_chat_id = data['sender_chat_id'] == 32
|
||||
return chat_id and sender_chat_id
|
||||
|
||||
monkeypatch.setattr(bot.request, 'post', make_assertion)
|
||||
assert bot.unbanChatSenderChat(2, 32)
|
||||
|
||||
def test_set_chat_permissions(self, monkeypatch, bot, chat_permissions):
|
||||
def test(url, data, *args, **kwargs):
|
||||
chat_id = data['chat_id'] == 2
|
||||
@@ -1688,6 +1717,37 @@ class TestBot:
|
||||
assert isinstance(invite_link, str)
|
||||
assert invite_link != ''
|
||||
|
||||
def test_create_edit_invite_link_mutually_exclusive_arguments(self, bot, channel_id):
|
||||
data = {'chat_id': channel_id, 'member_limit': 17, 'creates_join_request': True}
|
||||
|
||||
with pytest.raises(ValueError, match="`member_limit` can't be specified"):
|
||||
bot.create_chat_invite_link(**data)
|
||||
|
||||
data.update({'invite_link': 'https://invite.link'})
|
||||
with pytest.raises(ValueError, match="`member_limit` can't be specified"):
|
||||
bot.edit_chat_invite_link(**data)
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize('creates_join_request', [True, False])
|
||||
@pytest.mark.parametrize('name', [None, 'name'])
|
||||
def test_create_chat_invite_link_basics(self, bot, creates_join_request, name, channel_id):
|
||||
data = {}
|
||||
if creates_join_request:
|
||||
data['creates_join_request'] = True
|
||||
if name:
|
||||
data['name'] = name
|
||||
invite_link = bot.create_chat_invite_link(chat_id=channel_id, **data)
|
||||
|
||||
assert invite_link.member_limit is None
|
||||
assert invite_link.expire_date is None
|
||||
assert invite_link.creates_join_request == creates_join_request
|
||||
assert invite_link.name == name
|
||||
|
||||
revoked_link = bot.revoke_chat_invite_link(
|
||||
chat_id=channel_id, invite_link=invite_link.invite_link
|
||||
)
|
||||
assert revoked_link.is_revoked
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize('datetime', argvalues=[True, False], ids=['datetime', 'integer'])
|
||||
def test_advanced_chat_invite_links(self, bot, channel_id, datetime):
|
||||
@@ -1712,12 +1772,29 @@ class TestBot:
|
||||
aware_time_in_future = pytz.UTC.localize(time_in_future)
|
||||
|
||||
edited_invite_link = bot.edit_chat_invite_link(
|
||||
channel_id, invite_link.invite_link, expire_date=expire_time, member_limit=20
|
||||
channel_id,
|
||||
invite_link.invite_link,
|
||||
expire_date=expire_time,
|
||||
member_limit=20,
|
||||
name='NewName',
|
||||
)
|
||||
assert edited_invite_link.invite_link == invite_link.invite_link
|
||||
assert pytest.approx(edited_invite_link.expire_date == aware_time_in_future)
|
||||
assert edited_invite_link.name == 'NewName'
|
||||
assert edited_invite_link.member_limit == 20
|
||||
|
||||
edited_invite_link = bot.edit_chat_invite_link(
|
||||
channel_id,
|
||||
invite_link.invite_link,
|
||||
name='EvenNewerName',
|
||||
creates_join_request=True,
|
||||
)
|
||||
assert edited_invite_link.invite_link == invite_link.invite_link
|
||||
assert pytest.approx(edited_invite_link.expire_date == aware_time_in_future)
|
||||
assert edited_invite_link.name == 'EvenNewerName'
|
||||
assert edited_invite_link.creates_join_request is True
|
||||
assert edited_invite_link.member_limit is None
|
||||
|
||||
revoked_invite_link = bot.revoke_chat_invite_link(channel_id, invite_link.invite_link)
|
||||
assert revoked_invite_link.invite_link == invite_link.invite_link
|
||||
assert revoked_invite_link.is_revoked is True
|
||||
@@ -1742,16 +1819,49 @@ class TestBot:
|
||||
time_in_future = aware_expire_date.replace(tzinfo=None)
|
||||
|
||||
edited_invite_link = tz_bot.edit_chat_invite_link(
|
||||
channel_id, invite_link.invite_link, expire_date=time_in_future, member_limit=20
|
||||
channel_id,
|
||||
invite_link.invite_link,
|
||||
expire_date=time_in_future,
|
||||
member_limit=20,
|
||||
name='NewName',
|
||||
)
|
||||
assert edited_invite_link.invite_link == invite_link.invite_link
|
||||
assert pytest.approx(edited_invite_link.expire_date == aware_expire_date)
|
||||
assert edited_invite_link.name == 'NewName'
|
||||
assert edited_invite_link.member_limit == 20
|
||||
|
||||
edited_invite_link = tz_bot.edit_chat_invite_link(
|
||||
channel_id,
|
||||
invite_link.invite_link,
|
||||
name='EvenNewerName',
|
||||
creates_join_request=True,
|
||||
)
|
||||
assert edited_invite_link.invite_link == invite_link.invite_link
|
||||
assert pytest.approx(edited_invite_link.expire_date == aware_expire_date)
|
||||
assert edited_invite_link.name == 'EvenNewerName'
|
||||
assert edited_invite_link.creates_join_request is True
|
||||
assert edited_invite_link.member_limit is None
|
||||
|
||||
revoked_invite_link = tz_bot.revoke_chat_invite_link(channel_id, invite_link.invite_link)
|
||||
assert revoked_invite_link.invite_link == invite_link.invite_link
|
||||
assert revoked_invite_link.is_revoked is True
|
||||
|
||||
@flaky(3, 1)
|
||||
def test_approve_chat_join_request(self, bot, chat_id, channel_id):
|
||||
# TODO: Need incoming join request to properly test
|
||||
# Since we can't create join requests on the fly, we just tests the call to TG
|
||||
# by checking that it complains about approving a user who is already in the chat
|
||||
with pytest.raises(BadRequest, match='User_already_participant'):
|
||||
bot.approve_chat_join_request(chat_id=channel_id, user_id=chat_id)
|
||||
|
||||
@flaky(3, 1)
|
||||
def test_decline_chat_join_request(self, bot, chat_id, channel_id):
|
||||
# TODO: Need incoming join request to properly test
|
||||
# Since we can't create join requests on the fly, we just tests the call to TG
|
||||
# by checking that it complains about declining a user who is already in the chat
|
||||
with pytest.raises(BadRequest, match='User_already_participant'):
|
||||
bot.decline_chat_join_request(chat_id=channel_id, user_id=chat_id)
|
||||
|
||||
@flaky(3, 1)
|
||||
def test_set_chat_photo(self, bot, channel_id):
|
||||
def func():
|
||||
@@ -2024,7 +2134,8 @@ class TestBot:
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize('json_keyboard', [True, False])
|
||||
def test_copy_message(self, monkeypatch, bot, chat_id, media_message, json_keyboard):
|
||||
@pytest.mark.parametrize('caption', ["<b>Test</b>", '', None])
|
||||
def test_copy_message(self, monkeypatch, bot, chat_id, media_message, json_keyboard, caption):
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
[[InlineKeyboardButton(text="test", callback_data="test2")]]
|
||||
)
|
||||
@@ -2033,7 +2144,7 @@ class TestBot:
|
||||
assert data["chat_id"] == chat_id
|
||||
assert data["from_chat_id"] == chat_id
|
||||
assert data["message_id"] == media_message.message_id
|
||||
assert data["caption"] == "<b>Test</b>"
|
||||
assert data.get("caption") == caption
|
||||
assert data["parse_mode"] == ParseMode.HTML
|
||||
assert data["reply_to_message_id"] == media_message.message_id
|
||||
assert data["reply_markup"] == keyboard.to_json()
|
||||
@@ -2046,7 +2157,7 @@ class TestBot:
|
||||
chat_id,
|
||||
from_chat_id=chat_id,
|
||||
message_id=media_message.message_id,
|
||||
caption="<b>Test</b>",
|
||||
caption=caption,
|
||||
caption_entities=[MessageEntity(MessageEntity.BOLD, 0, 4)],
|
||||
parse_mode=ParseMode.HTML,
|
||||
reply_to_message_id=media_message.message_id,
|
||||
@@ -2454,3 +2565,25 @@ class TestBot:
|
||||
assert 'Passing Defaults to telegram.Bot is deprecated.' in str(recwarn[-1].message)
|
||||
else:
|
||||
assert len(recwarn) == 0
|
||||
|
||||
def test_camel_case_redefinition_extbot(self):
|
||||
invalid_camel_case_functions = []
|
||||
for function_name, function in ExtBot.__dict__.items():
|
||||
camel_case_function = getattr(ExtBot, to_camel_case(function_name), False)
|
||||
if callable(function) and camel_case_function and camel_case_function is not function:
|
||||
invalid_camel_case_functions.append(function_name)
|
||||
assert invalid_camel_case_functions == []
|
||||
|
||||
def test_camel_case_bot(self):
|
||||
not_available_camelcase_functions = []
|
||||
for function_name, function in Bot.__dict__.items():
|
||||
if (
|
||||
function_name.startswith("_")
|
||||
or not callable(function)
|
||||
or function_name == "to_dict"
|
||||
):
|
||||
continue
|
||||
camel_case_function = getattr(Bot, to_camel_case(function_name), False)
|
||||
if not camel_case_function:
|
||||
not_available_camelcase_functions.append(function_name)
|
||||
assert not_available_camelcase_functions == []
|
||||
|
||||
@@ -41,6 +41,8 @@ def chat(bot):
|
||||
bio=TestChat.bio,
|
||||
linked_chat_id=TestChat.linked_chat_id,
|
||||
location=TestChat.location,
|
||||
has_private_forwards=True,
|
||||
has_protected_content=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -62,6 +64,8 @@ class TestChat:
|
||||
bio = "I'm a Barbie Girl in a Barbie World"
|
||||
linked_chat_id = 11880
|
||||
location = ChatLocation(Location(123, 456), 'Barbie World')
|
||||
has_protected_content = True
|
||||
has_private_forwards = True
|
||||
|
||||
def test_slot_behaviour(self, chat, recwarn, mro_slots):
|
||||
for attr in chat.__slots__:
|
||||
@@ -84,6 +88,8 @@ class TestChat:
|
||||
'slow_mode_delay': self.slow_mode_delay,
|
||||
'message_auto_delete_time': self.message_auto_delete_time,
|
||||
'bio': self.bio,
|
||||
'has_protected_content': self.has_protected_content,
|
||||
'has_private_forwards': self.has_private_forwards,
|
||||
'linked_chat_id': self.linked_chat_id,
|
||||
'location': self.location.to_dict(),
|
||||
}
|
||||
@@ -100,6 +106,8 @@ class TestChat:
|
||||
assert chat.slow_mode_delay == self.slow_mode_delay
|
||||
assert chat.message_auto_delete_time == self.message_auto_delete_time
|
||||
assert chat.bio == self.bio
|
||||
assert chat.has_protected_content == self.has_protected_content
|
||||
assert chat.has_private_forwards == self.has_private_forwards
|
||||
assert chat.linked_chat_id == self.linked_chat_id
|
||||
assert chat.location.location == self.location.location
|
||||
assert chat.location.address == self.location.address
|
||||
@@ -117,6 +125,8 @@ class TestChat:
|
||||
assert chat_dict['slow_mode_delay'] == chat.slow_mode_delay
|
||||
assert chat_dict['message_auto_delete_time'] == chat.message_auto_delete_time
|
||||
assert chat_dict['bio'] == chat.bio
|
||||
assert chat_dict['has_private_forwards'] == chat.has_private_forwards
|
||||
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()
|
||||
|
||||
@@ -225,6 +235,36 @@ class TestChat:
|
||||
monkeypatch.setattr(chat.bot, 'ban_chat_member', make_assertion)
|
||||
assert chat.ban_member(user_id=42, until_date=43)
|
||||
|
||||
def test_ban_sender_chat(self, monkeypatch, chat):
|
||||
def make_assertion(*_, **kwargs):
|
||||
chat_id = kwargs['chat_id'] == chat.id
|
||||
sender_chat_id = kwargs['sender_chat_id'] == 42
|
||||
return chat_id and sender_chat_id
|
||||
|
||||
assert check_shortcut_signature(
|
||||
Chat.ban_sender_chat, Bot.ban_chat_sender_chat, ['chat_id'], []
|
||||
)
|
||||
assert check_shortcut_call(chat.ban_sender_chat, chat.bot, 'ban_chat_sender_chat')
|
||||
assert check_defaults_handling(chat.ban_sender_chat, chat.bot)
|
||||
|
||||
monkeypatch.setattr(chat.bot, 'ban_chat_sender_chat', make_assertion)
|
||||
assert chat.ban_sender_chat(42)
|
||||
|
||||
def test_ban_chat(self, monkeypatch, chat):
|
||||
def make_assertion(*_, **kwargs):
|
||||
chat_id = kwargs['chat_id'] == 42
|
||||
sender_chat_id = kwargs['sender_chat_id'] == chat.id
|
||||
return chat_id and sender_chat_id
|
||||
|
||||
assert check_shortcut_signature(
|
||||
Chat.ban_chat, Bot.ban_chat_sender_chat, ['sender_chat_id'], []
|
||||
)
|
||||
assert check_shortcut_call(chat.ban_chat, chat.bot, 'ban_chat_sender_chat')
|
||||
assert check_defaults_handling(chat.ban_chat, chat.bot)
|
||||
|
||||
monkeypatch.setattr(chat.bot, 'ban_chat_sender_chat', make_assertion)
|
||||
assert chat.ban_chat(42)
|
||||
|
||||
def test_kick_member_warning(self, chat, monkeypatch, recwarn):
|
||||
def make_assertion(*_, **kwargs):
|
||||
chat_id = kwargs['chat_id'] == chat.id
|
||||
@@ -252,6 +292,36 @@ class TestChat:
|
||||
monkeypatch.setattr(chat.bot, 'unban_chat_member', make_assertion)
|
||||
assert chat.unban_member(user_id=42, only_if_banned=only_if_banned)
|
||||
|
||||
def test_unban_sender_chat(self, monkeypatch, chat):
|
||||
def make_assertion(*_, **kwargs):
|
||||
chat_id = kwargs['chat_id'] == chat.id
|
||||
sender_chat_id = kwargs['sender_chat_id'] == 42
|
||||
return chat_id and sender_chat_id
|
||||
|
||||
assert check_shortcut_signature(
|
||||
Chat.unban_sender_chat, Bot.unban_chat_sender_chat, ['chat_id'], []
|
||||
)
|
||||
assert check_shortcut_call(chat.unban_sender_chat, chat.bot, 'unban_chat_sender_chat')
|
||||
assert check_defaults_handling(chat.unban_sender_chat, chat.bot)
|
||||
|
||||
monkeypatch.setattr(chat.bot, 'unban_chat_sender_chat', make_assertion)
|
||||
assert chat.unban_sender_chat(42)
|
||||
|
||||
def test_unban_chat(self, monkeypatch, chat):
|
||||
def make_assertion(*_, **kwargs):
|
||||
chat_id = kwargs['chat_id'] == 42
|
||||
sender_chat_id = kwargs['sender_chat_id'] == chat.id
|
||||
return chat_id and sender_chat_id
|
||||
|
||||
assert check_shortcut_signature(
|
||||
Chat.unban_chat, Bot.ban_chat_sender_chat, ['sender_chat_id'], []
|
||||
)
|
||||
assert check_shortcut_call(chat.unban_chat, chat.bot, 'unban_chat_sender_chat')
|
||||
assert check_defaults_handling(chat.unban_chat, chat.bot)
|
||||
|
||||
monkeypatch.setattr(chat.bot, 'unban_chat_sender_chat', make_assertion)
|
||||
assert chat.unban_chat(42)
|
||||
|
||||
@pytest.mark.parametrize('is_anonymous', [True, False, None])
|
||||
def test_promote_member(self, monkeypatch, chat, is_anonymous):
|
||||
def make_assertion(*_, **kwargs):
|
||||
@@ -636,6 +706,36 @@ class TestChat:
|
||||
monkeypatch.setattr(chat.bot, 'revoke_chat_invite_link', make_assertion)
|
||||
assert chat.revoke_invite_link(invite_link=link)
|
||||
|
||||
def test_approve_join_request(self, monkeypatch, chat):
|
||||
def make_assertion(*_, **kwargs):
|
||||
return kwargs['chat_id'] == chat.id and kwargs['user_id'] == 42
|
||||
|
||||
assert check_shortcut_signature(
|
||||
Chat.approve_join_request, Bot.approve_chat_join_request, ['chat_id'], []
|
||||
)
|
||||
assert check_shortcut_call(
|
||||
chat.approve_join_request, chat.bot, 'approve_chat_join_request'
|
||||
)
|
||||
assert check_defaults_handling(chat.approve_join_request, chat.bot)
|
||||
|
||||
monkeypatch.setattr(chat.bot, 'approve_chat_join_request', make_assertion)
|
||||
assert chat.approve_join_request(user_id=42)
|
||||
|
||||
def test_decline_join_request(self, monkeypatch, chat):
|
||||
def make_assertion(*_, **kwargs):
|
||||
return kwargs['chat_id'] == chat.id and kwargs['user_id'] == 42
|
||||
|
||||
assert check_shortcut_signature(
|
||||
Chat.decline_join_request, Bot.decline_chat_join_request, ['chat_id'], []
|
||||
)
|
||||
assert check_shortcut_call(
|
||||
chat.decline_join_request, chat.bot, 'decline_chat_join_request'
|
||||
)
|
||||
assert check_defaults_handling(chat.decline_join_request, chat.bot)
|
||||
|
||||
monkeypatch.setattr(chat.bot, 'decline_chat_join_request', make_assertion)
|
||||
assert chat.decline_join_request(user_id=42)
|
||||
|
||||
def test_equality(self):
|
||||
a = Chat(self.id_, self.title, self.type_)
|
||||
b = Chat(self.id_, self.title, self.type_)
|
||||
|
||||
@@ -38,6 +38,8 @@ def invite_link(creator):
|
||||
TestChatInviteLink.revoked,
|
||||
expire_date=TestChatInviteLink.expire_date,
|
||||
member_limit=TestChatInviteLink.member_limit,
|
||||
name=TestChatInviteLink.name,
|
||||
pending_join_request_count=TestChatInviteLink.pending_join_request_count,
|
||||
)
|
||||
|
||||
|
||||
@@ -48,6 +50,8 @@ class TestChatInviteLink:
|
||||
revoked = False
|
||||
expire_date = datetime.datetime.utcnow()
|
||||
member_limit = 42
|
||||
name = 'LinkName'
|
||||
pending_join_request_count = 42
|
||||
|
||||
def test_slot_behaviour(self, recwarn, mro_slots, invite_link):
|
||||
for attr in invite_link.__slots__:
|
||||
@@ -79,7 +83,9 @@ class TestChatInviteLink:
|
||||
'is_primary': self.primary,
|
||||
'is_revoked': self.revoked,
|
||||
'expire_date': to_timestamp(self.expire_date),
|
||||
'member_limit': self.member_limit,
|
||||
'member_limit': str(self.member_limit),
|
||||
'name': self.name,
|
||||
'pending_join_request_count': str(self.pending_join_request_count),
|
||||
}
|
||||
|
||||
invite_link = ChatInviteLink.de_json(json_dict, bot)
|
||||
@@ -91,6 +97,8 @@ class TestChatInviteLink:
|
||||
assert pytest.approx(invite_link.expire_date == self.expire_date)
|
||||
assert to_timestamp(invite_link.expire_date) == to_timestamp(self.expire_date)
|
||||
assert invite_link.member_limit == self.member_limit
|
||||
assert invite_link.name == self.name
|
||||
assert invite_link.pending_join_request_count == self.pending_join_request_count
|
||||
|
||||
def test_to_dict(self, invite_link):
|
||||
invite_link_dict = invite_link.to_dict()
|
||||
@@ -101,6 +109,8 @@ class TestChatInviteLink:
|
||||
assert invite_link_dict['is_revoked'] == self.revoked
|
||||
assert invite_link_dict['expire_date'] == to_timestamp(self.expire_date)
|
||||
assert invite_link_dict['member_limit'] == self.member_limit
|
||||
assert invite_link_dict['name'] == self.name
|
||||
assert invite_link_dict['pending_join_request_count'] == self.pending_join_request_count
|
||||
|
||||
def test_equality(self):
|
||||
a = ChatInviteLink("link", User(1, '', False), True, True)
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
from telegram import ChatJoinRequest, User, Chat, ChatInviteLink, Bot
|
||||
from telegram.utils.helpers import to_timestamp
|
||||
from tests.conftest import check_shortcut_signature, check_shortcut_call, check_defaults_handling
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def time():
|
||||
return datetime.datetime.now(tz=pytz.utc)
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def chat_join_request(bot, time):
|
||||
return ChatJoinRequest(
|
||||
chat=TestChatJoinRequest.chat,
|
||||
from_user=TestChatJoinRequest.from_user,
|
||||
date=time,
|
||||
bio=TestChatJoinRequest.bio,
|
||||
invite_link=TestChatJoinRequest.invite_link,
|
||||
bot=bot,
|
||||
)
|
||||
|
||||
|
||||
class TestChatJoinRequest:
|
||||
chat = Chat(1, Chat.SUPERGROUP)
|
||||
from_user = User(2, 'first_name', False)
|
||||
bio = 'bio'
|
||||
invite_link = ChatInviteLink(
|
||||
'https://invite.link',
|
||||
User(42, 'creator', False),
|
||||
name='InviteLink',
|
||||
is_revoked=False,
|
||||
is_primary=False,
|
||||
)
|
||||
|
||||
def test_slot_behaviour(self, chat_join_request, recwarn, mro_slots):
|
||||
inst = chat_join_request
|
||||
for attr in inst.__slots__:
|
||||
assert getattr(inst, attr, 'err') != 'err', f"got extra slot '{attr}'"
|
||||
assert not inst.__dict__, f"got missing slot(s): {inst.__dict__}"
|
||||
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
|
||||
inst.custom, inst.bio = 'should give warning', self.bio
|
||||
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
|
||||
|
||||
def test_de_json(self, bot, time):
|
||||
json_dict = {
|
||||
'chat': self.chat.to_dict(),
|
||||
'from': self.from_user.to_dict(),
|
||||
'date': to_timestamp(time),
|
||||
}
|
||||
chat_join_request = ChatJoinRequest.de_json(json_dict, bot)
|
||||
|
||||
assert chat_join_request.chat == self.chat
|
||||
assert chat_join_request.from_user == self.from_user
|
||||
assert pytest.approx(chat_join_request.date == time)
|
||||
assert to_timestamp(chat_join_request.date) == to_timestamp(time)
|
||||
|
||||
json_dict.update({'bio': self.bio, 'invite_link': self.invite_link.to_dict()})
|
||||
chat_join_request = ChatJoinRequest.de_json(json_dict, bot)
|
||||
|
||||
assert chat_join_request.chat == self.chat
|
||||
assert chat_join_request.from_user == self.from_user
|
||||
assert pytest.approx(chat_join_request.date == time)
|
||||
assert to_timestamp(chat_join_request.date) == to_timestamp(time)
|
||||
assert chat_join_request.bio == self.bio
|
||||
assert chat_join_request.invite_link == self.invite_link
|
||||
|
||||
def test_to_dict(self, chat_join_request, time):
|
||||
chat_join_request_dict = chat_join_request.to_dict()
|
||||
|
||||
assert isinstance(chat_join_request_dict, dict)
|
||||
assert chat_join_request_dict['chat'] == chat_join_request.chat.to_dict()
|
||||
assert chat_join_request_dict['from'] == chat_join_request.from_user.to_dict()
|
||||
assert chat_join_request_dict['date'] == to_timestamp(chat_join_request.date)
|
||||
assert chat_join_request_dict['bio'] == chat_join_request.bio
|
||||
assert chat_join_request_dict['invite_link'] == chat_join_request.invite_link.to_dict()
|
||||
|
||||
def test_equality(self, chat_join_request, time):
|
||||
a = chat_join_request
|
||||
b = ChatJoinRequest(self.chat, self.from_user, time)
|
||||
c = ChatJoinRequest(self.chat, self.from_user, time, bio='bio')
|
||||
d = ChatJoinRequest(self.chat, self.from_user, time + datetime.timedelta(1))
|
||||
e = ChatJoinRequest(self.chat, User(-1, 'last_name', True), time)
|
||||
f = User(456, '', False)
|
||||
|
||||
assert a == b
|
||||
assert hash(a) == hash(b)
|
||||
assert a is not b
|
||||
|
||||
assert a == c
|
||||
assert hash(a) == hash(c)
|
||||
|
||||
assert a != d
|
||||
assert hash(a) != hash(d)
|
||||
|
||||
assert a != e
|
||||
assert hash(a) != hash(e)
|
||||
|
||||
assert a != f
|
||||
assert hash(a) != hash(f)
|
||||
|
||||
def test_approve(self, monkeypatch, chat_join_request):
|
||||
def make_assertion(*_, **kwargs):
|
||||
chat_id_test = kwargs['chat_id'] == chat_join_request.chat.id
|
||||
user_id_test = kwargs['user_id'] == chat_join_request.from_user.id
|
||||
|
||||
return chat_id_test and user_id_test
|
||||
|
||||
assert check_shortcut_signature(
|
||||
ChatJoinRequest.approve, Bot.approve_chat_join_request, ['chat_id', 'user_id'], []
|
||||
)
|
||||
assert check_shortcut_call(
|
||||
chat_join_request.approve, chat_join_request.bot, 'approve_chat_join_request'
|
||||
)
|
||||
assert check_defaults_handling(chat_join_request.approve, chat_join_request.bot)
|
||||
|
||||
monkeypatch.setattr(chat_join_request.bot, 'approve_chat_join_request', make_assertion)
|
||||
assert chat_join_request.approve()
|
||||
|
||||
def test_decline(self, monkeypatch, chat_join_request):
|
||||
def make_assertion(*_, **kwargs):
|
||||
chat_id_test = kwargs['chat_id'] == chat_join_request.chat.id
|
||||
user_id_test = kwargs['user_id'] == chat_join_request.from_user.id
|
||||
|
||||
return chat_id_test and user_id_test
|
||||
|
||||
assert check_shortcut_signature(
|
||||
ChatJoinRequest.decline, Bot.decline_chat_join_request, ['chat_id', 'user_id'], []
|
||||
)
|
||||
assert check_shortcut_call(
|
||||
chat_join_request.decline, chat_join_request.bot, 'decline_chat_join_request'
|
||||
)
|
||||
assert check_defaults_handling(chat_join_request.decline, chat_join_request.bot)
|
||||
|
||||
monkeypatch.setattr(chat_join_request.bot, 'decline_chat_join_request', make_assertion)
|
||||
assert chat_join_request.decline()
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import datetime
|
||||
from queue import Queue
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
from telegram import (
|
||||
Update,
|
||||
Bot,
|
||||
Message,
|
||||
User,
|
||||
Chat,
|
||||
CallbackQuery,
|
||||
ChosenInlineResult,
|
||||
ShippingQuery,
|
||||
PreCheckoutQuery,
|
||||
ChatJoinRequest,
|
||||
ChatInviteLink,
|
||||
)
|
||||
from telegram.ext import CallbackContext, JobQueue, ChatJoinRequestHandler
|
||||
|
||||
|
||||
message = Message(1, None, Chat(1, ''), from_user=User(1, '', False), text='Text')
|
||||
|
||||
params = [
|
||||
{'message': message},
|
||||
{'edited_message': message},
|
||||
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat', message=message)},
|
||||
{'channel_post': message},
|
||||
{'edited_channel_post': message},
|
||||
{'chosen_inline_result': ChosenInlineResult('id', User(1, '', False), '')},
|
||||
{'shipping_query': ShippingQuery('id', User(1, '', False), '', None)},
|
||||
{'pre_checkout_query': PreCheckoutQuery('id', User(1, '', False), '', 0, '')},
|
||||
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')},
|
||||
]
|
||||
|
||||
ids = (
|
||||
'message',
|
||||
'edited_message',
|
||||
'callback_query',
|
||||
'channel_post',
|
||||
'edited_channel_post',
|
||||
'chosen_inline_result',
|
||||
'shipping_query',
|
||||
'pre_checkout_query',
|
||||
'callback_query_without_message',
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='class', params=params, ids=ids)
|
||||
def false_update(request):
|
||||
return Update(update_id=2, **request.param)
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def time():
|
||||
return datetime.datetime.now(tz=pytz.utc)
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def chat_join_request(time, bot):
|
||||
return ChatJoinRequest(
|
||||
chat=Chat(1, Chat.SUPERGROUP),
|
||||
from_user=User(2, 'first_name', False),
|
||||
date=time,
|
||||
bio='bio',
|
||||
invite_link=ChatInviteLink(
|
||||
'https://invite.link',
|
||||
User(42, 'creator', False),
|
||||
name='InviteLink',
|
||||
is_revoked=False,
|
||||
is_primary=False,
|
||||
),
|
||||
bot=bot,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def chat_join_request_update(bot, chat_join_request):
|
||||
return Update(0, chat_join_request=chat_join_request)
|
||||
|
||||
|
||||
class TestChatJoinRequestHandler:
|
||||
test_flag = False
|
||||
|
||||
def test_slot_behaviour(self, recwarn, mro_slots):
|
||||
action = ChatJoinRequestHandler(self.callback_basic)
|
||||
for attr in action.__slots__:
|
||||
assert getattr(action, attr, 'err') != 'err', f"got extra slot '{attr}'"
|
||||
assert not action.__dict__, f"got missing slot(s): {action.__dict__}"
|
||||
assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
|
||||
action.custom = 'should give warning'
|
||||
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset(self):
|
||||
self.test_flag = False
|
||||
|
||||
def callback_basic(self, bot, update):
|
||||
test_bot = isinstance(bot, Bot)
|
||||
test_update = isinstance(update, Update)
|
||||
self.test_flag = test_bot and test_update
|
||||
|
||||
def callback_data_1(self, bot, update, user_data=None, chat_data=None):
|
||||
self.test_flag = (user_data is not None) or (chat_data is not None)
|
||||
|
||||
def callback_data_2(self, bot, update, user_data=None, chat_data=None):
|
||||
self.test_flag = (user_data is not None) and (chat_data is not None)
|
||||
|
||||
def callback_queue_1(self, bot, update, job_queue=None, update_queue=None):
|
||||
self.test_flag = (job_queue is not None) or (update_queue is not None)
|
||||
|
||||
def callback_queue_2(self, bot, update, job_queue=None, update_queue=None):
|
||||
self.test_flag = (job_queue is not None) and (update_queue is not None)
|
||||
|
||||
def callback_context(self, update, context):
|
||||
self.test_flag = (
|
||||
isinstance(context, CallbackContext)
|
||||
and isinstance(context.bot, Bot)
|
||||
and isinstance(update, Update)
|
||||
and isinstance(context.update_queue, Queue)
|
||||
and isinstance(context.job_queue, JobQueue)
|
||||
and isinstance(context.user_data, dict)
|
||||
and isinstance(context.chat_data, dict)
|
||||
and isinstance(context.bot_data, dict)
|
||||
and isinstance(
|
||||
update.chat_join_request,
|
||||
ChatJoinRequest,
|
||||
)
|
||||
)
|
||||
|
||||
def test_basic(self, dp, chat_join_request_update):
|
||||
handler = ChatJoinRequestHandler(self.callback_basic)
|
||||
dp.add_handler(handler)
|
||||
|
||||
assert handler.check_update(chat_join_request_update)
|
||||
|
||||
dp.process_update(chat_join_request_update)
|
||||
assert self.test_flag
|
||||
|
||||
def test_pass_user_or_chat_data(self, dp, chat_join_request_update):
|
||||
handler = ChatJoinRequestHandler(self.callback_data_1, pass_user_data=True)
|
||||
dp.add_handler(handler)
|
||||
|
||||
dp.process_update(chat_join_request_update)
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
handler = ChatJoinRequestHandler(self.callback_data_1, pass_chat_data=True)
|
||||
dp.add_handler(handler)
|
||||
|
||||
self.test_flag = False
|
||||
dp.process_update(chat_join_request_update)
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
handler = ChatJoinRequestHandler(
|
||||
self.callback_data_2, pass_chat_data=True, pass_user_data=True
|
||||
)
|
||||
dp.add_handler(handler)
|
||||
|
||||
self.test_flag = False
|
||||
dp.process_update(chat_join_request_update)
|
||||
assert self.test_flag
|
||||
|
||||
def test_pass_job_or_update_queue(self, dp, chat_join_request_update):
|
||||
handler = ChatJoinRequestHandler(self.callback_queue_1, pass_job_queue=True)
|
||||
dp.add_handler(handler)
|
||||
|
||||
dp.process_update(chat_join_request_update)
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
handler = ChatJoinRequestHandler(self.callback_queue_1, pass_update_queue=True)
|
||||
dp.add_handler(handler)
|
||||
|
||||
self.test_flag = False
|
||||
dp.process_update(chat_join_request_update)
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
handler = ChatJoinRequestHandler(
|
||||
self.callback_queue_2, pass_job_queue=True, pass_update_queue=True
|
||||
)
|
||||
dp.add_handler(handler)
|
||||
|
||||
self.test_flag = False
|
||||
dp.process_update(chat_join_request_update)
|
||||
assert self.test_flag
|
||||
|
||||
def test_other_update_types(self, false_update):
|
||||
handler = ChatJoinRequestHandler(self.callback_basic)
|
||||
assert not handler.check_update(false_update)
|
||||
assert not handler.check_update(True)
|
||||
|
||||
def test_context(self, cdp, chat_join_request_update):
|
||||
handler = ChatJoinRequestHandler(callback=self.callback_context)
|
||||
cdp.add_handler(handler)
|
||||
|
||||
cdp.process_update(chat_join_request_update)
|
||||
assert self.test_flag
|
||||
@@ -1718,6 +1718,16 @@ class TestFilters:
|
||||
update.message.sender_chat = None
|
||||
assert not Filters.sender_chat.channel(update)
|
||||
|
||||
def test_filters_is_automatic_forward(self, update):
|
||||
assert not Filters.is_automatic_forward(update)
|
||||
update.message.is_automatic_forward = True
|
||||
assert Filters.is_automatic_forward(update)
|
||||
|
||||
def test_filters_has_protected_content(self, update):
|
||||
assert not Filters.has_protected_content(update)
|
||||
update.message.has_protected_content = True
|
||||
assert Filters.has_protected_content(update)
|
||||
|
||||
def test_filters_invoice(self, update):
|
||||
assert not Filters.invoice(update)
|
||||
update.message.invoice = 'test'
|
||||
|
||||
@@ -591,6 +591,21 @@ class TestSendMediaGroup:
|
||||
)
|
||||
assert isinstance(new_message, Message)
|
||||
|
||||
def test_edit_message_media_with_thumb(
|
||||
self, bot, chat_id, video_file, photo_file, monkeypatch # noqa: F811
|
||||
):
|
||||
def test(*args, **kwargs):
|
||||
data = kwargs['fields']
|
||||
video_check = data[input_video.media.attach] == input_video.media.field_tuple
|
||||
thumb_check = data[input_video.thumb.attach] == input_video.thumb.field_tuple
|
||||
result = video_check and thumb_check
|
||||
raise Exception(f"Test was {'successful' if result else 'failing'}")
|
||||
|
||||
monkeypatch.setattr('telegram.utils.request.Request._request_wrapper', test)
|
||||
input_video = InputMediaVideo(video_file, thumb=photo_file)
|
||||
with pytest.raises(Exception, match='Test was successful'):
|
||||
bot.edit_message_media(chat_id=chat_id, message_id=123, media=input_video)
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize(
|
||||
'default_bot', [{'parse_mode': ParseMode.HTML}], indirect=True, ids=['HTML-Bot']
|
||||
|
||||
@@ -180,6 +180,8 @@ def message(bot):
|
||||
)
|
||||
},
|
||||
{'sender_chat': Chat(-123, 'discussion_channel')},
|
||||
{'is_automatic_forward': True},
|
||||
{'has_protected_content': True},
|
||||
],
|
||||
ids=[
|
||||
'forwarded_user',
|
||||
@@ -229,6 +231,8 @@ def message(bot):
|
||||
'voice_chat_ended',
|
||||
'voice_chat_participants_invited',
|
||||
'sender_chat',
|
||||
'is_automatic_forward',
|
||||
'has_protected_content',
|
||||
],
|
||||
)
|
||||
def message_params(bot, request):
|
||||
|
||||
@@ -32,6 +32,7 @@ import logging
|
||||
import os
|
||||
import pickle
|
||||
from collections import defaultdict
|
||||
from collections.abc import Container
|
||||
from time import sleep
|
||||
from sys import version_info as py_ver
|
||||
|
||||
@@ -566,17 +567,32 @@ class TestBasePersistence:
|
||||
|
||||
def __init__(self):
|
||||
self.bot = bot
|
||||
self.not_in_dict = bot
|
||||
self.not_in_slots = bot
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, CustomSlottedClass):
|
||||
return self.bot is other.bot and self.not_in_dict is other.not_in_dict
|
||||
return self.bot is other.bot and self.not_in_slots is other.not_in_slots
|
||||
return False
|
||||
|
||||
class DictNotInSlots(Container):
|
||||
"""This classes parent has slots, but __dict__ is not in those slots."""
|
||||
|
||||
def __init__(self):
|
||||
self.bot = bot
|
||||
|
||||
def __contains__(self, item):
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, DictNotInSlots):
|
||||
return self.bot is other.bot
|
||||
return False
|
||||
|
||||
class CustomClass:
|
||||
def __init__(self):
|
||||
self.bot = bot
|
||||
self.slotted_object = CustomSlottedClass()
|
||||
self.dict_not_in_slots_object = DictNotInSlots()
|
||||
self.list_ = [1, 2, bot]
|
||||
self.tuple_ = tuple(self.list_)
|
||||
self.set_ = set(self.list_)
|
||||
@@ -589,7 +605,8 @@ class TestBasePersistence:
|
||||
cc = CustomClass()
|
||||
cc.bot = BasePersistence.REPLACED_BOT
|
||||
cc.slotted_object.bot = BasePersistence.REPLACED_BOT
|
||||
cc.slotted_object.not_in_dict = BasePersistence.REPLACED_BOT
|
||||
cc.slotted_object.not_in_slots = BasePersistence.REPLACED_BOT
|
||||
cc.dict_not_in_slots_object.bot = BasePersistence.REPLACED_BOT
|
||||
cc.list_ = [1, 2, BasePersistence.REPLACED_BOT]
|
||||
cc.tuple_ = tuple(cc.list_)
|
||||
cc.set_ = set(cc.list_)
|
||||
@@ -603,6 +620,7 @@ class TestBasePersistence:
|
||||
return (
|
||||
self.bot is other.bot
|
||||
and self.slotted_object == other.slotted_object
|
||||
and self.dict_not_in_slots_object == other.dict_not_in_slots_object
|
||||
and self.list_ == other.list_
|
||||
and self.tuple_ == other.tuple_
|
||||
and self.set_ == other.set_
|
||||
|
||||
+14
-1
@@ -34,6 +34,7 @@ from telegram import (
|
||||
PollOption,
|
||||
ChatMemberUpdated,
|
||||
ChatMember,
|
||||
ChatJoinRequest,
|
||||
)
|
||||
from telegram.poll import PollAnswer
|
||||
from telegram.utils.helpers import from_timestamp
|
||||
@@ -47,6 +48,14 @@ chat_member_updated = ChatMemberUpdated(
|
||||
ChatMember(User(1, '', False), ChatMember.CREATOR),
|
||||
)
|
||||
|
||||
|
||||
chat_join_request = ChatJoinRequest(
|
||||
chat=Chat(1, Chat.SUPERGROUP),
|
||||
from_user=User(1, 'first_name', False),
|
||||
date=from_timestamp(int(time.time())),
|
||||
bio='bio',
|
||||
)
|
||||
|
||||
params = [
|
||||
{'message': message},
|
||||
{'edited_message': message},
|
||||
@@ -57,11 +66,13 @@ params = [
|
||||
{'chosen_inline_result': ChosenInlineResult('id', User(1, '', False), '')},
|
||||
{'shipping_query': ShippingQuery('id', User(1, '', False), '', None)},
|
||||
{'pre_checkout_query': PreCheckoutQuery('id', User(1, '', False), '', 0, '')},
|
||||
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')},
|
||||
{'poll': Poll('id', '?', [PollOption('.', 1)], False, False, False, Poll.REGULAR, True)},
|
||||
{'poll_answer': PollAnswer("id", User(1, '', False), [1])},
|
||||
{'my_chat_member': chat_member_updated},
|
||||
{'chat_member': chat_member_updated},
|
||||
{'chat_join_request': chat_join_request},
|
||||
# Must be last to conform with `ids` below!
|
||||
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')},
|
||||
]
|
||||
|
||||
all_types = (
|
||||
@@ -78,6 +89,7 @@ all_types = (
|
||||
'poll_answer',
|
||||
'my_chat_member',
|
||||
'chat_member',
|
||||
'chat_join_request',
|
||||
)
|
||||
|
||||
ids = all_types + ('callback_query_without_message',)
|
||||
@@ -171,6 +183,7 @@ class TestUpdate:
|
||||
or update.poll_answer is not None
|
||||
or update.my_chat_member is not None
|
||||
or update.chat_member is not None
|
||||
or update.chat_join_request is not None
|
||||
):
|
||||
assert eff_message.message_id == message.message_id
|
||||
else:
|
||||
|
||||
+42
-1
@@ -18,7 +18,7 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import pytest
|
||||
|
||||
from telegram import Update, User, Bot
|
||||
from telegram import Update, User, Bot, InlineKeyboardButton
|
||||
from telegram.utils.helpers import escape_markdown
|
||||
from tests.conftest import check_shortcut_signature, check_shortcut_call, check_defaults_handling
|
||||
|
||||
@@ -430,6 +430,40 @@ class TestUser:
|
||||
monkeypatch.setattr(user.bot, 'copy_message', make_assertion)
|
||||
assert user.copy_message(chat_id='chat_id', message_id='message_id')
|
||||
|
||||
def test_instance_method_approve_join_request(self, monkeypatch, user):
|
||||
def make_assertion(*_, **kwargs):
|
||||
chat_id = kwargs['chat_id'] == 'chat_id'
|
||||
user_id = kwargs['user_id'] == user.id
|
||||
return chat_id and user_id
|
||||
|
||||
assert check_shortcut_signature(
|
||||
User.approve_join_request, Bot.approve_chat_join_request, ['user_id'], []
|
||||
)
|
||||
assert check_shortcut_call(
|
||||
user.approve_join_request, user.bot, 'approve_chat_join_request'
|
||||
)
|
||||
assert check_defaults_handling(user.approve_join_request, user.bot)
|
||||
|
||||
monkeypatch.setattr(user.bot, 'approve_chat_join_request', make_assertion)
|
||||
assert user.approve_join_request(chat_id='chat_id')
|
||||
|
||||
def test_instance_method_decline_join_request(self, monkeypatch, user):
|
||||
def make_assertion(*_, **kwargs):
|
||||
chat_id = kwargs['chat_id'] == 'chat_id'
|
||||
user_id = kwargs['user_id'] == user.id
|
||||
return chat_id and user_id
|
||||
|
||||
assert check_shortcut_signature(
|
||||
User.decline_join_request, Bot.decline_chat_join_request, ['user_id'], []
|
||||
)
|
||||
assert check_shortcut_call(
|
||||
user.decline_join_request, user.bot, 'decline_chat_join_request'
|
||||
)
|
||||
assert check_defaults_handling(user.decline_join_request, user.bot)
|
||||
|
||||
monkeypatch.setattr(user.bot, 'decline_chat_join_request', make_assertion)
|
||||
assert user.decline_join_request(chat_id='chat_id')
|
||||
|
||||
def test_mention_html(self, user):
|
||||
expected = '<a href="tg://user?id={}">{}</a>'
|
||||
|
||||
@@ -439,6 +473,13 @@ class TestUser:
|
||||
)
|
||||
assert user.mention_html(user.username) == expected.format(user.id, user.username)
|
||||
|
||||
def test_mention_button(self, user):
|
||||
expected_name = InlineKeyboardButton(text="Bob", url=f"tg://user?id={user.id}")
|
||||
expected_full = InlineKeyboardButton(text=user.full_name, url=f"tg://user?id={user.id}")
|
||||
|
||||
assert user.mention_button("Bob") == expected_name
|
||||
assert user.mention_button() == expected_full
|
||||
|
||||
def test_mention_markdown(self, user):
|
||||
expected = '[{}](tg://user?id={})'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user