Add ability to forward messages as copies (#227)

* Add ability to forward messages as copies

* Add Messages.forward() method

* Update and clean up code
This commit is contained in:
bakatrouble 2019-03-21 15:53:07 +03:00 committed by Dan
parent ac591cf3c7
commit 081b9b280a
7 changed files with 265 additions and 71 deletions

View File

@ -29,7 +29,9 @@ class ForwardMessages(BaseClient):
chat_id: Union[int, str],
from_chat_id: Union[int, str],
message_ids: Iterable[int],
disable_notification: bool = None
disable_notification: bool = None,
as_copy: bool = False,
remove_caption: bool = False
) -> "pyrogram.Messages":
"""Use this method to forward messages of any kind.
@ -52,6 +54,15 @@ class ForwardMessages(BaseClient):
Sends the message silently.
Users will receive a notification with no sound.
as_copy (``bool``, *optional*):
Pass True to forward messages without the forward header (i.e.: send a copy of the message content).
Defaults to False.
remove_caption (``bool``, *optional*):
If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the
message. Has no effect if *as_copy* is not enabled.
Defaults to False.
Returns:
On success and in case *message_ids* was an iterable, the returned value will be a list of the forwarded
:obj:`Messages <pyrogram.Message>` even if a list contains just one element, otherwise if
@ -61,35 +72,55 @@ class ForwardMessages(BaseClient):
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
is_iterable = not isinstance(message_ids, int)
message_ids = list(message_ids) if is_iterable else [message_ids]
r = self.send(
functions.messages.ForwardMessages(
to_peer=self.resolve_peer(chat_id),
from_peer=self.resolve_peer(from_chat_id),
id=message_ids,
silent=disable_notification or None,
random_id=[self.rnd_id() for _ in message_ids]
)
)
messages = []
users = {i.id: i for i in r.users}
chats = {i.id: i for i in r.chats}
for i in r.updates:
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
messages.append(
pyrogram.Message._parse(
self, i.message,
users, chats
if as_copy:
sent_messages = []
for chunk in [message_ids[i:i + 200] for i in range(0, len(message_ids), 200)]:
messages = self.get_messages(chat_id=from_chat_id, message_ids=chunk) # type: pyrogram.Messages
for message in messages.messages:
sent_messages.append(
message.forward(
chat_id,
disable_notification=disable_notification,
as_copy=True,
remove_caption=remove_caption
)
)
return pyrogram.Messages(
client=self,
total_count=len(sent_messages),
messages=sent_messages
) if is_iterable else sent_messages[0]
else:
r = self.send(
functions.messages.ForwardMessages(
to_peer=self.resolve_peer(chat_id),
from_peer=self.resolve_peer(from_chat_id),
id=message_ids,
silent=disable_notification or None,
random_id=[self.rnd_id() for _ in message_ids]
)
)
return pyrogram.Messages(
client=self,
total_count=len(messages),
messages=messages
) if is_iterable else messages[0]
forwarded_messages = []
users = {i.id: i for i in r.users}
chats = {i.id: i for i in r.chats}
for i in r.updates:
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
forwarded_messages.append(
pyrogram.Message._parse(
self, i.message,
users, chats
)
)
return pyrogram.Messages(
client=self,
total_count=len(forwarded_messages),
messages=forwarded_messages
) if is_iterable else forwarded_messages[0]

View File

@ -29,8 +29,8 @@ class SendContact(BaseClient):
chat_id: Union[int, str],
phone_number: str,
first_name: str,
last_name: str = "",
vcard: str = "",
last_name: str = None,
vcard: str = None,
disable_notification: bool = None,
reply_to_message_id: int = None,
reply_markup: Union[
@ -83,8 +83,8 @@ class SendContact(BaseClient):
media=types.InputMediaContact(
phone_number=phone_number,
first_name=first_name,
last_name=last_name,
vcard=vcard
last_name=last_name or "",
vcard=vcard or ""
),
message="",
silent=disable_notification or None,

View File

@ -40,7 +40,7 @@ class HTML:
def parse(self, message: str):
entities = []
message = utils.add_surrogates(str(message))
message = utils.add_surrogates(str(message or ""))
offset = 0
for match in self.HTML_RE.finditer(message):

View File

@ -56,7 +56,7 @@ class Markdown:
self.peers_by_id = peers_by_id
def parse(self, message: str):
message = utils.add_surrogates(str(message)).strip()
message = utils.add_surrogates(str(message or "")).strip()
entities = []
offset = 0

View File

@ -16,12 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from functools import partial
from typing import List, Match, Union
import pyrogram
from pyrogram.api import types
from pyrogram.api.errors import MessageIdsEmpty
from pyrogram.client.ext import ChatAction
from pyrogram.client.ext import ChatAction, ParseMode
from pyrogram.client.types.input_media import InputMedia
from .contact import Contact
from .location import Location
@ -33,6 +34,32 @@ from ..user_and_chats.chat import Chat
from ..user_and_chats.user import User
class Str(str):
def __init__(self, *args):
super().__init__()
self._client = None
self._entities = None
def init(self, client, entities):
self._client = client
self._entities = entities
return self
@property
def text(self):
return self
@property
def markdown(self):
return self._client.markdown.unparse(self, self._entities)
@property
def html(self):
return self._client.html.unparse(self, self._entities)
class Message(PyrogramType, Update):
"""This object represents a message.
@ -268,7 +295,7 @@ class Message(PyrogramType, Update):
edit_date: int = None,
media_group_id: str = None,
author_signature: str = None,
text: str = None,
text: Str = None,
entities: List["pyrogram.MessageEntity"] = None,
caption_entities: List["pyrogram.MessageEntity"] = None,
audio: "pyrogram.Audio" = None,
@ -280,7 +307,7 @@ class Message(PyrogramType, Update):
video: "pyrogram.Video" = None,
voice: "pyrogram.Voice" = None,
video_note: "pyrogram.VideoNote" = None,
caption: str = None,
caption: Str = None,
contact: "pyrogram.Contact" = None,
location: "pyrogram.Location" = None,
venue: "pyrogram.Venue" = None,
@ -2519,7 +2546,13 @@ class Message(PyrogramType, Update):
reply_markup=reply_markup
)
def forward(self, chat_id: int or str, disable_notification: bool = None) -> "Message":
def forward(
self,
chat_id: int or str,
disable_notification: bool = None,
as_copy: bool = False,
remove_caption: bool = False
) -> "Message":
"""Bound method *forward* of :obj:`Message <pyrogram.Message>`.
Use as a shortcut for:
@ -2547,18 +2580,120 @@ class Message(PyrogramType, Update):
Sends the message silently.
Users will receive a notification with no sound.
as_copy (``bool``, *optional*):
Pass True to forward messages without the forward header (i.e.: send a copy of the message content).
Defaults to False.
remove_caption (``bool``, *optional*):
If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the
message. Has no effect if *as_copy* is not enabled.
Defaults to False.
Returns:
On success, the forwarded Message is returned.
Raises:
:class:`Error <pyrogram.Error>`
"""
return self._client.forward_messages(
chat_id=chat_id,
from_chat_id=self.chat.id,
message_ids=self.message_id,
disable_notification=disable_notification
)
if as_copy:
if self.service:
raise ValueError("Unable to copy service messages")
if self.game and not self._client.is_bot:
raise ValueError("Users cannot send messages with Game media type")
# TODO: Improve markdown parser. Currently html appears to be more stable, thus we use it here because users
# can"t choose.
if self.text:
return self._client.send_message(
chat_id,
text=self.text.html,
parse_mode="html",
disable_web_page_preview=not self.web_page,
disable_notification=disable_notification
)
elif self.media:
caption = self.caption.html if self.caption and not remove_caption else None
send_media = partial(
self._client.send_cached_media,
chat_id=chat_id,
disable_notification=disable_notification
)
if self.photo:
file_id = self.photo.sizes[-1].file_id
elif self.audio:
file_id = self.audio.file_id
elif self.document:
file_id = self.document.file_id
elif self.video:
file_id = self.video.file_id
elif self.animation:
file_id = self.animation.file_id
elif self.voice:
file_id = self.voice.file_id
elif self.sticker:
file_id = self.sticker.file_id
elif self.video_note:
file_id = self.video_note.file_id
elif self.contact:
return self._client.send_contact(
chat_id,
phone_number=self.contact.phone_number,
first_name=self.contact.first_name,
last_name=self.contact.last_name,
vcard=self.contact.vcard,
disable_notification=disable_notification
)
elif self.location:
return self._client.send_location(
chat_id,
latitude=self.location.latitude,
longitude=self.location.longitude,
disable_notification=disable_notification
)
elif self.venue:
return self._client.send_venue(
chat_id,
latitude=self.venue.location.latitude,
longitude=self.venue.location.longitude,
title=self.venue.title,
address=self.venue.address,
foursquare_id=self.venue.foursquare_id,
foursquare_type=self.venue.foursquare_type,
disable_notification=disable_notification
)
elif self.poll:
return self._client.send_poll(
chat_id,
question=self.poll.question,
options=[opt.text for opt in self.poll.options],
disable_notification=disable_notification
)
elif self.game:
return self._client.send_game(
chat_id,
game_short_name=self.game.short_name,
disable_notification=disable_notification
)
else:
raise ValueError("Unknown media type")
if self.sticker or self.video_note: # Sticker and VideoNote should have no caption
return send_media(file_id)
else:
return send_media(file_id=file_id, caption=caption, parse_mode=ParseMode.HTML)
else:
raise ValueError("Can't copy this message")
else:
return self._client.forward_messages(
chat_id=chat_id,
from_chat_id=self.chat.id,
message_ids=self.message_id,
disable_notification=disable_notification
)
def delete(self, revoke: bool = True):
"""Bound method *delete* of :obj:`Message <pyrogram.Message>`.
@ -2798,29 +2933,3 @@ class Message(PyrogramType, Update):
message_id=self.message_id,
disable_notification=disable_notification
)
class Str(str):
def __init__(self, *args):
super().__init__()
self.client = None
self.entities = None
def init(self, client, entities):
self.client = client
self.entities = entities
return self
@property
def text(self):
return self
@property
def markdown(self):
return self.client.markdown.unparse(self, self.entities)
@property
def html(self):
return self.client.html.unparse(self, self.entities)

View File

@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from typing import List
from typing import List, Union
import pyrogram
from pyrogram.api import types
@ -116,3 +116,55 @@ class Messages(PyrogramType, Update):
messages=parsed_messages,
client=client
)
def forward(
self,
chat_id: Union[int, str],
disable_notification: bool = None,
as_copy: bool = False,
remove_caption: bool = False
):
"""Bound method *forward* of :obj:`Message <pyrogram.Messages>`.
Args:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
disable_notification (``bool``, *optional*):
Sends messages silently.
Users will receive a notification with no sound.
as_copy (``bool``, *optional*):
Pass True to forward messages without the forward header (i.e.: send a copy of the message content).
Defaults to False.
remove_caption (``bool``, *optional*):
If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the
message. Has no effect if *as_copy* is not enabled.
Defaults to False.
Returns:
On success, a :class:`Messages <pyrogram.Messages>` containing forwarded messages is returned.
Raises:
:class:`Error <pyrogram.Error>`
"""
forwarded_messages = []
for message in self.messages:
forwarded_messages.append(
message.forward(
chat_id=chat_id,
as_copy=as_copy,
disable_notification=disable_notification,
remove_caption=remove_caption
)
)
return Messages(
total_count=len(forwarded_messages),
messages=forwarded_messages,
client=self._client
)

View File

@ -19,11 +19,13 @@
from collections import OrderedDict
from json import dumps
import pyrogram
class PyrogramType:
__slots__ = ["_client"]
def __init__(self, client):
def __init__(self, client: "pyrogram.client.ext.BaseClient"):
self._client = client
def __str__(self):