mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 14:35:00 +01:00
Explicit local_mode
Setting (#3154)
This commit is contained in:
parent
aed8e68fca
commit
fdfbcdf51e
21 changed files with 801 additions and 485 deletions
|
@ -64,6 +64,9 @@ source_suffix = ".rst"
|
|||
# The master toctree document.
|
||||
master_doc = "index"
|
||||
|
||||
# Global substitutions
|
||||
rst_prolog = (Path.cwd() / "../substitutions/global.rst").read_text(encoding="utf-8")
|
||||
|
||||
# -- Extension settings ------------------------------------------------
|
||||
napoleon_use_admonition_for_examples = True
|
||||
|
||||
|
|
|
@ -304,6 +304,8 @@
|
|||
- The first name of the bot
|
||||
* - :attr:`~telegram.Bot.last_name`
|
||||
- The last name of the bot
|
||||
* - :attr:`~telegram.Bot.local_mode`
|
||||
- Whether the bot is running in local mode
|
||||
* - :attr:`~telegram.Bot.username`
|
||||
- The username of the bot, without leading ``@``
|
||||
* - :attr:`~telegram.Bot.link`
|
||||
|
|
17
docs/substitutions/global.rst
Normal file
17
docs/substitutions/global.rst
Normal file
|
@ -0,0 +1,17 @@
|
|||
.. |uploadinput| replace:: To upload a file, you can either pass a :term:`file object` (e.g. ``open("filename", "rb")``), the file contents as bytes or the path of the file (as string or :class:`pathlib.Path` object). In the latter case, the file contents will either be read as bytes or the file path will be passed to Telegram, depending on the :paramref:`~telegram.Bot.local_mode` setting.
|
||||
|
||||
.. |uploadinputnopath| replace:: To upload a file, you can either pass a :term:`file object` (e.g. ``open("filename", "rb")``) or the file contents as bytes. If the bot is running in :paramref:`~telegram.Bot.local_mode`, passing the path of the file (as string or :class:`pathlib.Path` object) is supported as well.
|
||||
|
||||
.. |fileinputbase| replace:: Pass a ``file_id`` as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one.
|
||||
|
||||
.. |fileinput| replace:: |fileinputbase| |uploadinput|
|
||||
|
||||
.. |fileinputnopath| replace:: |fileinputbase| |uploadinputnopath|
|
||||
|
||||
.. |thumbdocstringbase| replace:: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
|
||||
.. |thumbdocstring| replace:: |thumbdocstringbase| |uploadinput|
|
||||
|
||||
.. |thumbdocstringnopath| replace:: |thumbdocstringbase| |uploadinputnopath|
|
||||
|
||||
.. |editreplymarkup| replace:: It is currently only possible to edit messages without :attr:`telegram.Message.reply_markup` or with inline keyboards.
|
408
telegram/_bot.py
408
telegram/_bot.py
|
@ -99,6 +99,7 @@ from telegram.request._requestparameter import RequestParameter
|
|||
if TYPE_CHECKING:
|
||||
from telegram import (
|
||||
InlineQueryResult,
|
||||
InputFile,
|
||||
InputMediaAudio,
|
||||
InputMediaDocument,
|
||||
InputMediaPhoto,
|
||||
|
@ -160,6 +161,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
``location``, ``filename``, ``venue``, ``contact``,
|
||||
``{read, write, connect, pool}_timeout``, ``api_kwargs``. Use a named argument for those,
|
||||
and notice that some positional arguments changed position as a result.
|
||||
* For uploading files, file paths are now always accepted. If :paramref:`local_mode` is
|
||||
:obj:`False`, the file contents will be read in binary mode and uploaded. Otherwise,
|
||||
the file path will be passed in the
|
||||
`file URI scheme <https://en.wikipedia.org/wiki/File_URI_scheme>`_.
|
||||
|
||||
Args:
|
||||
token (:obj:`str`): Bot's unique authentication token.
|
||||
|
@ -175,6 +180,14 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
:class:`telegram.request.HTTPXRequest` will be used.
|
||||
private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data.
|
||||
private_key_password (:obj:`bytes`, optional): Password for above private key.
|
||||
local_mode (:obj:`bool`, optional): Set to :obj:`True`, if the :paramref:`base_url` is
|
||||
the URI of a `Local Bot API Server <https://core.telegram.org/bots/api#using-a-local\
|
||||
-bot-api-server>`_ that runs with the ``--local`` flag. Currently, the only effect of
|
||||
this is that files are uploaded using their local path in the
|
||||
`file URI scheme <https://en.wikipedia.org/wiki/File_URI_scheme>`_.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
.. versionadded:: 20.0.
|
||||
|
||||
.. include:: inclusions/bot_methods.rst
|
||||
|
||||
|
@ -189,6 +202,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
"_request",
|
||||
"_logger",
|
||||
"_initialized",
|
||||
"_local_mode",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
|
@ -200,6 +214,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
get_updates_request: BaseRequest = None,
|
||||
private_key: bytes = None,
|
||||
private_key_password: bytes = None,
|
||||
local_mode: bool = False,
|
||||
):
|
||||
if not token:
|
||||
raise InvalidToken("You must pass the token you received from https://t.me/Botfather!")
|
||||
|
@ -207,6 +222,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
|
||||
self._base_url = base_url + self._token
|
||||
self._base_file_url = base_file_url + self._token
|
||||
self._local_mode = local_mode
|
||||
self._bot_user: Optional[User] = None
|
||||
self._private_key = None
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
@ -253,6 +269,14 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
"""
|
||||
return self._base_file_url
|
||||
|
||||
@property
|
||||
def local_mode(self) -> bool:
|
||||
""":obj:`bool`: Whether this bot is running in local mode.
|
||||
|
||||
.. versionadded:: 20.0
|
||||
"""
|
||||
return self._local_mode
|
||||
|
||||
# Proper type hints are difficult because:
|
||||
# 1. cryptography doesn't have a nice base class, so it would get lengthy
|
||||
# 2. we can't import cryptography if it's not installed
|
||||
|
@ -283,6 +307,21 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
|
||||
return decorator
|
||||
|
||||
def _parse_file_input(
|
||||
self,
|
||||
file_input: Union[FileInput, "TelegramObject"],
|
||||
tg_type: Type["TelegramObject"] = None,
|
||||
filename: str = None,
|
||||
attach: bool = False,
|
||||
) -> Union[str, "InputFile", Any]:
|
||||
return parse_file_input(
|
||||
file_input=file_input,
|
||||
tg_type=tg_type,
|
||||
filename=filename,
|
||||
attach=attach,
|
||||
local_mode=self._local_mode,
|
||||
)
|
||||
|
||||
def _insert_defaults(self, data: Dict[str, object]) -> None: # skipcq: PYL-R0201
|
||||
"""This method is here to make ext.Defaults work. Because we need to be able to tell
|
||||
e.g. `send_message(chat_id, text)` from `send_message(chat_id, text, parse_mode=None)`, the
|
||||
|
@ -888,10 +927,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
) -> Message:
|
||||
"""Use this method to send photos.
|
||||
|
||||
Note:
|
||||
The photo argument can be either a file_id, an URL or a file from disk
|
||||
``open(filename, 'rb')``
|
||||
|
||||
.. seealso:: :attr:`telegram.Message.reply_photo`, :attr:`telegram.Chat.send_photo`,
|
||||
:attr:`telegram.User.send_photo`
|
||||
|
||||
|
@ -900,13 +935,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
of the target channel (in the format ``@channelusername``).
|
||||
photo (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.PhotoSize`): Photo to send.
|
||||
Pass a file_id as String to send a photo that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL as a String for Telegram to get a photo from the
|
||||
Internet, or upload a new photo using multipart/form-data. Lastly you can pass
|
||||
an existing :class:`telegram.PhotoSize` object to send.
|
||||
|fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
caption (:obj:`str`, optional): Photo caption (may also be used when resending photos
|
||||
by file_id), 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH`
|
||||
characters after entities parsing.
|
||||
|
@ -961,7 +998,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
"""
|
||||
data: JSONDict = {
|
||||
"chat_id": chat_id,
|
||||
"photo": parse_file_input(photo, PhotoSize, filename=filename),
|
||||
"photo": self._parse_file_input(photo, PhotoSize, filename=filename),
|
||||
"parse_mode": parse_mode,
|
||||
}
|
||||
|
||||
|
@ -1021,10 +1058,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
|
||||
For sending voice messages, use the :meth:`send_voice` method instead.
|
||||
|
||||
Note:
|
||||
The audio argument can be either a file_id, an URL or a file from disk
|
||||
``open(filename, 'rb')``
|
||||
|
||||
.. seealso:: :attr:`telegram.Message.reply_audio`, :attr:`telegram.Chat.send_audio`,
|
||||
:attr:`telegram.User.send_audio`
|
||||
|
||||
|
@ -1033,13 +1066,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
of the target channel (in the format ``@channelusername``).
|
||||
audio (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Audio`): Audio file to send.
|
||||
Pass a file_id as String to send an audio file that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL as a String for Telegram to get an audio file from
|
||||
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
|
||||
an existing :class:`telegram.Audio` object to send.
|
||||
|fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.Audio` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
caption (:obj:`str`, optional): Audio caption,
|
||||
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
|
||||
entities parsing.
|
||||
|
@ -1067,16 +1102,16 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
|
||||
Additional interface options. An object for an inline keyboard, custom reply
|
||||
keyboard, instructions to remove reply keyboard or to force a reply from the user.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail
|
||||
of the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||
optional): |thumbdocstring|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
|
||||
Keyword Args:
|
||||
filename (:obj:`str`, optional): Custom file name for the audio, when uploading a
|
||||
new file. Convenience parameter, useful e.g. when sending files generated by the
|
||||
|
@ -1106,7 +1141,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
"""
|
||||
data: JSONDict = {
|
||||
"chat_id": chat_id,
|
||||
"audio": parse_file_input(audio, Audio, filename=filename),
|
||||
"audio": self._parse_file_input(audio, Audio, filename=filename),
|
||||
"parse_mode": parse_mode,
|
||||
}
|
||||
|
||||
|
@ -1122,7 +1157,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
if caption_entities:
|
||||
data["caption_entities"] = caption_entities
|
||||
if thumb:
|
||||
data["thumb"] = parse_file_input(thumb, attach=True)
|
||||
data["thumb"] = self._parse_file_input(thumb, attach=True)
|
||||
|
||||
return await self._send_message( # type: ignore[return-value]
|
||||
"sendAudio",
|
||||
|
@ -1169,12 +1204,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
:tg-const:`telegram.constants.FileSizeLimit.FILESIZE_UPLOAD` in size, this limit may be
|
||||
changed in the future.
|
||||
|
||||
Note:
|
||||
* The document argument can be either a file_id, an URL or a file from disk
|
||||
``open(filename, 'rb')``.
|
||||
|
||||
* Sending by URL will currently only work ``GIF``, ``PDF`` & ``ZIP`` files.
|
||||
|
||||
.. seealso:: :attr:`telegram.Message.reply_document`, :attr:`telegram.Chat.send_document`,
|
||||
:attr:`telegram.User.send_document`
|
||||
|
||||
|
@ -1183,13 +1212,18 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
of the target channel (in the format ``@channelusername``).
|
||||
document (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Document`): File to send.
|
||||
Pass a file_id as String to send a file that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL as a String for Telegram to get a file from the
|
||||
Internet, or upload a new one using multipart/form-data. Lastly you can pass
|
||||
an existing :class:`telegram.Document` object to send.
|
||||
|fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.Document` object to send.
|
||||
|
||||
Note:
|
||||
Sending by URL will currently only work ``GIF``, ``PDF`` & ``ZIP`` files.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
caption (:obj:`str`, optional): Document caption (may also be used when resending
|
||||
documents by file_id), 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH`
|
||||
characters after entities parsing.
|
||||
|
@ -1216,16 +1250,16 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
|
||||
Additional interface options. An object for an inline keyboard, custom reply
|
||||
keyboard, instructions to remove reply keyboard or to force a reply from the user.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail
|
||||
of the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||
optional): |thumbdocstring|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
|
||||
Keyword Args:
|
||||
filename (:obj:`str`, optional): Custom file name for the document, when uploading a
|
||||
new file. Convenience parameter, useful e.g. when sending files generated by the
|
||||
|
@ -1253,7 +1287,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
"""
|
||||
data: JSONDict = {
|
||||
"chat_id": chat_id,
|
||||
"document": parse_file_input(document, Document, filename=filename),
|
||||
"document": self._parse_file_input(document, Document, filename=filename),
|
||||
"parse_mode": parse_mode,
|
||||
}
|
||||
|
||||
|
@ -1265,7 +1299,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
if disable_content_type_detection is not None:
|
||||
data["disable_content_type_detection"] = disable_content_type_detection
|
||||
if thumb:
|
||||
data["thumb"] = parse_file_input(thumb, attach=True)
|
||||
data["thumb"] = self._parse_file_input(thumb, attach=True)
|
||||
|
||||
return await self._send_message( # type: ignore[return-value]
|
||||
"sendDocument",
|
||||
|
@ -1302,10 +1336,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
"""
|
||||
Use this method to send static ``.WEBP``, animated ``.TGS``, or video ``.WEBM`` stickers.
|
||||
|
||||
Note:
|
||||
The :paramref:`sticker` argument can be either a file_id, an URL or a file from disk
|
||||
``open(filename, 'rb')``
|
||||
|
||||
.. seealso:: :attr:`telegram.Message.reply_sticker`, :attr:`telegram.Chat.send_sticker`,
|
||||
:attr:`telegram.User.send_sticker`
|
||||
|
||||
|
@ -1314,13 +1344,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
of the target channel (in the format ``@channelusername``).
|
||||
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Sticker`): Sticker to send.
|
||||
Pass a file_id as String to send a file that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL as a String for Telegram to get a .webp file from
|
||||
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
|
||||
an existing :class:`telegram.Sticker` object to send.
|
||||
|fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.Sticker` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
|
||||
receive a notification with no sound.
|
||||
protect_content (:obj:`bool`, optional): Protects the contents of the sent message from
|
||||
|
@ -1359,7 +1391,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
data: JSONDict = {"chat_id": chat_id, "sticker": parse_file_input(sticker, Sticker)}
|
||||
data: JSONDict = {"chat_id": chat_id, "sticker": self._parse_file_input(sticker, Sticker)}
|
||||
return await self._send_message( # type: ignore[return-value]
|
||||
"sendSticker",
|
||||
data,
|
||||
|
@ -1410,9 +1442,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
changed in the future.
|
||||
|
||||
Note:
|
||||
* The :paramref:`video` argument can be either a file_id, an URL or a file from disk
|
||||
``open(filename, 'rb')``
|
||||
* :paramref:`thumb` will be ignored for small video files, for which Telegram can
|
||||
:paramref:`thumb` will be ignored for small video files, for which Telegram can
|
||||
easily generate thumbnails. However, this behaviour is undocumented and might be
|
||||
changed by Telegram.
|
||||
|
||||
|
@ -1424,13 +1454,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
of the target channel (in the format ``@channelusername``).
|
||||
video (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Video`): Video file to send.
|
||||
Pass a file_id as String to send an video file that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL as a String for Telegram to get an video file from
|
||||
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
|
||||
an existing :class:`telegram.Video` object to send.
|
||||
|fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
duration (:obj:`int`, optional): Duration of sent video in seconds.
|
||||
width (:obj:`int`, optional): Video width.
|
||||
height (:obj:`int`, optional): Video height.
|
||||
|
@ -1460,16 +1492,16 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
|
||||
Additional interface options. An object for an inline keyboard, custom reply
|
||||
keyboard, instructions to remove reply keyboard or to force a reply from the user.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail
|
||||
of the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||
optional): |thumbdocstring|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
|
||||
Keyword Args:
|
||||
filename (:obj:`str`, optional): Custom file name for the video, when uploading a
|
||||
new file. Convenience parameter, useful e.g. when sending files generated by the
|
||||
|
@ -1499,7 +1531,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
"""
|
||||
data: JSONDict = {
|
||||
"chat_id": chat_id,
|
||||
"video": parse_file_input(video, Video, filename=filename),
|
||||
"video": self._parse_file_input(video, Video, filename=filename),
|
||||
"parse_mode": parse_mode,
|
||||
}
|
||||
|
||||
|
@ -1516,7 +1548,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
if height:
|
||||
data["height"] = height
|
||||
if thumb:
|
||||
data["thumb"] = parse_file_input(thumb, attach=True)
|
||||
data["thumb"] = self._parse_file_input(thumb, attach=True)
|
||||
|
||||
return await self._send_message( # type: ignore[return-value]
|
||||
"sendVideo",
|
||||
|
@ -1559,9 +1591,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
Use this method to send video messages.
|
||||
|
||||
Note:
|
||||
* The :paramref:`video_note` argument can be either a file_id or a file from disk
|
||||
``open(filename, 'rb')``
|
||||
* :paramref:`thumb` will be ignored for small video files, for which Telegram can
|
||||
:paramref:`thumb` will be ignored for small video files, for which Telegram can
|
||||
easily generate thumbnails. However, this behaviour is undocumented and might be
|
||||
changed by Telegram.
|
||||
|
||||
|
@ -1573,14 +1603,19 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target channel (in the format ``@channelusername``).
|
||||
video_note (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.VideoNote`): Video note
|
||||
to send. Pass a file_id as String to send a video note that exists on the Telegram
|
||||
servers (recommended) or upload a new video using multipart/form-data. Or you can
|
||||
pass an existing :class:`telegram.VideoNote` object to send. Sending video notes by
|
||||
a URL is currently unsupported.
|
||||
:class:`telegram.VideoNote`): Video note to send.
|
||||
Pass a file_id as String to send a video note that exists on the Telegram
|
||||
servers (recommended) or upload a new video using multipart/form-data.
|
||||
|uploadinput|
|
||||
Lastly you can pass an existing :class:`telegram.VideoNote` object to send.
|
||||
Sending video notes by a URL is currently unsupported.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
duration (:obj:`int`, optional): Duration of sent video in seconds.
|
||||
length (:obj:`int`, optional): Video width and height, i.e. diameter of the video
|
||||
message.
|
||||
|
@ -1599,16 +1634,16 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
|
||||
Additional interface options. An object for an inline keyboard, custom reply
|
||||
keyboard, instructions to remove reply keyboard or to force a reply from the user.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail
|
||||
of the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||
optional): |thumbdocstring|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
|
||||
Keyword Args:
|
||||
filename (:obj:`str`, optional): Custom file name for the video note, when uploading a
|
||||
new file. Convenience parameter, useful e.g. when sending files generated by the
|
||||
|
@ -1638,7 +1673,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
"""
|
||||
data: JSONDict = {
|
||||
"chat_id": chat_id,
|
||||
"video_note": parse_file_input(video_note, VideoNote, filename=filename),
|
||||
"video_note": self._parse_file_input(video_note, VideoNote, filename=filename),
|
||||
}
|
||||
|
||||
if duration is not None:
|
||||
|
@ -1646,7 +1681,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
if length is not None:
|
||||
data["length"] = length
|
||||
if thumb:
|
||||
data["thumb"] = parse_file_input(thumb, attach=True)
|
||||
data["thumb"] = self._parse_file_input(thumb, attach=True)
|
||||
|
||||
return await self._send_message( # type: ignore[return-value]
|
||||
"sendVideoNote",
|
||||
|
@ -1696,7 +1731,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
|
||||
Note:
|
||||
:paramref:`thumb` will be ignored for small files, for which Telegram can easily
|
||||
generate thumb nails. However, this behaviour is undocumented and might be changed
|
||||
generate thumbnails. However, this behaviour is undocumented and might be changed
|
||||
by Telegram.
|
||||
|
||||
.. seealso:: :attr:`telegram.Message.reply_animation`,
|
||||
|
@ -1707,10 +1742,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target channel (in the format ``@channelusername``).
|
||||
animation (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Animation`): Animation to
|
||||
send. Pass a file_id as String to send an animation that exists on the Telegram
|
||||
servers (recommended), pass an HTTP URL as a String for Telegram to get an
|
||||
animation from the Internet, or upload a new animation using multipart/form-data.
|
||||
:class:`telegram.Animation`): Animation to send.
|
||||
|fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.Animation` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
|
@ -1718,15 +1751,16 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
duration (:obj:`int`, optional): Duration of sent animation in seconds.
|
||||
width (:obj:`int`, optional): Animation width.
|
||||
height (:obj:`int`, optional): Animation height.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail
|
||||
of the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||
optional): |thumbdocstring|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
|
||||
caption (:obj:`str`, optional): Animation caption (may also be used when resending
|
||||
animations by file_id),
|
||||
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
|
||||
|
@ -1782,7 +1816,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
"""
|
||||
data: JSONDict = {
|
||||
"chat_id": chat_id,
|
||||
"animation": parse_file_input(animation, Animation, filename=filename),
|
||||
"animation": self._parse_file_input(animation, Animation, filename=filename),
|
||||
"parse_mode": parse_mode,
|
||||
}
|
||||
|
||||
|
@ -1793,7 +1827,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
if height:
|
||||
data["height"] = height
|
||||
if thumb:
|
||||
data["thumb"] = parse_file_input(thumb, attach=True)
|
||||
data["thumb"] = self._parse_file_input(thumb, attach=True)
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
if caption_entities:
|
||||
|
@ -1844,10 +1878,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
in size, this limit may be changed in the future.
|
||||
|
||||
Note:
|
||||
* The :paramref:`voice` argument can be either a file_id, an URL or a file from disk
|
||||
``open(filename, 'rb')``.
|
||||
|
||||
* To use this method, the file must have the type :mimetype:`audio/ogg` and be no more
|
||||
To use this method, the file must have the type :mimetype:`audio/ogg` and be no more
|
||||
than ``1MB`` in size. ``1-20MB`` voice notes will be sent as files.
|
||||
|
||||
.. seealso:: :attr:`telegram.Message.reply_voice`, :attr:`telegram.Chat.send_voice`,
|
||||
|
@ -1858,13 +1889,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
of the target channel (in the format ``@channelusername``).
|
||||
voice (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Voice`): Voice file to send.
|
||||
Pass a file_id as String to send an voice file that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL as a String for Telegram to get an voice file from
|
||||
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
|
||||
an existing :class:`telegram.Voice` object to send.
|
||||
|fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.Voice` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
caption (:obj:`str`, optional): Voice message caption,
|
||||
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
|
||||
entities parsing.
|
||||
|
@ -1920,7 +1953,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
"""
|
||||
data: JSONDict = {
|
||||
"chat_id": chat_id,
|
||||
"voice": parse_file_input(voice, Voice, filename=filename),
|
||||
"voice": self._parse_file_input(voice, Voice, filename=filename),
|
||||
"parse_mode": parse_mode,
|
||||
}
|
||||
|
||||
|
@ -3502,8 +3535,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
Use this method to edit text and game messages.
|
||||
|
||||
Note:
|
||||
It is currently only possible to edit messages without
|
||||
:attr:`telegram.Message.reply_markup` or with inline keyboards.
|
||||
|editreplymarkup|.
|
||||
|
||||
.. seealso:: :attr:`telegram.Message.edit_text`,
|
||||
:attr:`telegram.CallbackQuery.edit_message_text`
|
||||
|
@ -3601,8 +3633,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
Use this method to edit captions of messages.
|
||||
|
||||
Note:
|
||||
It is currently only possible to edit messages without
|
||||
:attr:`telegram.Message.reply_markup` or with inline keyboards
|
||||
|editreplymarkup|
|
||||
|
||||
.. seealso:: :attr:`telegram.Message.edit_caption`,
|
||||
:attr:`telegram.CallbackQuery.edit_message_caption`
|
||||
|
@ -3698,8 +3729,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
:attr:`~telegram.File.file_id` or specify a URL.
|
||||
|
||||
Note:
|
||||
It is currently only possible to edit messages without
|
||||
:attr:`telegram.Message.reply_markup` or with inline keyboards
|
||||
|editreplymarkup|
|
||||
|
||||
.. seealso:: :attr:`telegram.Message.edit_media`,
|
||||
:attr:`telegram.CallbackQuery.edit_message_media`
|
||||
|
@ -3779,8 +3809,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
(for inline bots).
|
||||
|
||||
Note:
|
||||
It is currently only possible to edit messages without
|
||||
:attr:`telegram.Message.reply_markup` or with inline keyboards
|
||||
|editreplymarkup|
|
||||
|
||||
.. seealso:: :attr:`telegram.Message.edit_reply_markup`,
|
||||
:attr:`telegram.CallbackQuery.edit_message_reply_markup`
|
||||
|
@ -3856,6 +3885,12 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
) -> List[Update]:
|
||||
"""Use this method to receive incoming updates using long polling.
|
||||
|
||||
Note:
|
||||
1. This method will not work if an outgoing webhook is set up.
|
||||
2. In order to avoid getting duplicate updates, recalculate offset after each
|
||||
server response.
|
||||
3. To take full advantage of this library take a look at :class:`telegram.ext.Updater`
|
||||
|
||||
Args:
|
||||
offset (:obj:`int`, optional): Identifier of the first update to be returned. Must be
|
||||
greater by one than the highest among the identifiers of previously received
|
||||
|
@ -3895,12 +3930,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
Note:
|
||||
1. This method will not work if an outgoing webhook is set up.
|
||||
2. In order to avoid getting duplicate updates, recalculate offset after each
|
||||
server response.
|
||||
3. To take full advantage of this library take a look at :class:`telegram.ext.Updater`
|
||||
|
||||
Returns:
|
||||
List[:class:`telegram.Update`]
|
||||
|
||||
|
@ -3970,15 +3999,25 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
``X-Telegram-Bot-Api-Secret-Token`` with the secret token as content.
|
||||
|
||||
Note:
|
||||
The certificate argument should be a file from disk ``open(filename, 'rb')``.
|
||||
1. You will not be able to receive updates using :meth:`get_updates` for long as an
|
||||
outgoing webhook is set up.
|
||||
2. To use a self-signed certificate, you need to upload your public key certificate
|
||||
using :paramref:`certificate` parameter. Please upload as
|
||||
:class:`~telegram.InputFile`, sending a String will not work.
|
||||
3. Ports currently supported for Webhooks:
|
||||
:attr:`telegram.constants.SUPPORTED_WEBHOOK_PORTS`.
|
||||
|
||||
If you're having any trouble setting up webhooks, please check out this `guide to
|
||||
Webhooks`_.
|
||||
|
||||
Args:
|
||||
url (:obj:`str`): HTTPS url to send updates to. Use an empty string to remove webhook
|
||||
integration.
|
||||
certificate (:term:`file object`): Upload your public key certificate so that the root
|
||||
certificate in use can be checked. See our self-signed guide for details.
|
||||
(https://github.com/python-telegram-bot/python-telegram-bot/wiki/Webhooks#\
|
||||
creating-a-self-signed-certificate-using-openssl)
|
||||
certificate (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`):
|
||||
Upload your public key certificate so that the root
|
||||
certificate in use can be checked. See our `self-signed guide <https://github.com/\
|
||||
python-telegram-bot/python-telegram-bot/wiki/Webhooks#creating-a-self-signed-\
|
||||
certificate-using-openssl>`_ for details. |uploadinputnopath|
|
||||
ip_address (:obj:`str`, optional): The fixed IP address which will be used to send
|
||||
webhook requests instead of the IP address resolved through DNS.
|
||||
max_connections (:obj:`int`, optional): Maximum allowed number of simultaneous HTTPS
|
||||
|
@ -4021,18 +4060,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
Note:
|
||||
1. You will not be able to receive updates using :meth:`get_updates` for long as an
|
||||
outgoing webhook is set up.
|
||||
2. To use a self-signed certificate, you need to upload your public key certificate
|
||||
using certificate parameter. Please upload as InputFile, sending a String will not
|
||||
work.
|
||||
3. Ports currently supported for Webhooks:
|
||||
:attr:`telegram.constants.SUPPORTED_WEBHOOK_PORTS`.
|
||||
|
||||
If you're having any trouble setting up webhooks, please check out this `guide to
|
||||
Webhooks`_.
|
||||
|
||||
Returns:
|
||||
:obj:`bool` On success, :obj:`True` is returned.
|
||||
|
||||
|
@ -4045,7 +4072,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
data: JSONDict = {"url": url}
|
||||
|
||||
if certificate:
|
||||
data["certificate"] = parse_file_input(certificate)
|
||||
data["certificate"] = self._parse_file_input(certificate)
|
||||
if max_connections is not None:
|
||||
data["max_connections"] = max_connections
|
||||
if allowed_updates is not None:
|
||||
|
@ -5492,6 +5519,13 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
link is revoked. The bot must be an administrator in the chat for this to work and must
|
||||
have the appropriate admin rights.
|
||||
|
||||
Note:
|
||||
Each administrator in a chat generates their own invite links. Bots can't use invite
|
||||
links generated by other administrators. If you want your bot to work with invite
|
||||
links, it will need to generate its own link using :meth:`export_chat_invite_link` or
|
||||
by calling the :meth:`get_chat` method. If your bot needs to generate a new primary
|
||||
invite link replacing its previous one, use :attr:`export_chat_invite_link` again.
|
||||
|
||||
.. seealso:: :attr:`telegram.Chat.export_invite_link`
|
||||
|
||||
Args:
|
||||
|
@ -5514,13 +5548,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
Note:
|
||||
Each administrator in a chat generates their own invite links. Bots can't use invite
|
||||
links generated by other administrators. If you want your bot to work with invite
|
||||
links, it will need to generate its own link using :meth:`export_chat_invite_link` or
|
||||
by calling the :meth:`get_chat` method. If your bot needs to generate a new primary
|
||||
invite link replacing its previous one, use :attr:`export_chat_invite_link` again.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: New invite link on success.
|
||||
|
||||
|
@ -5955,10 +5982,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target channel (in the format ``@channelusername``).
|
||||
photo (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`): New chat photo.
|
||||
|uploadinput|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
|
||||
Keyword Args:
|
||||
read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
|
||||
:paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to
|
||||
|
@ -5982,7 +6014,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
|||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
data: JSONDict = {"chat_id": chat_id, "photo": parse_file_input(photo)}
|
||||
data: JSONDict = {"chat_id": chat_id, "photo": self._parse_file_input(photo)}
|
||||
result = await self._post(
|
||||
"setChatPhoto",
|
||||
data,
|
||||
|
@ -6488,19 +6520,20 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
:meth:`create_new_sticker_set` and :meth:`add_sticker_to_set` methods (can be used multiple
|
||||
times).
|
||||
|
||||
Note:
|
||||
The :paramref:`png_sticker` argument can be either a file_id, an URL or a file from
|
||||
disk ``open(filename, 'rb')``
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): User identifier of sticker file owner.
|
||||
png_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`):
|
||||
**PNG** image with the sticker, must be up to 512 kilobytes in size,
|
||||
dimensions must not exceed 512px, and either width or height must be exactly 512px.
|
||||
|uploadinput|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
|
||||
Keyword Args:
|
||||
read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
|
||||
:paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to
|
||||
|
@ -6524,7 +6557,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
data: JSONDict = {"user_id": user_id, "png_sticker": parse_file_input(png_sticker)}
|
||||
data: JSONDict = {"user_id": user_id, "png_sticker": self._parse_file_input(png_sticker)}
|
||||
result = await self._post(
|
||||
"uploadStickerFile",
|
||||
data,
|
||||
|
@ -6566,10 +6599,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
of the arguments had to be changed. Use keyword arguments to make sure that the
|
||||
arguments are passed correctly.
|
||||
|
||||
Note:
|
||||
The :paramref:`png_sticker` and :paramref:`tgs_sticker` argument can be either a
|
||||
file_id, an URL or a file from disk ``open(filename, 'rb')``
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
The parameter ``contains_masks`` has been removed. Use :paramref:`sticker_type`
|
||||
instead.
|
||||
|
@ -6585,27 +6614,37 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
png_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
|
||||
optional): **PNG** image with the sticker,
|
||||
must be up to 512 kilobytes in size, dimensions must not exceed 512px,
|
||||
and either width or height must be exactly 512px. Pass a file_id as a String to
|
||||
send a file that already exists on the Telegram servers, pass an HTTP URL as a
|
||||
String for Telegram to get a file from the Internet, or upload a new one
|
||||
using multipart/form-data.
|
||||
and either width or height must be exactly 512px.
|
||||
|fileinput|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
tgs_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
|
||||
optional): **TGS** animation with the sticker, uploaded using multipart/form-data.
|
||||
optional): **TGS** animation with the sticker. |uploadinput|
|
||||
See https://core.telegram.org/stickers#animated-sticker-requirements for technical
|
||||
requirements.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
webm_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`,\
|
||||
optional): **WEBM** video with the sticker, uploaded using multipart/form-data.
|
||||
optional): **WEBM** video with the sticker. |uploadinput|
|
||||
See https://core.telegram.org/stickers#video-sticker-requirements for
|
||||
technical requirements.
|
||||
|
||||
.. versionadded:: 13.11
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
|
||||
emojis (:obj:`str`): One or more emoji corresponding to the sticker.
|
||||
mask_position (:class:`telegram.MaskPosition`, optional): Position where the mask
|
||||
should be placed on faces.
|
||||
|
@ -6642,11 +6681,11 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
data: JSONDict = {"user_id": user_id, "name": name, "title": title, "emojis": emojis}
|
||||
|
||||
if png_sticker is not None:
|
||||
data["png_sticker"] = parse_file_input(png_sticker)
|
||||
data["png_sticker"] = self._parse_file_input(png_sticker)
|
||||
if tgs_sticker is not None:
|
||||
data["tgs_sticker"] = parse_file_input(tgs_sticker)
|
||||
data["tgs_sticker"] = self._parse_file_input(tgs_sticker)
|
||||
if webm_sticker is not None:
|
||||
data["webm_sticker"] = parse_file_input(webm_sticker)
|
||||
data["webm_sticker"] = self._parse_file_input(webm_sticker)
|
||||
if mask_position is not None:
|
||||
data["mask_position"] = mask_position
|
||||
if sticker_type is not None:
|
||||
|
@ -6693,10 +6732,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
of the arguments had to be changed. Use keyword arguments to make sure that the
|
||||
arguments are passed correctly.
|
||||
|
||||
Note:
|
||||
The :paramref:`png_sticker` and :paramref:`tgs_sticker` argument can be either a
|
||||
file_id, an URL or a file from disk ``open(filename, 'rb')``
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): User identifier of created sticker set owner.
|
||||
|
||||
|
@ -6704,26 +6739,36 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
png_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
|
||||
optional): **PNG** image with the sticker,
|
||||
must be up to 512 kilobytes in size, dimensions must not exceed 512px,
|
||||
and either width or height must be exactly 512px. Pass a file_id as a String to
|
||||
send a file that already exists on the Telegram servers, pass an HTTP URL as a
|
||||
String for Telegram to get a file from the Internet, or upload a new one
|
||||
using multipart/form-data.
|
||||
and either width or height must be exactly 512px.
|
||||
|fileinput|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
tgs_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
|
||||
optional): **TGS** animation with the sticker, uploaded using multipart/form-data.
|
||||
optional): **TGS** animation with the sticker. |uploadinput|
|
||||
See https://core.telegram.org/stickers#animated-sticker-requirements for technical
|
||||
requirements.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
webm_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`,\
|
||||
optional): **WEBM** video with the sticker, uploaded using multipart/form-data.
|
||||
optional): **WEBM** video with the sticker. |uploadinput|
|
||||
See https://core.telegram.org/stickers#video-sticker-requirements for
|
||||
technical requirements.
|
||||
|
||||
.. versionadded:: 13.11
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
File paths as input is also accepted for bots *not* running in
|
||||
:paramref:`~telegram.Bot.local_mode`.
|
||||
emojis (:obj:`str`): One or more emoji corresponding to the sticker.
|
||||
mask_position (:class:`telegram.MaskPosition`, optional): Position where the mask
|
||||
should be placed on faces.
|
||||
|
@ -6754,11 +6799,11 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
data: JSONDict = {"user_id": user_id, "name": name, "emojis": emojis}
|
||||
|
||||
if png_sticker is not None:
|
||||
data["png_sticker"] = parse_file_input(png_sticker)
|
||||
data["png_sticker"] = self._parse_file_input(png_sticker)
|
||||
if tgs_sticker is not None:
|
||||
data["tgs_sticker"] = parse_file_input(tgs_sticker)
|
||||
data["tgs_sticker"] = self._parse_file_input(tgs_sticker)
|
||||
if webm_sticker is not None:
|
||||
data["webm_sticker"] = parse_file_input(webm_sticker)
|
||||
data["webm_sticker"] = self._parse_file_input(webm_sticker)
|
||||
if mask_position is not None:
|
||||
data["mask_position"] = mask_position
|
||||
|
||||
|
@ -6895,10 +6940,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
for animated sticker sets only. Video thumbnails can be set only for video sticker sets
|
||||
only.
|
||||
|
||||
Note:
|
||||
The :paramref:`thumb` can be either a file_id, an URL or a file from disk
|
||||
``open(filename, 'rb')``
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): Sticker set name
|
||||
user_id (:obj:`int`): User identifier of created sticker set owner.
|
||||
|
@ -6910,9 +6951,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
sticker technical requirements, or a **WEBM** video with the thumbnail up to 32
|
||||
kilobytes in size; see
|
||||
https://core.telegram.org/stickers#video-sticker-requirements for video sticker
|
||||
technical requirements. Pass a file_id as a String to send a file that
|
||||
already exists on the Telegram servers, pass an HTTP URL as a String for Telegram
|
||||
to get a file from the Internet, or upload a new one using multipart/form-data.
|
||||
technical requirements.
|
||||
|fileinput|
|
||||
Animated sticker set thumbnails can't be uploaded via HTTP URL.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
|
@ -6943,7 +6983,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
"""
|
||||
data: JSONDict = {"name": name, "user_id": user_id}
|
||||
if thumb is not None:
|
||||
data["thumb"] = parse_file_input(thumb)
|
||||
data["thumb"] = self._parse_file_input(thumb)
|
||||
|
||||
result = await self._post(
|
||||
"setStickerSetThumb",
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
|
||||
import logging
|
||||
import mimetypes
|
||||
from pathlib import Path
|
||||
from typing import IO, Optional, Union
|
||||
from uuid import uuid4
|
||||
|
||||
from telegram._utils.files import load_file
|
||||
from telegram._utils.types import FieldTuple
|
||||
|
||||
_DEFAULT_MIME_TYPE = "application/octet-stream"
|
||||
|
@ -75,15 +75,10 @@ class InputFile:
|
|||
elif isinstance(obj, str):
|
||||
self.input_file_content = obj.encode("utf-8")
|
||||
else:
|
||||
self.input_file_content = obj.read()
|
||||
self.attach_name: Optional[str] = "attached" + uuid4().hex if attach else None
|
||||
reported_filename, self.input_file_content = load_file(obj)
|
||||
filename = filename or reported_filename
|
||||
|
||||
if (
|
||||
not filename
|
||||
and hasattr(obj, "name")
|
||||
and not isinstance(obj.name, int) # type: ignore[union-attr]
|
||||
):
|
||||
filename = Path(obj.name).name # type: ignore[union-attr]
|
||||
self.attach_name: Optional[str] = "attached" + uuid4().hex if attach else None
|
||||
|
||||
if filename:
|
||||
self.mimetype = mimetypes.guess_type(filename, strict=False)[0] or _DEFAULT_MIME_TYPE
|
||||
|
|
|
@ -48,9 +48,8 @@ class InputMedia(TelegramObject):
|
|||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Animation` | :class:`telegram.Audio` | \
|
||||
:class:`telegram.Document` | :class:`telegram.PhotoSize` | \
|
||||
:class:`telegram.Video`):
|
||||
File to send. Pass a file_id to send a file that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL for Telegram to get a file from the Internet.
|
||||
:class:`telegram.Video`): File to send.
|
||||
|fileinputnopath|
|
||||
Lastly you can pass an existing telegram media object of the corresponding type
|
||||
to send.
|
||||
caption (:obj:`str`, optional): Caption of the media to be sent,
|
||||
|
@ -99,7 +98,11 @@ class InputMedia(TelegramObject):
|
|||
|
||||
@staticmethod
|
||||
def _parse_thumb_input(thumb: Optional[FileInput]) -> Optional[Union[str, InputFile]]:
|
||||
return parse_file_input(thumb, attach=True) if thumb is not None else thumb
|
||||
# We use local_mode=True because we don't have access to the actual setting and want
|
||||
# things to work in local mode.
|
||||
return (
|
||||
parse_file_input(thumb, attach=True, local_mode=True) if thumb is not None else thumb
|
||||
)
|
||||
|
||||
|
||||
class InputMediaAnimation(InputMedia):
|
||||
|
@ -112,10 +115,8 @@ class InputMediaAnimation(InputMedia):
|
|||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Animation`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Animation` object to send.
|
||||
:class:`telegram.Animation`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.Animation` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
@ -124,12 +125,8 @@ class InputMediaAnimation(InputMedia):
|
|||
:obj:`tempfile` module.
|
||||
|
||||
.. versionadded:: 13.1
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of
|
||||
the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than ``200`` kB in size. A thumbnail's width and height should
|
||||
not exceed ``320``. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||
optional): |thumbdocstringnopath|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
@ -180,7 +177,9 @@ class InputMediaAnimation(InputMedia):
|
|||
duration = media.duration if duration is None else duration
|
||||
media = media.file_id
|
||||
else:
|
||||
media = parse_file_input(media, filename=filename, attach=True)
|
||||
# We use local_mode=True because we don't have access to the actual setting and want
|
||||
# things to work in local mode.
|
||||
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
|
||||
|
||||
super().__init__(InputMediaType.ANIMATION, media, caption, caption_entities, parse_mode)
|
||||
self.thumb = self._parse_thumb_input(thumb)
|
||||
|
@ -194,10 +193,8 @@ class InputMediaPhoto(InputMedia):
|
|||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.PhotoSize`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.PhotoSize` object to send.
|
||||
:class:`telegram.PhotoSize`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
@ -236,7 +233,9 @@ class InputMediaPhoto(InputMedia):
|
|||
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
|
||||
filename: str = None,
|
||||
):
|
||||
media = parse_file_input(media, PhotoSize, filename=filename, attach=True)
|
||||
# We use local_mode=True because we don't have access to the actual setting and want
|
||||
# things to work in local mode.
|
||||
media = parse_file_input(media, PhotoSize, filename=filename, attach=True, local_mode=True)
|
||||
super().__init__(InputMediaType.PHOTO, media, caption, caption_entities, parse_mode)
|
||||
|
||||
|
||||
|
@ -253,10 +252,8 @@ class InputMediaVideo(InputMedia):
|
|||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Video`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Video` object to send.
|
||||
:class:`telegram.Video`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
@ -279,12 +276,8 @@ class InputMediaVideo(InputMedia):
|
|||
duration (:obj:`int`, optional): Video duration in seconds.
|
||||
supports_streaming (:obj:`bool`, optional): Pass :obj:`True`, if the uploaded video is
|
||||
suitable for streaming.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of
|
||||
the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than ``200`` kB in size. A thumbnail's width and height should
|
||||
not exceed ``320``. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||
optional): |thumbdocstringnopath|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
@ -327,7 +320,9 @@ class InputMediaVideo(InputMedia):
|
|||
duration = duration if duration is not None else media.duration
|
||||
media = media.file_id
|
||||
else:
|
||||
media = parse_file_input(media, filename=filename, attach=True)
|
||||
# We use local_mode=True because we don't have access to the actual setting and want
|
||||
# things to work in local mode.
|
||||
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
|
||||
|
||||
super().__init__(InputMediaType.VIDEO, media, caption, caption_entities, parse_mode)
|
||||
self.width = width
|
||||
|
@ -347,11 +342,8 @@ class InputMediaAudio(InputMedia):
|
|||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Audio`):
|
||||
File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Audio` object to send.
|
||||
:class:`telegram.Audio`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.Audio` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
@ -373,12 +365,8 @@ class InputMediaAudio(InputMedia):
|
|||
performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio
|
||||
tags.
|
||||
title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of
|
||||
the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than ``200`` kB in size. A thumbnail's width and height should
|
||||
not exceed ``320``. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||
optional): |thumbdocstringnopath|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
@ -418,7 +406,9 @@ class InputMediaAudio(InputMedia):
|
|||
title = media.title if title is None else title
|
||||
media = media.file_id
|
||||
else:
|
||||
media = parse_file_input(media, filename=filename, attach=True)
|
||||
# We use local_mode=True because we don't have access to the actual setting and want
|
||||
# things to work in local mode.
|
||||
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
|
||||
|
||||
super().__init__(InputMediaType.AUDIO, media, caption, caption_entities, parse_mode)
|
||||
self.thumb = self._parse_thumb_input(thumb)
|
||||
|
@ -432,10 +422,8 @@ class InputMediaDocument(InputMedia):
|
|||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Document`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Document` object to send.
|
||||
:class:`telegram.Document`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.Document` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
@ -453,12 +441,8 @@ class InputMediaDocument(InputMedia):
|
|||
caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special
|
||||
entities that appear in the caption, which can be specified instead of
|
||||
:paramref:`parse_mode`.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of
|
||||
the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than ``200`` kB in size. A thumbnail's width and height should
|
||||
not exceed ``320``. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||
optional): |thumbdocstringnopath|
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
Accept :obj:`bytes` as input.
|
||||
|
@ -492,7 +476,9 @@ class InputMediaDocument(InputMedia):
|
|||
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
|
||||
filename: str = None,
|
||||
):
|
||||
media = parse_file_input(media, Document, filename=filename, attach=True)
|
||||
# We use local_mode=True because we don't have access to the actual setting and want
|
||||
# things to work in local mode.
|
||||
media = parse_file_input(media, Document, filename=filename, attach=True, local_mode=True)
|
||||
super().__init__(InputMediaType.DOCUMENT, media, caption, caption_entities, parse_mode)
|
||||
self.thumb = self._parse_thumb_input(thumb)
|
||||
self.disable_content_type_detection = disable_content_type_detection
|
||||
|
|
|
@ -29,13 +29,47 @@ Warning:
|
|||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import IO, TYPE_CHECKING, Any, Optional, Type, Union, cast
|
||||
from typing import IO, TYPE_CHECKING, Any, Optional, Tuple, Type, TypeVar, Union, cast, overload
|
||||
|
||||
from telegram._utils.types import FileInput, FilePathInput
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import InputFile, TelegramObject
|
||||
|
||||
_T = TypeVar("_T", bound=Union[bytes, "InputFile", str, Path, None])
|
||||
|
||||
|
||||
@overload
|
||||
def load_file(obj: IO[bytes]) -> Tuple[Optional[str], bytes]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def load_file(obj: _T) -> Tuple[None, _T]:
|
||||
...
|
||||
|
||||
|
||||
def load_file(
|
||||
obj: Optional[FileInput],
|
||||
) -> Tuple[Optional[str], Union[bytes, "InputFile", str, Path, None]]:
|
||||
"""If the input is a file handle, read the data and name and return it. Otherwise, return
|
||||
the input unchanged.
|
||||
"""
|
||||
if obj is None:
|
||||
return None, None
|
||||
|
||||
try:
|
||||
contents = obj.read() # type: ignore[union-attr]
|
||||
except AttributeError:
|
||||
return None, cast(Union[bytes, "InputFile", str, Path], obj)
|
||||
|
||||
if hasattr(obj, "name") and not isinstance(obj.name, int): # type: ignore[union-attr]
|
||||
filename = Path(obj.name).name # type: ignore[union-attr]
|
||||
else:
|
||||
filename = None
|
||||
|
||||
return filename, contents
|
||||
|
||||
|
||||
def is_local_file(obj: Optional[FilePathInput]) -> bool:
|
||||
"""
|
||||
|
@ -54,18 +88,24 @@ def is_local_file(obj: Optional[FilePathInput]) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def parse_file_input(
|
||||
def parse_file_input( # pylint: disable=too-many-return-statements
|
||||
file_input: Union[FileInput, "TelegramObject"],
|
||||
tg_type: Type["TelegramObject"] = None,
|
||||
filename: str = None,
|
||||
attach: bool = False,
|
||||
local_mode: bool = False,
|
||||
) -> Union[str, "InputFile", Any]:
|
||||
"""
|
||||
Parses input for sending files:
|
||||
|
||||
* For string input, if the input is an absolute path of a local file,
|
||||
adds the ``file://`` prefix. If the input is a relative path of a local file, computes the
|
||||
absolute path and adds the ``file://`` prefix. Returns the input unchanged, otherwise.
|
||||
* For string input, if the input is an absolute path of a local file:
|
||||
|
||||
* if ``local_mode`` is ``True``, adds the ``file://`` prefix. If the input is a relative
|
||||
path of a local file, computes the absolute path and adds the ``file://`` prefix.
|
||||
* if ``local_mode`` is ``False``, loads the file as binary data and builds an
|
||||
:class:`InputFile` from that
|
||||
|
||||
Returns the input unchanged, otherwise.
|
||||
* :class:`pathlib.Path` objects are treated the same way as strings.
|
||||
* For IO and bytes input, returns an :class:`telegram.InputFile`.
|
||||
* If :attr:`tg_type` is specified and the input is of that type, returns the ``file_id``
|
||||
|
@ -81,6 +121,8 @@ def parse_file_input(
|
|||
attach (:obj:`bool`, optional): Pass :obj:`True` if the parameter this file belongs to in
|
||||
the request to Telegram should point to the multipart data via an ``attach://`` URI.
|
||||
Defaults to `False`. Only relevant if an :class:`telegram.InputFile` is returned.
|
||||
local_mode (:obj:`bool`, optional): Pass :obj:`True` if the bot is running an api server
|
||||
in ``--local`` mode.
|
||||
|
||||
Returns:
|
||||
:obj:`str` | :class:`telegram.InputFile` | :obj:`object`: The parsed input or the untouched
|
||||
|
@ -90,13 +132,17 @@ def parse_file_input(
|
|||
from telegram import InputFile # pylint: disable=import-outside-toplevel
|
||||
|
||||
if isinstance(file_input, str) and file_input.startswith("file://"):
|
||||
if not local_mode:
|
||||
raise ValueError("Specified file input is a file URI, but local mode is not enabled.")
|
||||
return file_input
|
||||
if isinstance(file_input, (str, Path)):
|
||||
if is_local_file(file_input):
|
||||
out = Path(file_input).absolute().as_uri()
|
||||
else:
|
||||
out = file_input # type: ignore[assignment]
|
||||
return out
|
||||
path = Path(file_input)
|
||||
if local_mode:
|
||||
return path.absolute().as_uri()
|
||||
return InputFile(path.open(mode="rb"), filename=filename, attach=attach)
|
||||
|
||||
return file_input
|
||||
if isinstance(file_input, bytes):
|
||||
return InputFile(file_input, filename=filename, attach=attach)
|
||||
if hasattr(file_input, "read"):
|
||||
|
|
|
@ -44,8 +44,7 @@ FilePathInput = Union[str, Path]
|
|||
|
||||
FileInput = Union[FilePathInput, FileLike, bytes, str]
|
||||
"""Valid input for passing files to Telegram. Either a file id as string, a file like object,
|
||||
a local file path as string, :class:`pathlib.Path` or the file contents as :obj:`bytes` or
|
||||
:obj:`str`."""
|
||||
a local file path as string, :class:`pathlib.Path` or the file contents as :obj:`bytes`."""
|
||||
|
||||
JSONDict = Dict[str, Any]
|
||||
"""Dictionary containing response from Telegram or data to send to the API."""
|
||||
|
|
|
@ -83,6 +83,7 @@ _BOT_CHECKS = [
|
|||
("arbitrary_callback_data", "arbitrary_callback_data"),
|
||||
("private_key", "private_key"),
|
||||
("rate_limiter", "rate_limiter instance"),
|
||||
("local_mode", "local_mode setting"),
|
||||
]
|
||||
|
||||
_TWO_ARGS_REQ = "The parameter `{}` may only be set, if no {} was set."
|
||||
|
@ -148,6 +149,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
"_update_queue",
|
||||
"_updater",
|
||||
"_write_timeout",
|
||||
"_local_mode",
|
||||
)
|
||||
|
||||
def __init__(self: "InitApplicationBuilder"):
|
||||
|
@ -172,6 +174,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
self._private_key_password: ODVInput[bytes] = DEFAULT_NONE
|
||||
self._defaults: ODVInput["Defaults"] = DEFAULT_NONE
|
||||
self._arbitrary_callback_data: DVInput[Union[bool, int]] = DEFAULT_FALSE
|
||||
self._local_mode: DVInput[bool] = DEFAULT_FALSE
|
||||
self._bot: DVInput[Bot] = DEFAULT_NONE
|
||||
self._update_queue: DVInput[Queue] = DefaultValue(Queue())
|
||||
self._job_queue: ODVInput["JobQueue"] = DefaultValue(JobQueue())
|
||||
|
@ -232,8 +235,17 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
request=self._build_request(get_updates=False),
|
||||
get_updates_request=self._build_request(get_updates=True),
|
||||
rate_limiter=DefaultValue.get_value(self._rate_limiter),
|
||||
local_mode=DefaultValue.get_value(self._local_mode),
|
||||
)
|
||||
|
||||
def _bot_check(self, name: str) -> None:
|
||||
if self._bot is not DEFAULT_NONE:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format(name, "bot instance"))
|
||||
|
||||
def _updater_check(self, name: str) -> None:
|
||||
if self._updater not in (DEFAULT_NONE, None):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format(name, "updater"))
|
||||
|
||||
def build(
|
||||
self: "ApplicationBuilder[BT, CCT, UD, CD, BD, JQ]",
|
||||
) -> Application[BT, CCT, UD, CD, BD, JQ]:
|
||||
|
@ -326,10 +338,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
Returns:
|
||||
:class:`ApplicationBuilder`: The same builder with the updated argument.
|
||||
"""
|
||||
if self._bot is not DEFAULT_NONE:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("token", "bot instance"))
|
||||
if self._updater not in (DEFAULT_NONE, None):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("token", "updater"))
|
||||
self._bot_check("token")
|
||||
self._updater_check("token")
|
||||
self._token = token
|
||||
return self
|
||||
|
||||
|
@ -347,10 +357,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
Returns:
|
||||
:class:`ApplicationBuilder`: The same builder with the updated argument.
|
||||
"""
|
||||
if self._bot is not DEFAULT_NONE:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("base_url", "bot instance"))
|
||||
if self._updater not in (DEFAULT_NONE, None):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("base_url", "updater"))
|
||||
self._bot_check("base_url")
|
||||
self._updater_check("base_url")
|
||||
self._base_url = base_url
|
||||
return self
|
||||
|
||||
|
@ -368,10 +376,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
Returns:
|
||||
:class:`ApplicationBuilder`: The same builder with the updated argument.
|
||||
"""
|
||||
if self._bot is not DEFAULT_NONE:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("base_file_url", "bot instance"))
|
||||
if self._updater not in (DEFAULT_NONE, None):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("base_file_url", "updater"))
|
||||
self._bot_check("base_file_url")
|
||||
self._updater_check("base_file_url")
|
||||
self._base_file_url = base_file_url
|
||||
return self
|
||||
|
||||
|
@ -388,8 +394,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
raise RuntimeError(_TWO_ARGS_REQ.format(name, "connection_pool_size"))
|
||||
if not isinstance(getattr(self, f"_{prefix}proxy_url"), DefaultValue):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format(name, "proxy_url"))
|
||||
if self._bot is not DEFAULT_NONE:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format(name, "bot instance"))
|
||||
self._bot_check(name)
|
||||
if self._updater not in (DEFAULT_NONE, None):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format(name, "updater instance"))
|
||||
|
||||
|
@ -670,10 +675,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
Returns:
|
||||
:class:`ApplicationBuilder`: The same builder with the updated argument.
|
||||
"""
|
||||
if self._bot is not DEFAULT_NONE:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("private_key", "bot instance"))
|
||||
if self._updater not in (DEFAULT_NONE, None):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("private_key", "updater"))
|
||||
self._bot_check("private_key")
|
||||
self._updater_check("private_key")
|
||||
|
||||
self._private_key = (
|
||||
private_key if isinstance(private_key, bytes) else Path(private_key).read_bytes()
|
||||
|
@ -698,10 +701,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
Returns:
|
||||
:class:`ApplicationBuilder`: The same builder with the updated argument.
|
||||
"""
|
||||
if self._bot is not DEFAULT_NONE:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("defaults", "bot instance"))
|
||||
if self._updater not in (DEFAULT_NONE, None):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("defaults", "updater"))
|
||||
self._bot_check("defaults")
|
||||
self._updater_check("defaults")
|
||||
self._defaults = defaults
|
||||
return self
|
||||
|
||||
|
@ -725,13 +726,30 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
Returns:
|
||||
:class:`ApplicationBuilder`: The same builder with the updated argument.
|
||||
"""
|
||||
if self._bot is not DEFAULT_NONE:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("arbitrary_callback_data", "bot instance"))
|
||||
if self._updater not in (DEFAULT_NONE, None):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("arbitrary_callback_data", "updater"))
|
||||
self._bot_check("arbitrary_callback_data")
|
||||
self._updater_check("arbitrary_callback_data")
|
||||
self._arbitrary_callback_data = arbitrary_callback_data
|
||||
return self
|
||||
|
||||
def local_mode(self: BuilderType, local_mode: bool) -> BuilderType:
|
||||
"""Specifies the value for :paramref:`~telegram.Bot.local_mode` for the
|
||||
:attr:`telegram.ext.Application.bot`.
|
||||
If not called, will default to :obj:`False`.
|
||||
|
||||
.. seealso:: `Local Bot API Server <https://github.com/python-telegram-bot/\
|
||||
python-telegram-bot/wiki/Local-Bot-API-Server>`_,
|
||||
|
||||
Args:
|
||||
local_mode (:obj:`bool`): Whether the bot should run in local mode.
|
||||
|
||||
Returns:
|
||||
:class:`ApplicationBuilder`: The same builder with the updated argument.
|
||||
"""
|
||||
self._bot_check("local_mode")
|
||||
self._updater_check("local_mode")
|
||||
self._local_mode = local_mode
|
||||
return self
|
||||
|
||||
def bot(
|
||||
self: "ApplicationBuilder[BT, CCT, UD, CD, BD, JQ]",
|
||||
bot: InBT,
|
||||
|
@ -746,8 +764,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
Returns:
|
||||
:class:`ApplicationBuilder`: The same builder with the updated argument.
|
||||
"""
|
||||
if self._updater not in (DEFAULT_NONE, None):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("bot", "updater"))
|
||||
self._updater_check("bot")
|
||||
for attr, error in _BOT_CHECKS:
|
||||
if not isinstance(getattr(self, f"_{attr}"), DefaultValue):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("bot", error))
|
||||
|
@ -992,10 +1009,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
|
|||
Returns:
|
||||
:class:`ApplicationBuilder`: The same builder with the updated argument.
|
||||
"""
|
||||
if self._bot is not DEFAULT_NONE:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("rate_limiter", "bot instance"))
|
||||
if self._updater not in (DEFAULT_NONE, None):
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format("rate_limiter", "updater"))
|
||||
self._bot_check("rate_limiter")
|
||||
self._updater_check("rate_limiter")
|
||||
self._rate_limiter = rate_limiter
|
||||
return self # type: ignore[return-value]
|
||||
|
||||
|
|
|
@ -163,6 +163,7 @@ class ExtBot(Bot, Generic[RLARGS]):
|
|||
private_key_password: bytes = None,
|
||||
defaults: "Defaults" = None,
|
||||
arbitrary_callback_data: Union[bool, int] = False,
|
||||
local_mode: bool = False,
|
||||
):
|
||||
...
|
||||
|
||||
|
@ -178,6 +179,7 @@ class ExtBot(Bot, Generic[RLARGS]):
|
|||
private_key_password: bytes = None,
|
||||
defaults: "Defaults" = None,
|
||||
arbitrary_callback_data: Union[bool, int] = False,
|
||||
local_mode: bool = False,
|
||||
rate_limiter: "BaseRateLimiter[RLARGS]" = None,
|
||||
):
|
||||
...
|
||||
|
@ -193,6 +195,7 @@ class ExtBot(Bot, Generic[RLARGS]):
|
|||
private_key_password: bytes = None,
|
||||
defaults: "Defaults" = None,
|
||||
arbitrary_callback_data: Union[bool, int] = False,
|
||||
local_mode: bool = False,
|
||||
rate_limiter: "BaseRateLimiter" = None,
|
||||
):
|
||||
super().__init__(
|
||||
|
@ -203,6 +206,7 @@ class ExtBot(Bot, Generic[RLARGS]):
|
|||
get_updates_request=get_updates_request,
|
||||
private_key=private_key,
|
||||
private_key_password=private_key_password,
|
||||
local_mode=local_mode,
|
||||
)
|
||||
self._defaults = defaults
|
||||
self._rate_limiter = rate_limiter
|
||||
|
|
|
@ -22,7 +22,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
from flaky import flaky
|
||||
|
||||
from telegram import Animation, Bot, MessageEntity, PhotoSize, Voice
|
||||
from telegram import Animation, Bot, InputFile, MessageEntity, PhotoSize, Voice
|
||||
from telegram.error import BadRequest, TelegramError
|
||||
from telegram.helpers import escape_markdown
|
||||
from telegram.request import RequestData
|
||||
|
@ -202,7 +202,10 @@ class TestAnimation:
|
|||
assert message.caption == test_markdown_string
|
||||
assert message.caption_markdown == escape_markdown(test_markdown_string)
|
||||
|
||||
async def test_send_animation_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_send_animation_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -210,12 +213,19 @@ class TestAnimation:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = data.get("animation") == expected and data.get("thumb") == expected
|
||||
else:
|
||||
test_flag = isinstance(data.get("animation"), InputFile) and isinstance(
|
||||
data.get("thumb"), InputFile
|
||||
)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.send_animation(chat_id, file, thumb=file)
|
||||
assert test_flag
|
||||
monkeypatch.delattr(bot, "_post")
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -84,6 +84,7 @@ class TestApplicationBuilder:
|
|||
assert app.bot.arbitrary_callback_data is False
|
||||
assert app.bot.defaults is None
|
||||
assert app.bot.rate_limiter is None
|
||||
assert app.bot.local_mode is False
|
||||
|
||||
get_updates_client = app.bot._request[0]._client
|
||||
assert get_updates_client.limits == httpx.Limits(
|
||||
|
@ -257,6 +258,8 @@ class TestApplicationBuilder:
|
|||
get_updates_request
|
||||
).rate_limiter(
|
||||
rate_limiter
|
||||
).local_mode(
|
||||
True
|
||||
)
|
||||
built_bot = builder.build().bot
|
||||
|
||||
|
@ -273,6 +276,7 @@ class TestApplicationBuilder:
|
|||
assert built_bot.callback_data_cache.maxsize == 42
|
||||
assert built_bot.private_key
|
||||
assert built_bot.rate_limiter is rate_limiter
|
||||
assert built_bot.local_mode is True
|
||||
|
||||
@dataclass
|
||||
class Client:
|
||||
|
|
|
@ -22,7 +22,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
from flaky import flaky
|
||||
|
||||
from telegram import Audio, Bot, MessageEntity, Voice
|
||||
from telegram import Audio, Bot, InputFile, MessageEntity, Voice
|
||||
from telegram.error import TelegramError
|
||||
from telegram.helpers import escape_markdown
|
||||
from telegram.request import RequestData
|
||||
|
@ -234,7 +234,10 @@ class TestAudio:
|
|||
unprotected = await default_bot.send_audio(chat_id, audio, protect_content=False)
|
||||
assert not unprotected.has_protected_content
|
||||
|
||||
async def test_send_audio_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_send_audio_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -242,12 +245,19 @@ class TestAudio:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = data.get("audio") == expected and data.get("thumb") == expected
|
||||
else:
|
||||
test_flag = isinstance(data.get("audio"), InputFile) and isinstance(
|
||||
data.get("thumb"), InputFile
|
||||
)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.send_audio(chat_id, file, thumb=file)
|
||||
assert test_flag
|
||||
monkeypatch.delattr(bot, "_post")
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
def test_de_json(self, bot, audio):
|
||||
json_dict = {
|
||||
|
|
|
@ -44,6 +44,7 @@ from telegram import (
|
|||
InlineQueryResultArticle,
|
||||
InlineQueryResultDocument,
|
||||
InlineQueryResultVoice,
|
||||
InputFile,
|
||||
InputMedia,
|
||||
InputTextMessageContent,
|
||||
LabeledPrice,
|
||||
|
@ -1578,18 +1579,25 @@ class TestBot:
|
|||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize("use_ip", [True, False])
|
||||
async def test_set_webhook_get_webhook_info_and_delete_webhook(self, bot, use_ip):
|
||||
# local file path as file_input is tested below in test_set_webhook_params
|
||||
@pytest.mark.parametrize("file_input", ["bytes", "file_handle"])
|
||||
async def test_set_webhook_get_webhook_info_and_delete_webhook(self, bot, use_ip, file_input):
|
||||
url = "https://python-telegram-bot.org/test/webhook"
|
||||
# Get the ip address of the website - dynamically just in case it ever changes
|
||||
ip = socket.gethostbyname("python-telegram-bot.org")
|
||||
max_connections = 7
|
||||
allowed_updates = ["message"]
|
||||
file_input = (
|
||||
data_file("sslcert.pem").read_bytes()
|
||||
if file_input == "bytes"
|
||||
else data_file("sslcert.pem").open("rb")
|
||||
)
|
||||
await bot.set_webhook(
|
||||
url,
|
||||
max_connections=max_connections,
|
||||
allowed_updates=allowed_updates,
|
||||
ip_address=ip if use_ip else None,
|
||||
certificate=data_file("sslcert.pem").read_bytes() if use_ip else None,
|
||||
certificate=file_input if use_ip else None,
|
||||
)
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
@ -1620,16 +1628,25 @@ class TestBot:
|
|||
assert await bot.set_webhook("", drop_pending_updates=drop_pending_updates)
|
||||
assert await bot.delete_webhook(drop_pending_updates=drop_pending_updates)
|
||||
|
||||
async def test_set_webhook_params(self, bot, monkeypatch):
|
||||
@pytest.mark.parametrize("local_file", ["str", "Path", False])
|
||||
async def test_set_webhook_params(self, bot, monkeypatch, local_file):
|
||||
# actually making calls to TG is done in
|
||||
# test_set_webhook_get_webhook_info_and_delete_webhook. Sadly secret_token can't be tested
|
||||
# there so we have this function \o/
|
||||
async def make_assertion(*args, **_):
|
||||
kwargs = args[1]
|
||||
|
||||
if local_file is False:
|
||||
cert_assertion = (
|
||||
kwargs["certificate"].input_file_content
|
||||
== data_file("sslcert.pem").read_bytes()
|
||||
)
|
||||
else:
|
||||
cert_assertion = data_file("sslcert.pem").as_uri()
|
||||
|
||||
return (
|
||||
kwargs["url"] == "example.com"
|
||||
and kwargs["certificate"].input_file_content
|
||||
== data_file("sslcert.pem").read_bytes()
|
||||
and cert_assertion
|
||||
and kwargs["max_connections"] == 7
|
||||
and kwargs["allowed_updates"] == ["messages"]
|
||||
and kwargs["ip_address"] == "127.0.0.1"
|
||||
|
@ -1639,9 +1656,17 @@ class TestBot:
|
|||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
|
||||
cert_path = data_file("sslcert.pem")
|
||||
if local_file == "str":
|
||||
certificate = str(cert_path)
|
||||
elif local_file == "Path":
|
||||
certificate = cert_path
|
||||
else:
|
||||
certificate = cert_path.read_bytes()
|
||||
|
||||
assert await bot.set_webhook(
|
||||
"example.com",
|
||||
data_file("sslcert.pem").read_bytes(),
|
||||
certificate,
|
||||
7,
|
||||
["messages"],
|
||||
"127.0.0.1",
|
||||
|
@ -2163,7 +2188,10 @@ class TestBot:
|
|||
func, "Type of file mismatch", "Telegram did not accept the file."
|
||||
)
|
||||
|
||||
async def test_set_chat_photo_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_set_chat_photo_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -2171,11 +2199,16 @@ class TestBot:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = data.get("photo") == expected
|
||||
else:
|
||||
test_flag = isinstance(data.get("photo"), InputFile)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.set_chat_photo(chat_id, file)
|
||||
assert test_flag
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
@flaky(3, 1)
|
||||
async def test_delete_chat_photo(self, bot, channel_id):
|
||||
|
|
|
@ -22,7 +22,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
from flaky import flaky
|
||||
|
||||
from telegram import Bot, Document, MessageEntity, PhotoSize, Voice
|
||||
from telegram import Bot, Document, InputFile, MessageEntity, PhotoSize, Voice
|
||||
from telegram.error import BadRequest, TelegramError
|
||||
from telegram.helpers import escape_markdown
|
||||
from telegram.request import RequestData
|
||||
|
@ -255,7 +255,10 @@ class TestDocument:
|
|||
unprotected = await default_bot.send_document(chat_id, document, protect_content=False)
|
||||
assert not unprotected.has_protected_content
|
||||
|
||||
async def test_send_document_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_send_document_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -263,11 +266,18 @@ class TestDocument:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = data.get("document") == expected and data.get("thumb") == expected
|
||||
else:
|
||||
test_flag = isinstance(data.get("document"), InputFile) and isinstance(
|
||||
data.get("thumb"), InputFile
|
||||
)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.send_document(chat_id, file, thumb=file)
|
||||
assert test_flag
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
def test_de_json(self, bot, document):
|
||||
json_dict = {
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
#
|
||||
# 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 subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -43,23 +46,43 @@ class TestFiles:
|
|||
assert telegram._utils.files.is_local_file(string) == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"string,expected",
|
||||
"string,expected_local,expected_non_local",
|
||||
[
|
||||
(data_file("game.gif"), data_file("game.gif").as_uri()),
|
||||
(TEST_DATA_PATH, TEST_DATA_PATH),
|
||||
("file://foobar", "file://foobar"),
|
||||
(str(data_file("game.gif")), data_file("game.gif").as_uri()),
|
||||
(str(TEST_DATA_PATH), str(TEST_DATA_PATH)),
|
||||
(data_file("game.gif"), data_file("game.gif").as_uri()),
|
||||
(TEST_DATA_PATH, TEST_DATA_PATH),
|
||||
(data_file("game.gif"), data_file("game.gif").as_uri(), InputFile),
|
||||
(TEST_DATA_PATH, TEST_DATA_PATH, TEST_DATA_PATH),
|
||||
("file://foobar", "file://foobar", ValueError),
|
||||
(str(data_file("game.gif")), data_file("game.gif").as_uri(), InputFile),
|
||||
(str(TEST_DATA_PATH), str(TEST_DATA_PATH), str(TEST_DATA_PATH)),
|
||||
(
|
||||
"https:/api.org/file/botTOKEN/document/file_3",
|
||||
"https:/api.org/file/botTOKEN/document/file_3",
|
||||
"https:/api.org/file/botTOKEN/document/file_3",
|
||||
),
|
||||
],
|
||||
ids=[
|
||||
"Path(local_file)",
|
||||
"Path(directory)",
|
||||
"file_uri",
|
||||
"str-path local file",
|
||||
"str-path directory",
|
||||
"URL",
|
||||
],
|
||||
)
|
||||
def test_parse_file_input_string(self, string, expected_local, expected_non_local):
|
||||
assert telegram._utils.files.parse_file_input(string, local_mode=True) == expected_local
|
||||
|
||||
if expected_non_local is InputFile:
|
||||
assert isinstance(
|
||||
telegram._utils.files.parse_file_input(string, local_mode=False), InputFile
|
||||
)
|
||||
elif expected_non_local is ValueError:
|
||||
with pytest.raises(ValueError):
|
||||
telegram._utils.files.parse_file_input(string, local_mode=False)
|
||||
else:
|
||||
assert (
|
||||
telegram._utils.files.parse_file_input(string, local_mode=False)
|
||||
== expected_non_local
|
||||
)
|
||||
def test_parse_file_input_string(self, string, expected):
|
||||
assert telegram._utils.files.parse_file_input(string) == expected
|
||||
|
||||
def test_parse_file_input_file_like(self):
|
||||
source_file = data_file("game.gif")
|
||||
|
@ -105,3 +128,34 @@ class TestFiles:
|
|||
|
||||
assert isinstance(parsed, InputFile)
|
||||
assert bool(parsed.attach_name) is attach
|
||||
|
||||
def test_load_file_none(self):
|
||||
assert telegram._utils.files.load_file(None) == (None, None)
|
||||
|
||||
@pytest.mark.parametrize("arg", [b"bytes", "string", InputFile(b"content"), Path("file/path")])
|
||||
def test_load_file_no_file(self, arg):
|
||||
out = telegram._utils.files.load_file(arg)
|
||||
assert out[0] is None
|
||||
assert out[1] is arg
|
||||
|
||||
def test_load_file_file_handle(self):
|
||||
out = telegram._utils.files.load_file(data_file("telegram.gif").open("rb"))
|
||||
assert out[0] == "telegram.gif"
|
||||
assert out[1] == data_file("telegram.gif").read_bytes()
|
||||
|
||||
def test_load_file_subprocess_pipe(self):
|
||||
png_file = data_file("telegram.png")
|
||||
cmd_str = "type" if sys.platform == "win32" else "cat"
|
||||
cmd = [cmd_str, str(png_file)]
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=(sys.platform == "win32"))
|
||||
out = telegram._utils.files.load_file(proc.stdout)
|
||||
|
||||
assert out[0] is None
|
||||
assert out[1] == png_file.read_bytes()
|
||||
|
||||
try:
|
||||
proc.kill()
|
||||
except ProcessLookupError:
|
||||
# This exception may be thrown if the process has finished before we had the chance
|
||||
# to kill it.
|
||||
pass
|
||||
|
|
|
@ -236,7 +236,10 @@ class TestPhoto:
|
|||
unprotected = await default_bot.send_photo(chat_id, photo, protect_content=False)
|
||||
assert not unprotected.has_protected_content
|
||||
|
||||
async def test_send_photo_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_send_photo_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -244,11 +247,16 @@ class TestPhoto:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = data.get("photo") == expected
|
||||
else:
|
||||
test_flag = isinstance(data.get("photo"), InputFile)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.send_photo(chat_id, file)
|
||||
assert test_flag
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -23,7 +23,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
from flaky import flaky
|
||||
|
||||
from telegram import Audio, Bot, File, MaskPosition, PhotoSize, Sticker, StickerSet
|
||||
from telegram import Audio, Bot, File, InputFile, MaskPosition, PhotoSize, Sticker, StickerSet
|
||||
from telegram.error import BadRequest, TelegramError
|
||||
from telegram.request import RequestData
|
||||
from tests.conftest import (
|
||||
|
@ -250,7 +250,10 @@ class TestSticker:
|
|||
message = await bot.send_sticker(sticker=sticker, chat_id=chat_id)
|
||||
assert message
|
||||
|
||||
async def test_send_sticker_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_send_sticker_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -258,12 +261,17 @@ class TestSticker:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = data.get("sticker") == expected
|
||||
else:
|
||||
test_flag = isinstance(data.get("sticker"), InputFile)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.send_sticker(chat_id, file)
|
||||
assert test_flag
|
||||
monkeypatch.delattr(bot, "_post")
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -645,7 +653,10 @@ class TestStickerSet:
|
|||
file_id = video_sticker_set.stickers[-1].file_id
|
||||
assert await bot.delete_sticker_from_set(file_id)
|
||||
|
||||
async def test_upload_sticker_file_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_upload_sticker_file_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -653,14 +664,22 @@ class TestStickerSet:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = data.get("png_sticker") == expected
|
||||
else:
|
||||
test_flag = isinstance(data.get("png_sticker"), InputFile)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.upload_sticker_file(chat_id, file)
|
||||
assert test_flag
|
||||
monkeypatch.delattr(bot, "_post")
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
async def test_create_new_sticker_set_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_create_new_sticker_set_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -668,11 +687,18 @@ class TestStickerSet:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = (
|
||||
data.get("png_sticker") == expected
|
||||
and data.get("tgs_sticker") == expected
|
||||
and data.get("webm_sticker") == expected
|
||||
)
|
||||
else:
|
||||
test_flag = (
|
||||
isinstance(data.get("png_sticker"), InputFile)
|
||||
and isinstance(data.get("tgs_sticker"), InputFile)
|
||||
and isinstance(data.get("webm_sticker"), InputFile)
|
||||
)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.create_new_sticker_set(
|
||||
|
@ -686,6 +712,8 @@ class TestStickerSet:
|
|||
)
|
||||
assert test_flag
|
||||
monkeypatch.delattr(bot, "_post")
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
async def test_create_new_sticker_all_params(self, monkeypatch, bot, chat_id, mask_position):
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
|
@ -713,7 +741,10 @@ class TestStickerSet:
|
|||
)
|
||||
monkeypatch.delattr(bot, "_post")
|
||||
|
||||
async def test_add_sticker_to_set_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_add_sticker_to_set_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -721,14 +752,28 @@ class TestStickerSet:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
test_flag = data.get("png_sticker") == expected and data.get("tgs_sticker") == expected
|
||||
if local_mode:
|
||||
test_flag = (
|
||||
data.get("png_sticker") == expected and data.get("tgs_sticker") == expected
|
||||
)
|
||||
else:
|
||||
test_flag = isinstance(data.get("png_sticker"), InputFile) and isinstance(
|
||||
data.get("tgs_sticker"), InputFile
|
||||
)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.add_sticker_to_set(chat_id, "name", "emoji", png_sticker=file, tgs_sticker=file)
|
||||
await bot.add_sticker_to_set(
|
||||
chat_id, "name", "emoji", png_sticker=file, tgs_sticker=file
|
||||
)
|
||||
assert test_flag
|
||||
monkeypatch.delattr(bot, "_post")
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
async def test_set_sticker_set_thumb_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_set_sticker_set_thumb_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -736,12 +781,17 @@ class TestStickerSet:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = data.get("thumb") == expected
|
||||
else:
|
||||
test_flag = isinstance(data.get("thumb"), InputFile)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.set_sticker_set_thumb("name", chat_id, thumb=file)
|
||||
assert test_flag
|
||||
monkeypatch.delattr(bot, "_post")
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
async def test_get_file_instance_method(self, monkeypatch, sticker):
|
||||
async def make_assertion(*_, **kwargs):
|
||||
|
|
|
@ -22,7 +22,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
from flaky import flaky
|
||||
|
||||
from telegram import Bot, MessageEntity, PhotoSize, Video, Voice
|
||||
from telegram import Bot, InputFile, MessageEntity, PhotoSize, Video, Voice
|
||||
from telegram.error import BadRequest, TelegramError
|
||||
from telegram.helpers import escape_markdown
|
||||
from telegram.request import RequestData
|
||||
|
@ -247,7 +247,10 @@ class TestVideo:
|
|||
unprotected = await default_bot.send_video(chat_id, video, protect_content=False)
|
||||
assert not unprotected.has_protected_content
|
||||
|
||||
async def test_send_video_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_send_video_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -255,11 +258,18 @@ class TestVideo:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = data.get("video") == expected and data.get("thumb") == expected
|
||||
else:
|
||||
test_flag = isinstance(data.get("video"), InputFile) and isinstance(
|
||||
data.get("thumb"), InputFile
|
||||
)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.send_video(chat_id, file, thumb=file)
|
||||
assert test_flag
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -22,7 +22,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
from flaky import flaky
|
||||
|
||||
from telegram import Bot, PhotoSize, VideoNote, Voice
|
||||
from telegram import Bot, InputFile, PhotoSize, VideoNote, Voice
|
||||
from telegram.error import BadRequest, TelegramError
|
||||
from telegram.request import RequestData
|
||||
from tests.conftest import (
|
||||
|
@ -177,7 +177,10 @@ class TestVideoNote:
|
|||
assert video_note_dict["duration"] == video_note.duration
|
||||
assert video_note_dict["file_size"] == video_note.file_size
|
||||
|
||||
async def test_send_video_note_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_send_video_note_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -185,11 +188,20 @@ class TestVideoNote:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
test_flag = data.get("video_note") == expected and data.get("thumb") == expected
|
||||
if local_mode:
|
||||
test_flag = (
|
||||
data.get("video_note") == expected and data.get("thumb") == expected
|
||||
)
|
||||
else:
|
||||
test_flag = isinstance(data.get("video_note"), InputFile) and isinstance(
|
||||
data.get("thumb"), InputFile
|
||||
)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.send_video_note(chat_id, file, thumb=file)
|
||||
assert test_flag
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -22,7 +22,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
from flaky import flaky
|
||||
|
||||
from telegram import Audio, Bot, MessageEntity, Voice
|
||||
from telegram import Audio, Bot, InputFile, MessageEntity, Voice
|
||||
from telegram.error import BadRequest, TelegramError
|
||||
from telegram.helpers import escape_markdown
|
||||
from telegram.request import RequestData
|
||||
|
@ -207,7 +207,10 @@ class TestVoice:
|
|||
unprotected = await default_bot.send_voice(chat_id, voice, protect_content=False)
|
||||
assert not unprotected.has_protected_content
|
||||
|
||||
async def test_send_voice_local_files(self, monkeypatch, bot, chat_id):
|
||||
@pytest.mark.parametrize("local_mode", [True, False])
|
||||
async def test_send_voice_local_files(self, monkeypatch, bot, chat_id, local_mode):
|
||||
try:
|
||||
bot._local_mode = local_mode
|
||||
# For just test that the correct paths are passed as we have no local bot API set up
|
||||
test_flag = False
|
||||
file = data_file("telegram.jpg")
|
||||
|
@ -215,11 +218,16 @@ class TestVoice:
|
|||
|
||||
async def make_assertion(_, data, *args, **kwargs):
|
||||
nonlocal test_flag
|
||||
if local_mode:
|
||||
test_flag = data.get("voice") == expected
|
||||
else:
|
||||
test_flag = isinstance(data.get("voice"), InputFile)
|
||||
|
||||
monkeypatch.setattr(bot, "_post", make_assertion)
|
||||
await bot.send_voice(chat_id, file)
|
||||
assert test_flag
|
||||
finally:
|
||||
bot._local_mode = False
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.parametrize(
|
||||
|
|
Loading…
Reference in a new issue