Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/source/telegram.messageid.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
telegram.MessageId
==================

.. autoclass:: telegram.MessageId
:members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/source/telegram.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ telegram package
telegram.location
telegram.loginurl
telegram.message
telegram.messageid
telegram.messageentity
telegram.parsemode
telegram.photosize
Expand Down
2 changes: 2 additions & 0 deletions telegram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from .files.file import File
from .parsemode import ParseMode
from .messageentity import MessageEntity
from .messageid import MessageId
from .games.game import Game
from .poll import Poll, PollOption, PollAnswer
from .loginurl import LoginUrl
Expand Down Expand Up @@ -272,4 +273,5 @@
'KeyboardButtonPollType',
'Dice',
'BotCommand',
'MessageId',
]
87 changes: 87 additions & 0 deletions telegram/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
Location,
MaskPosition,
Message,
MessageEntity,
MessageId,
PassportElementError,
PhotoSize,
Poll,
Expand Down Expand Up @@ -4587,6 +4589,89 @@ def close(self) -> bool:
"""
return self._post('close') # type: ignore[return-value]

@log
def copy_message(
self,
chat_id: Union[int, str],
from_chat_id: Union[str, int],
message_id: Union[str, int],
caption: str = None,
parse_mode: str = None,
caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None,
disable_notification: bool = False,
reply_to_message_id: Union[int, str] = None,
allow_sending_without_reply: bool = False,
reply_markup: ReplyMarkup = None,
timeout: float = None,
api_kwargs: JSONDict = None,
) -> Optional[MessageId]:
"""
Use this method to copy messages of any kind. The method is analogous to the method
forwardMessages, but the copied message doesn't have a link to the original message.
Returns the MessageId of the sent message on success.

Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
from_chat_id (:obj:`int` | :obj:`str`): Unique identifier for the chat where the
original message was sent (or channel username in the format @channelusername).
message_id (:obj:`int`): Message identifier in the chat specified in from_chat_id.
caption (:obj:`str`, optional): New caption for media, 0-1024 characters after
entities parsing. If not specified, the original caption is kept.
parse_mode (:obj:`str`, optional): Mode for parsing entities in the new caption. See
the constants in :class:`telegram.ParseMode` for the available modes.
caption_entities (:class:`telegram.utils.types.SLT[MessageEntity]`): List of special
entities that appear in the new caption, which can be specified instead of
parse_mode
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound.
reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the
original message.
allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message
should be sent even if the specified replied-to message is not found.
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options.
A JSON-serialized object for an inline keyboard, custom reply keyboard,
instructions to remove reply keyboard or to force a reply from the user.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.

Returns:
:class:`telegram.MessageId`: On success

Raises:
:class:`telegram.TelegramError`
"""
data: JSONDict = {
'chat_id': chat_id,
'from_chat_id': from_chat_id,
'message_id': message_id,
}
if caption:
data['caption'] = caption
if parse_mode:
data['parse_mode'] = parse_mode
if caption_entities:
data['caption_entities'] = caption_entities
if disable_notification:
data['disable_notification'] = disable_notification
if reply_to_message_id:
data['reply_to_message_id'] = reply_to_message_id
if allow_sending_without_reply:
data['allow_sending_without_reply'] = allow_sending_without_reply
if reply_markup:
if isinstance(reply_markup, ReplyMarkup):
# We need to_json() instead of to_dict() here, because reply_markups may be
# attached to media messages, which aren't json dumped by utils.request
data['reply_markup'] = reply_markup.to_json()
else:
data['reply_markup'] = reply_markup

result = self._post('copyMessage', data, timeout=timeout, api_kwargs=api_kwargs)
return MessageId.de_json(result, self) # type: ignore

def to_dict(self) -> JSONDict:
data: JSONDict = {'id': self.id, 'username': self.username, 'first_name': self.first_name}

Expand Down Expand Up @@ -4740,3 +4825,5 @@ def to_dict(self) -> JSONDict:
"""Alias for :attr:`set_my_commands`"""
logOut = log_out
"""Alias for :attr:`log_out`"""
copyMessage = copy_message
"""Alias for :attr:`copy_message`"""
18 changes: 17 additions & 1 deletion telegram/callbackquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from telegram.utils.types import JSONDict

if TYPE_CHECKING:
from telegram import Bot, GameHighScore, InlineKeyboardMarkup
from telegram import Bot, GameHighScore, InlineKeyboardMarkup, MessageId


class CallbackQuery(TelegramObject):
Expand Down Expand Up @@ -353,3 +353,19 @@ def unpin_message(self, *args: Any, **kwargs: Any) -> bool:

"""
return self.message.unpin(*args, **kwargs)

def copy_message(self, chat_id: int, *args: Any, **kwargs: Any) -> 'MessageId':
"""Shortcut for::

update.callback_query.message.copy(
chat_id,
from_chat_id=update.message.chat_id,
message_id=update.message.message_id,
*args,
**kwargs)

Returns:
:class:`telegram.MessageId`: On success, returns the MessageId of the sent message.

"""
return self.message.copy(chat_id, *args, **kwargs)
24 changes: 23 additions & 1 deletion telegram/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from .chatlocation import ChatLocation

if TYPE_CHECKING:
from telegram import Bot, ChatMember, Message
from telegram import Bot, ChatMember, Message, MessageId


class Chat(TelegramObject):
Expand Down Expand Up @@ -523,3 +523,25 @@ def send_poll(self, *args: Any, **kwargs: Any) -> 'Message':

"""
return self.bot.send_poll(self.id, *args, **kwargs)

def send_copy(self, *args: Any, **kwargs: Any) -> 'MessageId':
"""Shortcut for::

bot.copy_message(chat_id=update.effective_chat.id, *args, **kwargs)

Returns:
:class:`telegram.Message`: On success, instance representing the message posted.

"""
return self.bot.copy_message(chat_id=self.id, *args, **kwargs)

def copy_message(self, *args: Any, **kwargs: Any) -> 'MessageId':
"""Shortcut for::

bot.copy_message(from_chat_id=update.effective_chat.id, *args, **kwargs)

Returns:
:class:`telegram.Message`: On success, instance representing the message posted.

"""
return self.bot.copy_message(from_chat_id=self.id, *args, **kwargs)
5 changes: 4 additions & 1 deletion telegram/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@
Attributes:
POLL_REGULAR (:obj:`str`): 'regular'
POLL_QUIZ (:obj:`str`): 'quiz'
MAX_POLL_QUESTION_LENGTH (:obj:`int`): 300
MAX_POLL_OPTION_LENGTH (:obj:`int`): 100

:class:`telegram.files.MaskPosition`:

Expand Down Expand Up @@ -223,7 +225,8 @@

POLL_REGULAR: str = 'regular'
POLL_QUIZ: str = 'quiz'

MAX_POLL_QUESTION_LENGTH: int = 300
MAX_POLL_OPTION_LENGTH: int = 100

STICKER_FOREHEAD: str = 'forehead'
STICKER_EYES: str = 'eyes'
Expand Down
38 changes: 37 additions & 1 deletion telegram/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
from telegram.utils.types import JSONDict

if TYPE_CHECKING:
from telegram import Bot, GameHighScore, InputMedia
from telegram import Bot, GameHighScore, InputMedia, MessageId

_UNDEFINED = object()

Expand Down Expand Up @@ -948,6 +948,42 @@ def forward(self, chat_id: int, *args: Any, **kwargs: Any) -> 'Message':
chat_id=chat_id, from_chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs
)

def copy(self, chat_id: int, *args: Any, **kwargs: Any) -> 'MessageId':
"""Shortcut for::

bot.copy_message(chat_id=chat_id,
from_chat_id=update.message.chat_id,
message_id=update.message.message_id,
*args,
**kwargs)

Returns:
:class:`telegram.MessageId`: On success, returns the MessageId of the sent message.

"""
return self.bot.copy_message(
chat_id=chat_id, from_chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs
)

def reply_copy(
self, from_chat_id: int, message_id: int, *args: Any, **kwargs: Any
) -> 'MessageId':
"""Shortcut for::

bot.copy_message(chat_id=message.chat.id,
from_chat_id=from_chat_id,
message_id=message_id,
*args,
**kwargs)

Returns:
:class:`telegram.MessageId`: On success, returns the MessageId of the sent message.

"""
return self.bot.copy_message(
chat_id=self.chat_id, from_chat_id=from_chat_id, message_id=message_id, *args, **kwargs
)

def edit_text(self, *args: Any, **kwargs: Any) -> Union['Message', bool]:
"""Shortcut for::

Expand Down
38 changes: 38 additions & 0 deletions telegram/messageid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents an instance of a Telegram MessageId."""
from typing import Any

from telegram import TelegramObject


class MessageId(TelegramObject):
"""This object represents a unique message identifier.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`message_id` is equal.

Attributes:
message_id (:obj:`int`): Unique message identifier
"""

def __init__(self, message_id: int, **_kwargs: Any):
self.message_id = int(message_id)

self._id_attrs = (self.message_id,)
11 changes: 9 additions & 2 deletions telegram/poll.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ def __init__(self, text: str, voter_count: int, **_kwargs: Any):

self._id_attrs = (self.text, self.voter_count)

MAX_LENGTH: ClassVar[int] = constants.MAX_POLL_OPTION_LENGTH
""":const:`telegram.constants.MAX_POLL_OPTION_LENGTH`"""


class PollAnswer(TelegramObject):
"""
Expand Down Expand Up @@ -103,7 +106,7 @@ class Poll(TelegramObject):

Attributes:
id (:obj:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, 1-255 characters.
question (:obj:`str`): Poll question, 1-300 characters.
options (List[:class:`PollOption`]): List of poll options.
total_voter_count (:obj:`int`): Total number of users that voted in the poll.
is_closed (:obj:`bool`): :obj:`True`, if the poll is closed.
Expand All @@ -122,7 +125,7 @@ class Poll(TelegramObject):

Args:
id (:obj:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, 1-255 characters.
question (:obj:`str`): Poll question, 1-300 characters.
options (List[:class:`PollOption`]): List of poll options.
is_closed (:obj:`bool`): :obj:`True`, if the poll is closed.
is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous.
Expand Down Expand Up @@ -262,3 +265,7 @@ def parse_explanation_entities(self, types: List[str] = None) -> Dict[MessageEnt
""":const:`telegram.constants.POLL_REGULAR`"""
QUIZ: ClassVar[str] = constants.POLL_QUIZ
""":const:`telegram.constants.POLL_QUIZ`"""
MAX_QUESTION_LENGTH: ClassVar[int] = constants.MAX_POLL_QUESTION_LENGTH
""":const:`telegram.constants.MAX_POLL_QUESTION_LENGTH`"""
MAX_OPTION_LENGTH: ClassVar[int] = constants.MAX_POLL_OPTION_LENGTH
""":const:`telegram.constants.MAX_POLL_OPTION_LENGTH`"""
24 changes: 23 additions & 1 deletion telegram/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from telegram.utils.helpers import mention_markdown as util_mention_markdown

if TYPE_CHECKING:
from telegram import Bot, Message, UserProfilePhotos
from telegram import Bot, Message, UserProfilePhotos, MessageId


class User(TelegramObject):
Expand Down Expand Up @@ -414,3 +414,25 @@ def send_poll(self, *args: Any, **kwargs: Any) -> 'Message':

"""
return self.bot.send_poll(self.id, *args, **kwargs)

def send_copy(self, *args: Any, **kwargs: Any) -> 'MessageId':
"""Shortcut for::

bot.copy_message(chat_id=update.effective_user.id, *args, **kwargs)

Returns:
:class:`telegram.Message`: On success, instance representing the message posted.

"""
return self.bot.copy_message(chat_id=self.id, *args, **kwargs)

def copy_message(self, *args: Any, **kwargs: Any) -> 'MessageId':
"""Shortcut for::

bot.copy_message(from_chat_id=update.effective_user.id, *args, **kwargs)

Returns:
:class:`telegram.Message`: On success, instance representing the message posted.

"""
return self.bot.copy_message(from_chat_id=self.id, *args, **kwargs)
Loading