Skip to content

Commit 081b9b2

Browse files
bakatroubledelivrance
authored andcommitted
Add ability to forward messages as copies (pyrogram#227)
* Add ability to forward messages as copies * Add Messages.forward() method * Update and clean up code
1 parent ac591cf commit 081b9b2

File tree

7 files changed

+262
-68
lines changed

7 files changed

+262
-68
lines changed

pyrogram/client/methods/messages/forward_messages.py

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ def forward_messages(
2929
chat_id: Union[int, str],
3030
from_chat_id: Union[int, str],
3131
message_ids: Iterable[int],
32-
disable_notification: bool = None
32+
disable_notification: bool = None,
33+
as_copy: bool = False,
34+
remove_caption: bool = False
3335
) -> "pyrogram.Messages":
3436
"""Use this method to forward messages of any kind.
3537
@@ -52,6 +54,15 @@ def forward_messages(
5254
Sends the message silently.
5355
Users will receive a notification with no sound.
5456
57+
as_copy (``bool``, *optional*):
58+
Pass True to forward messages without the forward header (i.e.: send a copy of the message content).
59+
Defaults to False.
60+
61+
remove_caption (``bool``, *optional*):
62+
If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the
63+
message. Has no effect if *as_copy* is not enabled.
64+
Defaults to False.
65+
5566
Returns:
5667
On success and in case *message_ids* was an iterable, the returned value will be a list of the forwarded
5768
:obj:`Messages <pyrogram.Message>` even if a list contains just one element, otherwise if
@@ -61,35 +72,55 @@ def forward_messages(
6172
Raises:
6273
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
6374
"""
75+
6476
is_iterable = not isinstance(message_ids, int)
6577
message_ids = list(message_ids) if is_iterable else [message_ids]
6678

67-
r = self.send(
68-
functions.messages.ForwardMessages(
69-
to_peer=self.resolve_peer(chat_id),
70-
from_peer=self.resolve_peer(from_chat_id),
71-
id=message_ids,
72-
silent=disable_notification or None,
73-
random_id=[self.rnd_id() for _ in message_ids]
79+
if as_copy:
80+
sent_messages = []
81+
for chunk in [message_ids[i:i + 200] for i in range(0, len(message_ids), 200)]:
82+
messages = self.get_messages(chat_id=from_chat_id, message_ids=chunk) # type: pyrogram.Messages
83+
for message in messages.messages:
84+
sent_messages.append(
85+
message.forward(
86+
chat_id,
87+
disable_notification=disable_notification,
88+
as_copy=True,
89+
remove_caption=remove_caption
90+
)
91+
)
92+
return pyrogram.Messages(
93+
client=self,
94+
total_count=len(sent_messages),
95+
messages=sent_messages
96+
) if is_iterable else sent_messages[0]
97+
else:
98+
r = self.send(
99+
functions.messages.ForwardMessages(
100+
to_peer=self.resolve_peer(chat_id),
101+
from_peer=self.resolve_peer(from_chat_id),
102+
id=message_ids,
103+
silent=disable_notification or None,
104+
random_id=[self.rnd_id() for _ in message_ids]
105+
)
74106
)
75-
)
76107

77-
messages = []
108+
forwarded_messages = []
78109

79-
users = {i.id: i for i in r.users}
80-
chats = {i.id: i for i in r.chats}
110+
users = {i.id: i for i in r.users}
111+
chats = {i.id: i for i in r.chats}
81112

82-
for i in r.updates:
83-
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
84-
messages.append(
85-
pyrogram.Message._parse(
86-
self, i.message,
87-
users, chats
113+
for i in r.updates:
114+
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
115+
forwarded_messages.append(
116+
pyrogram.Message._parse(
117+
self, i.message,
118+
users, chats
119+
)
88120
)
89-
)
90121

91-
return pyrogram.Messages(
92-
client=self,
93-
total_count=len(messages),
94-
messages=messages
95-
) if is_iterable else messages[0]
122+
return pyrogram.Messages(
123+
client=self,
124+
total_count=len(forwarded_messages),
125+
messages=forwarded_messages
126+
) if is_iterable else forwarded_messages[0]

pyrogram/client/methods/messages/send_contact.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ def send_contact(
2929
chat_id: Union[int, str],
3030
phone_number: str,
3131
first_name: str,
32-
last_name: str = "",
33-
vcard: str = "",
32+
last_name: str = None,
33+
vcard: str = None,
3434
disable_notification: bool = None,
3535
reply_to_message_id: int = None,
3636
reply_markup: Union[
@@ -83,8 +83,8 @@ def send_contact(
8383
media=types.InputMediaContact(
8484
phone_number=phone_number,
8585
first_name=first_name,
86-
last_name=last_name,
87-
vcard=vcard
86+
last_name=last_name or "",
87+
vcard=vcard or ""
8888
),
8989
message="",
9090
silent=disable_notification or None,

pyrogram/client/style/html.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def __init__(self, peers_by_id):
4040

4141
def parse(self, message: str):
4242
entities = []
43-
message = utils.add_surrogates(str(message))
43+
message = utils.add_surrogates(str(message or ""))
4444
offset = 0
4545

4646
for match in self.HTML_RE.finditer(message):

pyrogram/client/style/markdown.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def __init__(self, peers_by_id: dict):
5656
self.peers_by_id = peers_by_id
5757

5858
def parse(self, message: str):
59-
message = utils.add_surrogates(str(message)).strip()
59+
message = utils.add_surrogates(str(message or "")).strip()
6060
entities = []
6161
offset = 0
6262

pyrogram/client/types/messages_and_media/message.py

Lines changed: 145 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
# You should have received a copy of the GNU Lesser General Public License
1717
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
1818

19+
from functools import partial
1920
from typing import List, Match, Union
2021

2122
import pyrogram
2223
from pyrogram.api import types
2324
from pyrogram.api.errors import MessageIdsEmpty
24-
from pyrogram.client.ext import ChatAction
25+
from pyrogram.client.ext import ChatAction, ParseMode
2526
from pyrogram.client.types.input_media import InputMedia
2627
from .contact import Contact
2728
from .location import Location
@@ -33,6 +34,32 @@
3334
from ..user_and_chats.user import User
3435

3536

37+
class Str(str):
38+
def __init__(self, *args):
39+
super().__init__()
40+
41+
self._client = None
42+
self._entities = None
43+
44+
def init(self, client, entities):
45+
self._client = client
46+
self._entities = entities
47+
48+
return self
49+
50+
@property
51+
def text(self):
52+
return self
53+
54+
@property
55+
def markdown(self):
56+
return self._client.markdown.unparse(self, self._entities)
57+
58+
@property
59+
def html(self):
60+
return self._client.html.unparse(self, self._entities)
61+
62+
3663
class Message(PyrogramType, Update):
3764
"""This object represents a message.
3865
@@ -268,7 +295,7 @@ def __init__(
268295
edit_date: int = None,
269296
media_group_id: str = None,
270297
author_signature: str = None,
271-
text: str = None,
298+
text: Str = None,
272299
entities: List["pyrogram.MessageEntity"] = None,
273300
caption_entities: List["pyrogram.MessageEntity"] = None,
274301
audio: "pyrogram.Audio" = None,
@@ -280,7 +307,7 @@ def __init__(
280307
video: "pyrogram.Video" = None,
281308
voice: "pyrogram.Voice" = None,
282309
video_note: "pyrogram.VideoNote" = None,
283-
caption: str = None,
310+
caption: Str = None,
284311
contact: "pyrogram.Contact" = None,
285312
location: "pyrogram.Location" = None,
286313
venue: "pyrogram.Venue" = None,
@@ -2519,7 +2546,13 @@ def edit_reply_markup(self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None
25192546
reply_markup=reply_markup
25202547
)
25212548

2522-
def forward(self, chat_id: int or str, disable_notification: bool = None) -> "Message":
2549+
def forward(
2550+
self,
2551+
chat_id: int or str,
2552+
disable_notification: bool = None,
2553+
as_copy: bool = False,
2554+
remove_caption: bool = False
2555+
) -> "Message":
25232556
"""Bound method *forward* of :obj:`Message <pyrogram.Message>`.
25242557
25252558
Use as a shortcut for:
@@ -2547,18 +2580,120 @@ def forward(self, chat_id: int or str, disable_notification: bool = None) -> "Me
25472580
Sends the message silently.
25482581
Users will receive a notification with no sound.
25492582
2583+
as_copy (``bool``, *optional*):
2584+
Pass True to forward messages without the forward header (i.e.: send a copy of the message content).
2585+
Defaults to False.
2586+
2587+
remove_caption (``bool``, *optional*):
2588+
If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the
2589+
message. Has no effect if *as_copy* is not enabled.
2590+
Defaults to False.
2591+
25502592
Returns:
25512593
On success, the forwarded Message is returned.
25522594
25532595
Raises:
25542596
:class:`Error <pyrogram.Error>`
25552597
"""
2556-
return self._client.forward_messages(
2557-
chat_id=chat_id,
2558-
from_chat_id=self.chat.id,
2559-
message_ids=self.message_id,
2560-
disable_notification=disable_notification
2561-
)
2598+
if as_copy:
2599+
if self.service:
2600+
raise ValueError("Unable to copy service messages")
2601+
2602+
if self.game and not self._client.is_bot:
2603+
raise ValueError("Users cannot send messages with Game media type")
2604+
2605+
# TODO: Improve markdown parser. Currently html appears to be more stable, thus we use it here because users
2606+
# can"t choose.
2607+
2608+
if self.text:
2609+
return self._client.send_message(
2610+
chat_id,
2611+
text=self.text.html,
2612+
parse_mode="html",
2613+
disable_web_page_preview=not self.web_page,
2614+
disable_notification=disable_notification
2615+
)
2616+
elif self.media:
2617+
caption = self.caption.html if self.caption and not remove_caption else None
2618+
2619+
send_media = partial(
2620+
self._client.send_cached_media,
2621+
chat_id=chat_id,
2622+
disable_notification=disable_notification
2623+
)
2624+
2625+
if self.photo:
2626+
file_id = self.photo.sizes[-1].file_id
2627+
elif self.audio:
2628+
file_id = self.audio.file_id
2629+
elif self.document:
2630+
file_id = self.document.file_id
2631+
elif self.video:
2632+
file_id = self.video.file_id
2633+
elif self.animation:
2634+
file_id = self.animation.file_id
2635+
elif self.voice:
2636+
file_id = self.voice.file_id
2637+
elif self.sticker:
2638+
file_id = self.sticker.file_id
2639+
elif self.video_note:
2640+
file_id = self.video_note.file_id
2641+
elif self.contact:
2642+
return self._client.send_contact(
2643+
chat_id,
2644+
phone_number=self.contact.phone_number,
2645+
first_name=self.contact.first_name,
2646+
last_name=self.contact.last_name,
2647+
vcard=self.contact.vcard,
2648+
disable_notification=disable_notification
2649+
)
2650+
elif self.location:
2651+
return self._client.send_location(
2652+
chat_id,
2653+
latitude=self.location.latitude,
2654+
longitude=self.location.longitude,
2655+
disable_notification=disable_notification
2656+
)
2657+
elif self.venue:
2658+
return self._client.send_venue(
2659+
chat_id,
2660+
latitude=self.venue.location.latitude,
2661+
longitude=self.venue.location.longitude,
2662+
title=self.venue.title,
2663+
address=self.venue.address,
2664+
foursquare_id=self.venue.foursquare_id,
2665+
foursquare_type=self.venue.foursquare_type,
2666+
disable_notification=disable_notification
2667+
)
2668+
elif self.poll:
2669+
return self._client.send_poll(
2670+
chat_id,
2671+
question=self.poll.question,
2672+
options=[opt.text for opt in self.poll.options],
2673+
disable_notification=disable_notification
2674+
)
2675+
elif self.game:
2676+
return self._client.send_game(
2677+
chat_id,
2678+
game_short_name=self.game.short_name,
2679+
disable_notification=disable_notification
2680+
)
2681+
else:
2682+
raise ValueError("Unknown media type")
2683+
2684+
if self.sticker or self.video_note: # Sticker and VideoNote should have no caption
2685+
return send_media(file_id)
2686+
else:
2687+
return send_media(file_id=file_id, caption=caption, parse_mode=ParseMode.HTML)
2688+
else:
2689+
raise ValueError("Can't copy this message")
2690+
else:
2691+
return self._client.forward_messages(
2692+
chat_id=chat_id,
2693+
from_chat_id=self.chat.id,
2694+
message_ids=self.message_id,
2695+
disable_notification=disable_notification
2696+
)
25622697

25632698
def delete(self, revoke: bool = True):
25642699
"""Bound method *delete* of :obj:`Message <pyrogram.Message>`.
@@ -2798,29 +2933,3 @@ def pin(self, disable_notification: bool = None) -> "Message":
27982933
message_id=self.message_id,
27992934
disable_notification=disable_notification
28002935
)
2801-
2802-
2803-
class Str(str):
2804-
def __init__(self, *args):
2805-
super().__init__()
2806-
2807-
self.client = None
2808-
self.entities = None
2809-
2810-
def init(self, client, entities):
2811-
self.client = client
2812-
self.entities = entities
2813-
2814-
return self
2815-
2816-
@property
2817-
def text(self):
2818-
return self
2819-
2820-
@property
2821-
def markdown(self):
2822-
return self.client.markdown.unparse(self, self.entities)
2823-
2824-
@property
2825-
def html(self):
2826-
return self.client.html.unparse(self, self.entities)

0 commit comments

Comments
 (0)