Skip to content

Commit 5e60e7a

Browse files
authored
Make possible to attach photos, videos, stickers and locations to polls (#306)
1 parent 71c90af commit 5e60e7a

14 files changed

Lines changed: 658 additions & 233 deletions

compiler/docs/compiler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,7 @@ def get_title_list(s: str) -> list:
918918
InputMediaDocument
919919
InputMediaPhoto
920920
InputMediaVideo
921+
InputMediaSticker
921922
InputMessageContent
922923
InputPhoneContact
923924
InputPollOption

pyrogram/methods/messages/edit_message_media.py

Lines changed: 25 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,10 @@
1717
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
1818

1919
from datetime import datetime
20-
import io
21-
import os
22-
import re
23-
from typing import Union
20+
from typing import Optional, Union
2421

2522
import pyrogram
26-
from pyrogram import raw
27-
from pyrogram import types
28-
from pyrogram import utils
29-
from pyrogram.file_id import FileType
23+
from pyrogram import raw, types, utils
3024

3125

3226
class EditMessageMedia:
@@ -35,11 +29,10 @@ async def edit_message_media(
3529
chat_id: Union[int, str],
3630
message_id: int,
3731
media: "types.InputMedia",
38-
show_caption_above_media: bool = None,
39-
schedule_date: datetime = None,
40-
business_connection_id: str = None,
41-
reply_markup: "types.InlineKeyboardMarkup" = None,
42-
file_name: str = None
32+
show_caption_above_media: Optional[bool] = None,
33+
schedule_date: Optional[datetime] = None,
34+
business_connection_id: Optional[str] = None,
35+
reply_markup: Optional["types.InlineKeyboardMarkup"] = None,
4336
) -> "types.Message":
4437
"""Edit animation, audio, document, photo or video messages, or to add media to text messages.
4538
@@ -107,202 +100,38 @@ async def edit_message_media(
107100
message, entities = None, None
108101

109102
if caption is not None:
110-
message, entities = (await utils.parse_text_entities(self, caption, parse_mode, caption_entities)).values()
111-
112-
if isinstance(media, types.InputMediaPhoto):
113-
if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media):
114-
uploaded_media = await self.invoke(
115-
raw.functions.messages.UploadMedia(
116-
peer=await self.resolve_peer(chat_id),
117-
media=raw.types.InputMediaUploadedPhoto(
118-
file=await self.save_file(media.media),
119-
spoiler=media.has_spoiler
120-
)
121-
)
122-
)
123-
124-
media = raw.types.InputMediaPhoto(
125-
id=raw.types.InputPhoto(
126-
id=uploaded_media.photo.id,
127-
access_hash=uploaded_media.photo.access_hash,
128-
file_reference=uploaded_media.photo.file_reference
129-
),
130-
spoiler=media.has_spoiler
131-
)
132-
elif re.match("^https?://", media.media):
133-
media = raw.types.InputMediaPhotoExternal(
134-
url=media.media,
135-
spoiler=media.has_spoiler
136-
)
137-
else:
138-
media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO, has_spoiler=media.has_spoiler)
139-
elif isinstance(media, types.InputMediaVideo):
140-
if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media):
141-
uploaded_media = await self.invoke(
142-
raw.functions.messages.UploadMedia(
143-
peer=await self.resolve_peer(chat_id),
144-
media=raw.types.InputMediaUploadedDocument(
145-
mime_type=self.guess_mime_type(media.media) or "video/mp4",
146-
thumb=await self.save_file(media.thumb),
147-
spoiler=media.has_spoiler,
148-
file=await self.save_file(media.media),
149-
attributes=[
150-
raw.types.DocumentAttributeVideo(
151-
supports_streaming=media.supports_streaming or None,
152-
duration=media.duration,
153-
w=media.width,
154-
h=media.height
155-
),
156-
raw.types.DocumentAttributeFilename(
157-
file_name=file_name or os.path.basename(media.media)
158-
)
159-
]
160-
)
161-
)
162-
)
163-
164-
media = raw.types.InputMediaDocument(
165-
id=raw.types.InputDocument(
166-
id=uploaded_media.document.id,
167-
access_hash=uploaded_media.document.access_hash,
168-
file_reference=uploaded_media.document.file_reference
169-
),
170-
spoiler=media.has_spoiler
171-
)
172-
elif re.match("^https?://", media.media):
173-
media = raw.types.InputMediaDocumentExternal(
174-
url=media.media,
175-
spoiler=media.has_spoiler
176-
)
177-
else:
178-
media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO, has_spoiler=media.has_spoiler)
179-
elif isinstance(media, types.InputMediaAudio):
180-
if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media):
181-
media = await self.invoke(
182-
raw.functions.messages.UploadMedia(
183-
peer=await self.resolve_peer(chat_id),
184-
media=raw.types.InputMediaUploadedDocument(
185-
mime_type=self.guess_mime_type(media.media) or "audio/mpeg",
186-
thumb=await self.save_file(media.thumb),
187-
file=await self.save_file(media.media),
188-
attributes=[
189-
raw.types.DocumentAttributeAudio(
190-
duration=media.duration,
191-
performer=media.performer,
192-
title=media.title
193-
),
194-
raw.types.DocumentAttributeFilename(
195-
file_name=file_name or os.path.basename(media.media)
196-
)
197-
]
198-
)
199-
)
200-
)
201-
202-
media = raw.types.InputMediaDocument(
203-
id=raw.types.InputDocument(
204-
id=media.document.id,
205-
access_hash=media.document.access_hash,
206-
file_reference=media.document.file_reference
207-
)
208-
)
209-
elif re.match("^https?://", media.media):
210-
media = raw.types.InputMediaDocumentExternal(
211-
url=media.media
212-
)
213-
else:
214-
media = utils.get_input_media_from_file_id(media.media, FileType.AUDIO)
215-
elif isinstance(media, types.InputMediaAnimation):
216-
if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media):
217-
uploaded_media = await self.invoke(
218-
raw.functions.messages.UploadMedia(
219-
peer=await self.resolve_peer(chat_id),
220-
media=raw.types.InputMediaUploadedDocument(
221-
mime_type=self.guess_mime_type(media.media) or "video/mp4",
222-
thumb=await self.save_file(media.thumb),
223-
spoiler=media.has_spoiler,
224-
file=await self.save_file(media.media),
225-
attributes=[
226-
raw.types.DocumentAttributeVideo(
227-
supports_streaming=True,
228-
duration=media.duration,
229-
w=media.width,
230-
h=media.height
231-
),
232-
raw.types.DocumentAttributeFilename(
233-
file_name=file_name or os.path.basename(media.media)
234-
),
235-
raw.types.DocumentAttributeAnimated()
236-
]
237-
)
238-
)
239-
)
240-
241-
media = raw.types.InputMediaDocument(
242-
id=raw.types.InputDocument(
243-
id=uploaded_media.document.id,
244-
access_hash=uploaded_media.document.access_hash,
245-
file_reference=uploaded_media.document.file_reference
246-
),
247-
spoiler=media.has_spoiler
248-
)
249-
elif re.match("^https?://", media.media):
250-
media = raw.types.InputMediaDocumentExternal(
251-
url=media.media,
252-
spoiler=media.has_spoiler
253-
)
254-
else:
255-
media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION, has_spoiler=media.has_spoiler)
256-
elif isinstance(media, types.InputMediaDocument):
257-
if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media):
258-
media = await self.invoke(
259-
raw.functions.messages.UploadMedia(
260-
peer=await self.resolve_peer(chat_id),
261-
media=raw.types.InputMediaUploadedDocument(
262-
mime_type=self.guess_mime_type(media.media) or "application/zip",
263-
thumb=await self.save_file(media.thumb),
264-
file=await self.save_file(media.media),
265-
attributes=[
266-
raw.types.DocumentAttributeFilename(
267-
file_name=file_name or os.path.basename(media.media)
268-
)
269-
]
270-
)
271-
)
272-
)
273-
274-
media = raw.types.InputMediaDocument(
275-
id=raw.types.InputDocument(
276-
id=media.document.id,
277-
access_hash=media.document.access_hash,
278-
file_reference=media.document.file_reference
279-
)
280-
)
281-
elif re.match("^https?://", media.media):
282-
media = raw.types.InputMediaDocumentExternal(
283-
url=media.media
284-
)
285-
else:
286-
media = utils.get_input_media_from_file_id(media.media, FileType.DOCUMENT)
103+
message, entities = (
104+
await utils.parse_text_entities(self, caption, parse_mode, caption_entities)
105+
).values()
106+
107+
if not isinstance(
108+
media,
109+
(
110+
types.InputMediaPhoto,
111+
types.InputMediaVideo,
112+
types.InputMediaAudio,
113+
types.InputMediaAnimation,
114+
types.InputMediaDocument,
115+
),
116+
):
117+
raise ValueError(f"Unsupported media type {type(media)}")
287118

288119
r = await self.invoke(
289120
raw.functions.messages.EditMessage(
290121
peer=await self.resolve_peer(chat_id),
291122
id=message_id,
292123
invert_media=show_caption_above_media,
293-
media=media,
124+
media=await media.write(client=self),
294125
schedule_date=utils.datetime_to_timestamp(schedule_date),
295126
reply_markup=await reply_markup.write(self) if reply_markup else None,
296127
message=message,
297-
entities=entities
128+
entities=entities,
298129
),
299-
business_connection_id=business_connection_id
130+
business_connection_id=business_connection_id,
300131
)
301132

302133
for i in r.updates:
303134
if isinstance(i, (raw.types.UpdateEditMessage, raw.types.UpdateEditChannelMessage)):
304135
return await types.Message._parse(
305-
self, i.message,
306-
{i.id: i for i in r.users},
307-
{i.id: i for i in r.chats}
136+
self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}
308137
)

pyrogram/methods/messages/send_poll.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ async def send_poll(
3131
chat_id: Union[int, str],
3232
question: Union[str, "types.FormattedText"],
3333
options: List[Union[str, "types.InputPollOption"]],
34+
media: Optional["types.InputMedia"] = None,
3435
message_thread_id: Optional[int] = None,
3536
business_connection_id: Optional[str] = None,
3637
is_anonymous: bool = True,
@@ -85,6 +86,10 @@ async def send_poll(
8586
options (List of :obj:`~pyrogram.types.InputPollOption`):
8687
List of 2-12 answer options.
8788
89+
media (:obj:`~pyrogram.types.InputMediaPhoto` | :obj:`~pyrogram.types.InputMediaVideo` | :obj:`~pyrogram.types.InputMediaSticker` | :obj:`~pyrogram.types.Location`, *optional*):
90+
Media attached to the poll.
91+
Currently supports only photo, video, sticker or location.
92+
8893
message_thread_id (``int``, *optional*):
8994
Unique identifier for the target message thread (topic) of the forum.
9095
For supergroups only.
@@ -179,6 +184,7 @@ async def send_poll(
179184
Example:
180185
.. code-block:: python
181186
187+
# Regular poll
182188
await app.send_poll(
183189
chat_id=chat_id,
184190
question="Is this a poll question?",
@@ -188,7 +194,38 @@ async def send_poll(
188194
types.InputPollOption(text="Maybe")
189195
]
190196
)
197+
198+
# Poll with media
199+
await app.send_poll(
200+
chat_id=chat_id,
201+
question="Where we are?",
202+
media=types.InputMediaPhoto("photo.jpg"),
203+
options=[
204+
types.InputPollOption(
205+
text="Maybe here?",
206+
media=types.InputMediaPhoto("photo1.jpg")
207+
),
208+
types.InputPollOption(
209+
text="Or here?",
210+
media=types.Location(
211+
longitude=49.807760,
212+
latitude=73.088504
213+
),
214+
),
215+
]
216+
)
191217
"""
218+
if media is not None and not isinstance(
219+
media,
220+
(
221+
types.InputMediaPhoto,
222+
types.InputMediaVideo,
223+
types.InputMediaSticker,
224+
types.Location,
225+
),
226+
):
227+
raise ValueError("Unsupported media type")
228+
192229
if isinstance(question, str):
193230
question = types.FormattedText(text=question)
194231

@@ -246,6 +283,7 @@ async def send_poll(
246283
close_date=utils.datetime_to_timestamp(close_date)
247284
),
248285
correct_answers=correct_option_ids,
286+
attached_media=await media.write(client=self) if media is not None else None,
249287
solution=solution,
250288
solution_entities=solution_entities
251289
),

pyrogram/types/input_content/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from .input_media_document import InputMediaDocument
3636
from .input_media_photo import InputMediaPhoto
3737
from .input_media_video import InputMediaVideo
38+
from .input_media_sticker import InputMediaSticker
3839
from .input_message_content import InputMessageContent
3940
from .input_phone_contact import InputPhoneContact
4041
from .input_poll_option import InputPollOption
@@ -77,6 +78,7 @@
7778
"InputMediaDocument",
7879
"InputMediaPhoto",
7980
"InputMediaVideo",
81+
"InputMediaSticker",
8082
"InputMessageContent",
8183
"InputPhoneContact",
8284
"InputPollOption",

pyrogram/types/input_content/input_media.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
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 typing import List, Union, BinaryIO
19+
from typing import TYPE_CHECKING, BinaryIO, List, Optional, Union
2020

2121
from ..messages_and_media import MessageEntity
2222
from ..object import Object
2323

24+
if TYPE_CHECKING:
25+
from pyrogram import raw
26+
2427

2528
class InputMedia(Object):
2629
"""Content of a media message to be sent.
@@ -32,18 +35,22 @@ class InputMedia(Object):
3235
- :obj:`~pyrogram.types.InputMediaAudio`
3336
- :obj:`~pyrogram.types.InputMediaPhoto`
3437
- :obj:`~pyrogram.types.InputMediaVideo`
38+
- :obj:`~pyrogram.types.InputMediaSticker`
3539
"""
3640

3741
def __init__(
3842
self,
3943
media: Union[str, BinaryIO],
4044
caption: str = "",
41-
parse_mode: str = None,
42-
caption_entities: List[MessageEntity] = None
45+
parse_mode: Optional[str] = None,
46+
caption_entities: Optional[List[MessageEntity]] = None,
4347
):
4448
super().__init__()
4549

4650
self.media = media
4751
self.caption = caption
4852
self.parse_mode = parse_mode
4953
self.caption_entities = caption_entities
54+
55+
async def write(self, **kwargs) -> "raw.base.InputMedia":
56+
raise NotImplementedError

0 commit comments

Comments
 (0)