mirror of
https://github.com/pyrogram/pyrogram
synced 2025-08-28 21:07:59 +00:00
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:
parent
ac591cf3c7
commit
081b9b280a
@ -29,7 +29,9 @@ class ForwardMessages(BaseClient):
|
|||||||
chat_id: Union[int, str],
|
chat_id: Union[int, str],
|
||||||
from_chat_id: Union[int, str],
|
from_chat_id: Union[int, str],
|
||||||
message_ids: Iterable[int],
|
message_ids: Iterable[int],
|
||||||
disable_notification: bool = None
|
disable_notification: bool = None,
|
||||||
|
as_copy: bool = False,
|
||||||
|
remove_caption: bool = False
|
||||||
) -> "pyrogram.Messages":
|
) -> "pyrogram.Messages":
|
||||||
"""Use this method to forward messages of any kind.
|
"""Use this method to forward messages of any kind.
|
||||||
|
|
||||||
@ -52,6 +54,15 @@ class ForwardMessages(BaseClient):
|
|||||||
Sends the message silently.
|
Sends the message silently.
|
||||||
Users will receive a notification with no sound.
|
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:
|
Returns:
|
||||||
On success and in case *message_ids* was an iterable, the returned value will be a list of the forwarded
|
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
|
:obj:`Messages <pyrogram.Message>` even if a list contains just one element, otherwise if
|
||||||
@ -61,35 +72,55 @@ class ForwardMessages(BaseClient):
|
|||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
is_iterable = not isinstance(message_ids, int)
|
is_iterable = not isinstance(message_ids, int)
|
||||||
message_ids = list(message_ids) if is_iterable else [message_ids]
|
message_ids = list(message_ids) if is_iterable else [message_ids]
|
||||||
|
|
||||||
r = self.send(
|
if as_copy:
|
||||||
functions.messages.ForwardMessages(
|
sent_messages = []
|
||||||
to_peer=self.resolve_peer(chat_id),
|
for chunk in [message_ids[i:i + 200] for i in range(0, len(message_ids), 200)]:
|
||||||
from_peer=self.resolve_peer(from_chat_id),
|
messages = self.get_messages(chat_id=from_chat_id, message_ids=chunk) # type: pyrogram.Messages
|
||||||
id=message_ids,
|
for message in messages.messages:
|
||||||
silent=disable_notification or None,
|
sent_messages.append(
|
||||||
random_id=[self.rnd_id() for _ in message_ids]
|
message.forward(
|
||||||
)
|
chat_id,
|
||||||
)
|
disable_notification=disable_notification,
|
||||||
|
as_copy=True,
|
||||||
messages = []
|
remove_caption=remove_caption
|
||||||
|
)
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
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(
|
forwarded_messages = []
|
||||||
client=self,
|
|
||||||
total_count=len(messages),
|
users = {i.id: i for i in r.users}
|
||||||
messages=messages
|
chats = {i.id: i for i in r.chats}
|
||||||
) if is_iterable else messages[0]
|
|
||||||
|
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]
|
||||||
|
@ -29,8 +29,8 @@ class SendContact(BaseClient):
|
|||||||
chat_id: Union[int, str],
|
chat_id: Union[int, str],
|
||||||
phone_number: str,
|
phone_number: str,
|
||||||
first_name: str,
|
first_name: str,
|
||||||
last_name: str = "",
|
last_name: str = None,
|
||||||
vcard: str = "",
|
vcard: str = None,
|
||||||
disable_notification: bool = None,
|
disable_notification: bool = None,
|
||||||
reply_to_message_id: int = None,
|
reply_to_message_id: int = None,
|
||||||
reply_markup: Union[
|
reply_markup: Union[
|
||||||
@ -83,8 +83,8 @@ class SendContact(BaseClient):
|
|||||||
media=types.InputMediaContact(
|
media=types.InputMediaContact(
|
||||||
phone_number=phone_number,
|
phone_number=phone_number,
|
||||||
first_name=first_name,
|
first_name=first_name,
|
||||||
last_name=last_name,
|
last_name=last_name or "",
|
||||||
vcard=vcard
|
vcard=vcard or ""
|
||||||
),
|
),
|
||||||
message="",
|
message="",
|
||||||
silent=disable_notification or None,
|
silent=disable_notification or None,
|
||||||
|
@ -40,7 +40,7 @@ class HTML:
|
|||||||
|
|
||||||
def parse(self, message: str):
|
def parse(self, message: str):
|
||||||
entities = []
|
entities = []
|
||||||
message = utils.add_surrogates(str(message))
|
message = utils.add_surrogates(str(message or ""))
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
||||||
for match in self.HTML_RE.finditer(message):
|
for match in self.HTML_RE.finditer(message):
|
||||||
|
@ -56,7 +56,7 @@ class Markdown:
|
|||||||
self.peers_by_id = peers_by_id
|
self.peers_by_id = peers_by_id
|
||||||
|
|
||||||
def parse(self, message: str):
|
def parse(self, message: str):
|
||||||
message = utils.add_surrogates(str(message)).strip()
|
message = utils.add_surrogates(str(message or "")).strip()
|
||||||
entities = []
|
entities = []
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
||||||
|
@ -16,12 +16,13 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
from typing import List, Match, Union
|
from typing import List, Match, Union
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
from pyrogram.api import types
|
from pyrogram.api import types
|
||||||
from pyrogram.api.errors import MessageIdsEmpty
|
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 pyrogram.client.types.input_media import InputMedia
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
from .location import Location
|
from .location import Location
|
||||||
@ -33,6 +34,32 @@ from ..user_and_chats.chat import Chat
|
|||||||
from ..user_and_chats.user import User
|
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):
|
class Message(PyrogramType, Update):
|
||||||
"""This object represents a message.
|
"""This object represents a message.
|
||||||
|
|
||||||
@ -268,7 +295,7 @@ class Message(PyrogramType, Update):
|
|||||||
edit_date: int = None,
|
edit_date: int = None,
|
||||||
media_group_id: str = None,
|
media_group_id: str = None,
|
||||||
author_signature: str = None,
|
author_signature: str = None,
|
||||||
text: str = None,
|
text: Str = None,
|
||||||
entities: List["pyrogram.MessageEntity"] = None,
|
entities: List["pyrogram.MessageEntity"] = None,
|
||||||
caption_entities: List["pyrogram.MessageEntity"] = None,
|
caption_entities: List["pyrogram.MessageEntity"] = None,
|
||||||
audio: "pyrogram.Audio" = None,
|
audio: "pyrogram.Audio" = None,
|
||||||
@ -280,7 +307,7 @@ class Message(PyrogramType, Update):
|
|||||||
video: "pyrogram.Video" = None,
|
video: "pyrogram.Video" = None,
|
||||||
voice: "pyrogram.Voice" = None,
|
voice: "pyrogram.Voice" = None,
|
||||||
video_note: "pyrogram.VideoNote" = None,
|
video_note: "pyrogram.VideoNote" = None,
|
||||||
caption: str = None,
|
caption: Str = None,
|
||||||
contact: "pyrogram.Contact" = None,
|
contact: "pyrogram.Contact" = None,
|
||||||
location: "pyrogram.Location" = None,
|
location: "pyrogram.Location" = None,
|
||||||
venue: "pyrogram.Venue" = None,
|
venue: "pyrogram.Venue" = None,
|
||||||
@ -2519,7 +2546,13 @@ class Message(PyrogramType, Update):
|
|||||||
reply_markup=reply_markup
|
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>`.
|
"""Bound method *forward* of :obj:`Message <pyrogram.Message>`.
|
||||||
|
|
||||||
Use as a shortcut for:
|
Use as a shortcut for:
|
||||||
@ -2547,18 +2580,120 @@ class Message(PyrogramType, Update):
|
|||||||
Sends the message silently.
|
Sends the message silently.
|
||||||
Users will receive a notification with no sound.
|
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:
|
Returns:
|
||||||
On success, the forwarded Message is returned.
|
On success, the forwarded Message is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>`
|
||||||
"""
|
"""
|
||||||
return self._client.forward_messages(
|
if as_copy:
|
||||||
chat_id=chat_id,
|
if self.service:
|
||||||
from_chat_id=self.chat.id,
|
raise ValueError("Unable to copy service messages")
|
||||||
message_ids=self.message_id,
|
|
||||||
disable_notification=disable_notification
|
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):
|
def delete(self, revoke: bool = True):
|
||||||
"""Bound method *delete* of :obj:`Message <pyrogram.Message>`.
|
"""Bound method *delete* of :obj:`Message <pyrogram.Message>`.
|
||||||
@ -2798,29 +2933,3 @@ class Message(PyrogramType, Update):
|
|||||||
message_id=self.message_id,
|
message_id=self.message_id,
|
||||||
disable_notification=disable_notification
|
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)
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from typing import List
|
from typing import List, Union
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
from pyrogram.api import types
|
from pyrogram.api import types
|
||||||
@ -116,3 +116,55 @@ class Messages(PyrogramType, Update):
|
|||||||
messages=parsed_messages,
|
messages=parsed_messages,
|
||||||
client=client
|
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
|
||||||
|
)
|
||||||
|
@ -19,11 +19,13 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
|
||||||
|
|
||||||
class PyrogramType:
|
class PyrogramType:
|
||||||
__slots__ = ["_client"]
|
__slots__ = ["_client"]
|
||||||
|
|
||||||
def __init__(self, client):
|
def __init__(self, client: "pyrogram.client.ext.BaseClient"):
|
||||||
self._client = client
|
self._client = client
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user