mirror of
				https://github.com/LonamiWebs/Telethon.git
				synced 2025-10-30 23:47:33 +03:00 
			
		
		
		
	Support sending scheduled messages
This commit is contained in:
		
							parent
							
								
									9dd73cd494
								
							
						
					
					
						commit
						dab237e758
					
				|  | @ -123,6 +123,7 @@ class MessageParseMethods: | ||||||
| 
 | 
 | ||||||
|         random_to_id = {} |         random_to_id = {} | ||||||
|         id_to_message = {} |         id_to_message = {} | ||||||
|  |         sched_to_message = {}  # scheduled IDs may collide with normal IDs | ||||||
|         for update in updates: |         for update in updates: | ||||||
|             if isinstance(update, types.UpdateMessageID): |             if isinstance(update, types.UpdateMessageID): | ||||||
|                 random_to_id[update.random_id] = update.id |                 random_to_id[update.random_id] = update.id | ||||||
|  | @ -145,12 +146,30 @@ class MessageParseMethods: | ||||||
|                     update.message._finish_init(self, entities, input_chat) |                     update.message._finish_init(self, entities, input_chat) | ||||||
|                     return update.message |                     return update.message | ||||||
| 
 | 
 | ||||||
|  |             elif isinstance(update, types.UpdateNewScheduledMessage): | ||||||
|  |                 update.message._finish_init(self, entities, input_chat) | ||||||
|  |                 sched_to_message[update.message.id] = update.message | ||||||
|  | 
 | ||||||
|         if request is None: |         if request is None: | ||||||
|             return id_to_message |             return id_to_message | ||||||
| 
 | 
 | ||||||
|  |         # Use the scheduled mapping if we got a request with a scheduled message | ||||||
|  |         # | ||||||
|  |         # This breaks if the schedule date is too young, however, since the message | ||||||
|  |         # is sent immediately, so have a fallback. | ||||||
|  |         if getattr(request, 'schedule_date', None) is None: | ||||||
|  |             mapping = id_to_message | ||||||
|  |             opposite = {}  # if there's no schedule it can never be scheduled | ||||||
|  |         else: | ||||||
|  |             mapping = sched_to_message | ||||||
|  |             opposite = id_to_message  # scheduled may be treated as normal, though | ||||||
|  | 
 | ||||||
|         random_id = request if isinstance(request, int) else request.random_id |         random_id = request if isinstance(request, int) else request.random_id | ||||||
|         if not utils.is_list_like(random_id): |         if not utils.is_list_like(random_id): | ||||||
|             msg = id_to_message.get(random_to_id.get(random_id)) |             msg = mapping.get(random_to_id.get(random_id)) | ||||||
|  |             if not msg: | ||||||
|  |                 msg = opposite.get(random_to_id.get(random_id)) | ||||||
|  | 
 | ||||||
|             if not msg: |             if not msg: | ||||||
|                 self._log[__name__].warning( |                 self._log[__name__].warning( | ||||||
|                     'Request %s had missing message mapping %s', request, result) |                     'Request %s had missing message mapping %s', request, result) | ||||||
|  | @ -158,15 +177,22 @@ class MessageParseMethods: | ||||||
|             return msg |             return msg | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             return [id_to_message[random_to_id[rnd]] for rnd in random_id] |             return [mapping[random_to_id[rnd]] for rnd in random_id] | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             # Sometimes forwards fail (`MESSAGE_ID_INVALID` if a message gets |             try: | ||||||
|             # deleted or `WORKER_BUSY_TOO_LONG_RETRY` if there are issues at |                 return [opposite[random_to_id[rnd]] for rnd in random_id] | ||||||
|             # Telegram), in which case we get some "missing" message mappings. |             except KeyError: | ||||||
|             # Log them with the hope that we can better work around them. |                 # Sometimes forwards fail (`MESSAGE_ID_INVALID` if a message gets | ||||||
|             self._log[__name__].warning( |                 # deleted or `WORKER_BUSY_TOO_LONG_RETRY` if there are issues at | ||||||
|                 'Request %s had missing message mappings %s', request, result) |                 # Telegram), in which case we get some "missing" message mappings. | ||||||
|  |                 # Log them with the hope that we can better work around them. | ||||||
|  |                 self._log[__name__].warning( | ||||||
|  |                     'Request %s had missing message mappings %s', request, result) | ||||||
| 
 | 
 | ||||||
|             return [id_to_message.get(random_to_id.get(rnd)) for rnd in random_to_id] |         return [ | ||||||
|  |             mapping.get(random_to_id.get(rnd)) | ||||||
|  |             or opposite.get(random_to_id.get(rnd)) | ||||||
|  |             for rnd in random_to_id | ||||||
|  |         ] | ||||||
| 
 | 
 | ||||||
|     # endregion |     # endregion | ||||||
|  |  | ||||||
|  | @ -536,7 +536,9 @@ class MessageMethods: | ||||||
|             force_document: bool = False, |             force_document: bool = False, | ||||||
|             clear_draft: bool = False, |             clear_draft: bool = False, | ||||||
|             buttons: 'hints.MarkupLike' = None, |             buttons: 'hints.MarkupLike' = None, | ||||||
|             silent: bool = None) -> 'types.Message': |             silent: bool = None, | ||||||
|  |             schedule: 'hints.DateLike' = None | ||||||
|  |     ) -> 'types.Message': | ||||||
|         """ |         """ | ||||||
|         Sends a message to the specified user, chat or channel. |         Sends a message to the specified user, chat or channel. | ||||||
| 
 | 
 | ||||||
|  | @ -609,6 +611,11 @@ class MessageMethods: | ||||||
|                 channel or not. Defaults to `False`, which means it will |                 channel or not. Defaults to `False`, which means it will | ||||||
|                 notify them. Set it to `True` to alter this behaviour. |                 notify them. Set it to `True` to alter this behaviour. | ||||||
| 
 | 
 | ||||||
|  |             schedule (`hints.DateLike`, optional): | ||||||
|  |                 If set, the message won't send immediately, and instead | ||||||
|  |                 it will be scheduled to be automatically sent at a later | ||||||
|  |                 time. | ||||||
|  | 
 | ||||||
|         Returns |         Returns | ||||||
|             The sent `custom.Message <telethon.tl.custom.message.Message>`. |             The sent `custom.Message <telethon.tl.custom.message.Message>`. | ||||||
| 
 | 
 | ||||||
|  | @ -663,6 +670,10 @@ class MessageMethods: | ||||||
|                 # Forcing replies or clearing buttons. |                 # Forcing replies or clearing buttons. | ||||||
|                 await client.send_message(chat, 'Reply to me', buttons=Button.force_reply()) |                 await client.send_message(chat, 'Reply to me', buttons=Button.force_reply()) | ||||||
|                 await client.send_message(chat, 'Bye Keyboard!', buttons=Button.clear()) |                 await client.send_message(chat, 'Bye Keyboard!', buttons=Button.clear()) | ||||||
|  | 
 | ||||||
|  |                 # Scheduling a message to be sent after 5 minutes | ||||||
|  |                 from datetime import timedelta | ||||||
|  |                 await client.send_message(chat, 'Hi, future!', schedule=timedelta(minutes=5)) | ||||||
|         """ |         """ | ||||||
|         if file is not None: |         if file is not None: | ||||||
|             return await self.send_file( |             return await self.send_file( | ||||||
|  | @ -690,7 +701,8 @@ class MessageMethods: | ||||||
|                     silent=silent, |                     silent=silent, | ||||||
|                     reply_to=reply_to, |                     reply_to=reply_to, | ||||||
|                     buttons=markup, |                     buttons=markup, | ||||||
|                     entities=message.entities |                     entities=message.entities, | ||||||
|  |                     schedule=schedule | ||||||
|                 ) |                 ) | ||||||
| 
 | 
 | ||||||
|             request = functions.messages.SendMessageRequest( |             request = functions.messages.SendMessageRequest( | ||||||
|  | @ -702,7 +714,8 @@ class MessageMethods: | ||||||
|                 entities=message.entities, |                 entities=message.entities, | ||||||
|                 clear_draft=clear_draft, |                 clear_draft=clear_draft, | ||||||
|                 no_webpage=not isinstance( |                 no_webpage=not isinstance( | ||||||
|                     message.media, types.MessageMediaWebPage) |                     message.media, types.MessageMediaWebPage), | ||||||
|  |                 schedule_date=schedule | ||||||
|             ) |             ) | ||||||
|             message = message.message |             message = message.message | ||||||
|         else: |         else: | ||||||
|  | @ -720,7 +733,8 @@ class MessageMethods: | ||||||
|                 reply_to_msg_id=utils.get_message_id(reply_to), |                 reply_to_msg_id=utils.get_message_id(reply_to), | ||||||
|                 clear_draft=clear_draft, |                 clear_draft=clear_draft, | ||||||
|                 silent=silent, |                 silent=silent, | ||||||
|                 reply_markup=self.build_reply_markup(buttons) |                 reply_markup=self.build_reply_markup(buttons), | ||||||
|  |                 schedule_date=schedule | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|         result = await self(request) |         result = await self(request) | ||||||
|  | @ -747,7 +761,9 @@ class MessageMethods: | ||||||
|             from_peer: 'hints.EntityLike' = None, |             from_peer: 'hints.EntityLike' = None, | ||||||
|             *, |             *, | ||||||
|             silent: bool = None, |             silent: bool = None, | ||||||
|             as_album: bool = None) -> 'typing.Sequence[types.Message]': |             as_album: bool = None, | ||||||
|  |             schedule: 'hints.DateLike' = None | ||||||
|  |     ) -> 'typing.Sequence[types.Message]': | ||||||
|         """ |         """ | ||||||
|         Forwards the given messages to the specified entity. |         Forwards the given messages to the specified entity. | ||||||
| 
 | 
 | ||||||
|  | @ -787,6 +803,11 @@ class MessageMethods: | ||||||
|                 `True` will group always (even converting separate |                 `True` will group always (even converting separate | ||||||
|                 images into albums), and `False` will never group. |                 images into albums), and `False` will never group. | ||||||
| 
 | 
 | ||||||
|  |             schedule (`hints.DateLike`, optional): | ||||||
|  |                 If set, the message(s) won't forward immediately, and | ||||||
|  |                 instead they will be scheduled to be automatically sent | ||||||
|  |                 at a later time. | ||||||
|  | 
 | ||||||
|         Returns |         Returns | ||||||
|             The list of forwarded `Message <telethon.tl.custom.message.Message>`, |             The list of forwarded `Message <telethon.tl.custom.message.Message>`, | ||||||
|             or a single one if a list wasn't provided as input. |             or a single one if a list wasn't provided as input. | ||||||
|  | @ -873,7 +894,8 @@ class MessageMethods: | ||||||
|                 # Trying to send a single message as grouped will cause |                 # Trying to send a single message as grouped will cause | ||||||
|                 # GROUPED_MEDIA_INVALID. If more than one message is forwarded |                 # GROUPED_MEDIA_INVALID. If more than one message is forwarded | ||||||
|                 # (even without media...), this error goes away. |                 # (even without media...), this error goes away. | ||||||
|                 grouped=len(chunk) > 1 and grouped |                 grouped=len(chunk) > 1 and grouped, | ||||||
|  |                 schedule_date=schedule | ||||||
|             ) |             ) | ||||||
|             result = await self(req) |             result = await self(req) | ||||||
|             sent.extend(self._get_response_message(req, result, entity)) |             sent.extend(self._get_response_message(req, result, entity)) | ||||||
|  | @ -889,7 +911,9 @@ class MessageMethods: | ||||||
|             parse_mode: str = (), |             parse_mode: str = (), | ||||||
|             link_preview: bool = True, |             link_preview: bool = True, | ||||||
|             file: 'hints.FileLike' = None, |             file: 'hints.FileLike' = None, | ||||||
|             buttons: 'hints.MarkupLike' = None) -> 'types.Message': |             buttons: 'hints.MarkupLike' = None, | ||||||
|  |             schedule: 'hints.DateLike' = None | ||||||
|  |     ) -> 'types.Message': | ||||||
|         """ |         """ | ||||||
|         Edits the given message to change its text or media. |         Edits the given message to change its text or media. | ||||||
| 
 | 
 | ||||||
|  | @ -936,6 +960,14 @@ class MessageMethods: | ||||||
|                 you have signed in as a bot. You can also pass your own |                 you have signed in as a bot. You can also pass your own | ||||||
|                 :tl:`ReplyMarkup` here. |                 :tl:`ReplyMarkup` here. | ||||||
| 
 | 
 | ||||||
|  |             schedule (`hints.DateLike`, optional): | ||||||
|  |                 If set, the message won't be edited immediately, and instead | ||||||
|  |                 it will be scheduled to be automatically edited at a later | ||||||
|  |                 time. | ||||||
|  | 
 | ||||||
|  |                 Note that this parameter will have no effect if you are | ||||||
|  |                 trying to edit a message that was sent via inline bots. | ||||||
|  | 
 | ||||||
|         Returns |         Returns | ||||||
|             The edited `Message <telethon.tl.custom.message.Message>`, |             The edited `Message <telethon.tl.custom.message.Message>`, | ||||||
|             unless `entity` was a :tl:`InputBotInlineMessageID` in which |             unless `entity` was a :tl:`InputBotInlineMessageID` in which | ||||||
|  | @ -993,7 +1025,8 @@ class MessageMethods: | ||||||
|             no_webpage=not link_preview, |             no_webpage=not link_preview, | ||||||
|             entities=msg_entities, |             entities=msg_entities, | ||||||
|             media=media, |             media=media, | ||||||
|             reply_markup=self.build_reply_markup(buttons) |             reply_markup=self.build_reply_markup(buttons), | ||||||
|  |             schedule_date=schedule | ||||||
|         ) |         ) | ||||||
|         msg = self._get_response_message(request, await self(request), entity) |         msg = self._get_response_message(request, await self(request), entity) | ||||||
|         await self._cache_media(msg, file, file_handle, image=image) |         await self._cache_media(msg, file, file_handle, image=image) | ||||||
|  |  | ||||||
|  | @ -104,6 +104,7 @@ class UploadMethods: | ||||||
|             buttons: 'hints.MarkupLike' = None, |             buttons: 'hints.MarkupLike' = None, | ||||||
|             silent: bool = None, |             silent: bool = None, | ||||||
|             supports_streaming: bool = False, |             supports_streaming: bool = False, | ||||||
|  |             schedule: 'hints.DateLike' = None, | ||||||
|             **kwargs) -> 'types.Message': |             **kwargs) -> 'types.Message': | ||||||
|         """ |         """ | ||||||
|         Sends message with the given file to the specified entity. |         Sends message with the given file to the specified entity. | ||||||
|  | @ -230,6 +231,11 @@ class UploadMethods: | ||||||
|                 these to MP4 before sending if you want them to be streamable. |                 these to MP4 before sending if you want them to be streamable. | ||||||
|                 Unsupported formats will result in ``VideoContentTypeError``. |                 Unsupported formats will result in ``VideoContentTypeError``. | ||||||
| 
 | 
 | ||||||
|  |             schedule (`hints.DateLike`, optional): | ||||||
|  |                 If set, the file won't send immediately, and instead | ||||||
|  |                 it will be scheduled to be automatically sent at a later | ||||||
|  |                 time. | ||||||
|  | 
 | ||||||
|         Returns |         Returns | ||||||
|             The `Message <telethon.tl.custom.message.Message>` (or messages) |             The `Message <telethon.tl.custom.message.Message>` (or messages) | ||||||
|             containing the sent file, or messages if a list of them was passed. |             containing the sent file, or messages if a list of them was passed. | ||||||
|  | @ -295,7 +301,7 @@ class UploadMethods: | ||||||
|                 result += await self._send_album( |                 result += await self._send_album( | ||||||
|                     entity, images[:10], caption=image_captions[:10], |                     entity, images[:10], caption=image_captions[:10], | ||||||
|                     progress_callback=progress_callback, reply_to=reply_to, |                     progress_callback=progress_callback, reply_to=reply_to, | ||||||
|                     parse_mode=parse_mode, silent=silent |                     parse_mode=parse_mode, silent=silent, schedule=schedule | ||||||
|                 ) |                 ) | ||||||
|                 images = images[10:] |                 images = images[10:] | ||||||
|                 image_captions = image_captions[10:] |                 image_captions = image_captions[10:] | ||||||
|  | @ -307,7 +313,7 @@ class UploadMethods: | ||||||
|                     progress_callback=progress_callback, reply_to=reply_to, |                     progress_callback=progress_callback, reply_to=reply_to, | ||||||
|                     attributes=attributes, thumb=thumb, voice_note=voice_note, |                     attributes=attributes, thumb=thumb, voice_note=voice_note, | ||||||
|                     video_note=video_note, buttons=buttons, silent=silent, |                     video_note=video_note, buttons=buttons, silent=silent, | ||||||
|                     supports_streaming=supports_streaming, |                     supports_streaming=supports_streaming, schedule=schedule, | ||||||
|                     **kwargs |                     **kwargs | ||||||
|                 )) |                 )) | ||||||
| 
 | 
 | ||||||
|  | @ -339,7 +345,8 @@ class UploadMethods: | ||||||
|         markup = self.build_reply_markup(buttons) |         markup = self.build_reply_markup(buttons) | ||||||
|         request = functions.messages.SendMediaRequest( |         request = functions.messages.SendMediaRequest( | ||||||
|             entity, media, reply_to_msg_id=reply_to, message=caption, |             entity, media, reply_to_msg_id=reply_to, message=caption, | ||||||
|             entities=msg_entities, reply_markup=markup, silent=silent |             entities=msg_entities, reply_markup=markup, silent=silent, | ||||||
|  |             schedule_date=schedule | ||||||
|         ) |         ) | ||||||
|         msg = self._get_response_message(request, await self(request), entity) |         msg = self._get_response_message(request, await self(request), entity) | ||||||
|         await self._cache_media(msg, file, file_handle, image=image) |         await self._cache_media(msg, file, file_handle, image=image) | ||||||
|  | @ -348,7 +355,7 @@ class UploadMethods: | ||||||
| 
 | 
 | ||||||
|     async def _send_album(self: 'TelegramClient', entity, files, caption='', |     async def _send_album(self: 'TelegramClient', entity, files, caption='', | ||||||
|                           progress_callback=None, reply_to=None, |                           progress_callback=None, reply_to=None, | ||||||
|                           parse_mode=(), silent=None): |                           parse_mode=(), silent=None, schedule=None): | ||||||
|         """Specialized version of .send_file for albums""" |         """Specialized version of .send_file for albums""" | ||||||
|         # We don't care if the user wants to avoid cache, we will use it |         # We don't care if the user wants to avoid cache, we will use it | ||||||
|         # anyway. Why? The cached version will be exactly the same thing |         # anyway. Why? The cached version will be exactly the same thing | ||||||
|  | @ -398,7 +405,8 @@ class UploadMethods: | ||||||
| 
 | 
 | ||||||
|         # Now we can construct the multi-media request |         # Now we can construct the multi-media request | ||||||
|         result = await self(functions.messages.SendMultiMediaRequest( |         result = await self(functions.messages.SendMultiMediaRequest( | ||||||
|             entity, reply_to_msg_id=reply_to, multi_media=media, silent=silent |             entity, reply_to_msg_id=reply_to, multi_media=media, | ||||||
|  |             silent=silent, schedule_date=schedule | ||||||
|         )) |         )) | ||||||
| 
 | 
 | ||||||
|         # We never sent a `random_id` for the messages that resulted from |         # We never sent a `random_id` for the messages that resulted from | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user