diff --git a/davtelepot/__init__.py b/davtelepot/__init__.py index 4ebc1b7..7e53f89 100644 --- a/davtelepot/__init__.py +++ b/davtelepot/__init__.py @@ -11,7 +11,7 @@ __author__ = "Davide Testa" __email__ = "davide@davte.it" __credits__ = ["Marco Origlia", "Nick Lee @Nickoala"] __license__ = "GNU General Public License v3.0" -__version__ = "2.9.12" +__version__ = "2.10.1" __maintainer__ = "Davide Testa" __contact__ = "t.me/davte" diff --git a/davtelepot/api.py b/davtelepot/api.py index b78dfdb..33c1fc5 100644 --- a/davtelepot/api.py +++ b/davtelepot/api.py @@ -7,6 +7,7 @@ A simple aiohttp asynchronous web client is used to make requests. # Standard library modules import asyncio import datetime +import inspect import io import json import logging @@ -349,6 +350,104 @@ class InlineQueryResultsButton(dict): return +class DictToDump(dict): + def dumps(self): + parameters = {key: value for key, value in self.items() if value} + return json.dumps(parameters, separators=(',', ':')) + + +class ReplyParameters(DictToDump): + def __init__(self, message_id: int, + chat_id: Union[int, str] = None, + allow_sending_without_reply: bool = None, + quote: str = None, + quote_parse_mode: str = None, + quote_entities: list = None, + quote_position: int = None): + super().__init__(self) + self['message_id'] = message_id + self['chat_id'] = chat_id + self['allow_sending_without_reply'] = allow_sending_without_reply + self['quote'] = quote + self['quote_parse_mode'] = quote_parse_mode + self['quote_entities'] = quote_entities + self['quote_position'] = quote_position + + +class LinkPreviewOptions(DictToDump): + def __init__(self, + is_disabled: bool = None, + url: str = None, + prefer_small_media: bool = None, + prefer_large_media: bool = None, + show_above_text: bool = None): + super().__init__(self) + self['is_disabled'] = is_disabled + self['url'] = url + self['prefer_small_media'] = prefer_small_media + self['prefer_large_media'] = prefer_large_media + self['show_above_text'] = show_above_text + + +class ReactionType(DictToDump): + def __init__(self, + type_: str, + emoji: str = None, + custom_emoji_id: str = None): + super().__init__(self) + if type_ not in ('emoji', 'custom_emoji'): + raise TypeError( + f"ReactionType must be `emoji` or `custom_emoji`.\n" + f"Unknown type {type_}" + ) + self['type'] = type_ + if emoji and custom_emoji_id: + raise TypeError( + "One and only one of the two fields `emoji` or `custom_emoji` " + "may be not None." + ) + elif emoji: + self['emoji'] = emoji + elif custom_emoji_id: + self['custom_emoji_id'] = custom_emoji_id + else: + raise TypeError( + "At least one of the two fields `emoji` or `custom_emoji` " + "must be provided and not None." + ) + + +def handle_deprecated_disable_web_page_preview(parameters: dict, + kwargs: dict): + if 'disable_web_page_preview' in kwargs: + if parameters['link_preview_options'] is None: + parameters['link_preview_options'] = LinkPreviewOptions() + parameters['link_preview_options']['is_disabled'] = True + logging.error("DEPRECATION WARNING: `disable_web_page_preview` " + f"parameter of function `{inspect.stack()[2][3]}` has been " + "deprecated since Bot API 7.0. " + "Use `link_preview_options` instead.") + return parameters + + +def handle_deprecated_reply_parameters(parameters: dict, + kwargs: dict): + if 'reply_to_message_id' in kwargs and kwargs['reply_to_message_id']: + if parameters['reply_parameters'] is None: + parameters['reply_parameters'] = ReplyParameters( + message_id=kwargs['reply_to_message_id'] + ) + parameters['reply_parameters']['message_id'] = kwargs['reply_to_message_id'] + if 'allow_sending_without_reply' in kwargs: + parameters['reply_parameters'][ + 'allow_sending_without_reply' + ] = kwargs['allow_sending_without_reply'] + logging.error(f"DEPRECATION WARNING: `reply_to_message_id` and " + f"`allow_sending_without_reply` parameters of function " + f"`{inspect.stack()[2][3]}` have been deprecated since " + f"Bot API 7.0. Use `reply_parameters` instead.") + return parameters + # This class needs to mirror Telegram API, so camelCase method are needed # noinspection PyPep8Naming @@ -491,6 +590,8 @@ class TelegramBot: if (type(value) in (int, list,) or (type(value) is dict and 'file' not in value)): value = json.dumps(value, separators=(',', ':')) + elif isinstance(value, DictToDump): + value = value.dumps() data.add_field(key, value) return data @@ -757,19 +858,27 @@ class TelegramBot: message_thread_id: int = None, parse_mode: str = None, entities: List[dict] = None, - disable_web_page_preview: bool = None, + link_preview_options: LinkPreviewOptions = None, disable_notification: bool = None, protect_content: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Send a text message. On success, return it. See https://core.telegram.org/bots/api#sendmessage for details. """ + parameters = handle_deprecated_disable_web_page_preview( + parameters=locals().copy(), + kwargs=kwargs + ) + parameters = handle_deprecated_reply_parameters( + parameters=parameters, + kwargs=kwargs + ) return await self.api_request( 'sendMessage', - parameters=locals() + parameters=parameters ) async def forwardMessage(self, chat_id: Union[int, str], @@ -794,17 +903,21 @@ class TelegramBot: message_thread_id: int = None, protect_content: bool = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, has_spoiler: bool = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Send a photo from file_id, HTTP url or file. See https://core.telegram.org/bots/api#sendphoto for details. """ + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendPhoto', - parameters=locals() + parameters=parameters ) async def sendAudio(self, chat_id: Union[int, str], audio, @@ -816,10 +929,9 @@ class TelegramBot: title: str = None, thumbnail=None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, + reply_parameters: ReplyParameters = None, reply_markup=None, **kwargs): """Send an audio file from file_id, HTTP url or file. @@ -831,9 +943,13 @@ class TelegramBot: logging.error("DEPRECATION WARNING: `thumb` parameter of function" "`sendAudio` has been deprecated since Bot API 6.6. " "Use `thumbnail` instead.") + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendAudio', - parameters=locals() + parameters=parameters ) async def sendDocument(self, chat_id: Union[int, str], document, @@ -843,10 +959,9 @@ class TelegramBot: caption_entities: List[dict] = None, disable_content_type_detection: bool = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, + reply_parameters: ReplyParameters = None, reply_markup=None, **kwargs): """Send a document from file_id, HTTP url or file. @@ -858,9 +973,13 @@ class TelegramBot: logging.error("DEPRECATION WARNING: `thumb` parameter of function" "`sendDocument` has been deprecated since Bot API 6.6. " "Use `thumbnail` instead.") + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendDocument', - parameters=locals() + parameters=parameters ) async def sendVideo(self, chat_id: Union[int, str], video, @@ -873,11 +992,10 @@ class TelegramBot: caption_entities: List[dict] = None, supports_streaming: bool = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, has_spoiler: bool = None, + reply_parameters: ReplyParameters = None, reply_markup=None, **kwargs): """Send a video from file_id, HTTP url or file. @@ -889,9 +1007,13 @@ class TelegramBot: logging.error("DEPRECATION WARNING: `thumb` parameter of function" "`sendVideo` has been deprecated since Bot API 6.6. " "Use `thumbnail` instead.") + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendVideo', - parameters=locals() + parameters=parameters ) async def sendAnimation(self, chat_id: Union[int, str], animation, @@ -903,11 +1025,10 @@ class TelegramBot: parse_mode: str = None, caption_entities: List[dict] = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, has_spoiler: bool = None, + reply_parameters: ReplyParameters = None, reply_markup=None, **kwargs): """Send animation files (GIF or H.264/MPEG-4 AVC video without sound). @@ -919,9 +1040,13 @@ class TelegramBot: logging.error("DEPRECATION WARNING: `thumb` parameter of function" "`sendAnimation` has been deprecated since Bot API 6.6. " "Use `thumbnail` instead.") + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendAnimation', - parameters=locals() + parameters=parameters ) async def sendVoice(self, chat_id: Union[int, str], voice, @@ -930,19 +1055,23 @@ class TelegramBot: caption_entities: List[dict] = None, duration: int = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Send an audio file to be displayed as playable voice message. `voice` must be in an .ogg file encoded with OPUS. See https://core.telegram.org/bots/api#sendvoice for details. """ + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendVoice', - parameters=locals() + parameters=parameters ) async def sendVideoNote(self, chat_id: Union[int, str], video_note, @@ -950,10 +1079,9 @@ class TelegramBot: length: int = None, thumbnail=None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, + reply_parameters: ReplyParameters = None, reply_markup=None, **kwargs): """Send a rounded square mp4 video message of up to 1 minute long. @@ -965,26 +1093,34 @@ class TelegramBot: logging.error("DEPRECATION WARNING: `thumb` parameter of function" "`sendVideoNote` has been deprecated since Bot API 6.6. " "Use `thumbnail` instead.") + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendVideoNote', - parameters=locals() + parameters=parameters ) async def sendMediaGroup(self, chat_id: Union[int, str], media: list, disable_notification: bool = None, - reply_to_message_id: int = None, message_thread_id: int = None, protect_content: bool = None, - allow_sending_without_reply: bool = None): + reply_parameters: ReplyParameters = None, + **kwargs): """Send a group of photos or videos as an album. `media` must be a list of `InputMediaPhoto` and/or `InputMediaVideo` objects. See https://core.telegram.org/bots/api#sendmediagroup for details. """ + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendMediaGroup', - parameters=locals() + parameters=parameters ) async def sendLocation(self, chat_id: Union[int, str], @@ -994,11 +1130,11 @@ class TelegramBot: heading: int = None, proximity_alert_radius: int = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Send a point on the map. May be kept updated for a `live_period`. See https://core.telegram.org/bots/api#sendlocation for details. @@ -1011,9 +1147,13 @@ class TelegramBot: heading = max(1, min(heading, 360)) if proximity_alert_radius: # Distance 1-100000 m proximity_alert_radius = max(1, min(proximity_alert_radius, 100000)) + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendLocation', - parameters=locals() + parameters=parameters ) async def editMessageLiveLocation(self, latitude: float, longitude: float, @@ -1072,19 +1212,23 @@ class TelegramBot: google_place_id: str = None, google_place_type: str = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Send information about a venue. Integrated with FourSquare. See https://core.telegram.org/bots/api#sendvenue for details. """ + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendVenue', - parameters=locals() + parameters=parameters ) async def sendContact(self, chat_id: Union[int, str], @@ -1093,18 +1237,22 @@ class TelegramBot: last_name: str = None, vcard: str = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Send a phone contact. See https://core.telegram.org/bots/api#sendcontact for details. """ + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendContact', - parameters=locals() + parameters=parameters ) async def sendPoll(self, @@ -1122,11 +1270,11 @@ class TelegramBot: close_date: Union[int, datetime.datetime] = None, is_closed: bool = None, disable_notification: bool = None, - allow_sending_without_reply: bool = None, - reply_to_message_id: int = None, message_thread_id: int = None, protect_content: bool = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Send a native poll in a group, a supergroup or channel. See https://core.telegram.org/bots/api#sendpoll for details. @@ -1150,6 +1298,10 @@ class TelegramBot: parameters = locals().copy() parameters['type'] = parameters['type_'] del parameters['type_'] + parameters = handle_deprecated_reply_parameters( + parameters=parameters, + kwargs=kwargs + ) return await self.api_request( 'sendPoll', parameters=parameters @@ -1497,17 +1649,22 @@ class TelegramBot: inline_message_id: str = None, parse_mode: str = None, entities: List[dict] = None, - disable_web_page_preview: bool = None, - reply_markup=None): + link_preview_options: LinkPreviewOptions = None, + reply_markup=None, + **kwargs): """Edit text and game messages. On success, if edited message is sent by the bot, the edited Message is returned, otherwise True is returned. See https://core.telegram.org/bots/api#editmessagetext for details. """ + parameters = handle_deprecated_disable_web_page_preview( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'editMessageText', - parameters=locals() + parameters=parameters ) async def editMessageCaption(self, @@ -1604,15 +1761,28 @@ class TelegramBot: parameters=locals() ) + async def deleteMessages(self, chat_id: Union[int, str], + message_ids: List[int]): + """Delete multiple messages simultaneously. + + If some of the specified messages can't be found, they are skipped. + Returns True on success. + See https://core.telegram.org/bots/api#deletemessages for details. + """ + return await self.api_request( + 'deleteMessages', + parameters=locals() + ) + async def sendSticker(self, chat_id: Union[int, str], sticker: Union[str, dict, IO], disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, emoji: str = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Send `.webp` stickers. `sticker` must be a file path, a URL, a file handle or a dict @@ -1624,9 +1794,13 @@ class TelegramBot: if sticker is None: logging.error("Invalid sticker provided!") return + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) result = await self.api_request( 'sendSticker', - parameters=locals() + parameters=parameters ) if type(sticker) is dict: # Close sticker file, if it was open sticker['file'].close() @@ -1715,7 +1889,7 @@ class TelegramBot: raise TypeError(f"Unknown sticker type `{sticker_type}`.") result = await self.api_request( 'createNewStickerSet', - parameters=locals(), + parameters=locals().copy(), exclude=['old_sticker_format'] ) return result @@ -1749,7 +1923,7 @@ class TelegramBot: return result = await self.api_request( 'addStickerToSet', - parameters=locals(), + parameters=locals().copy(), exclude=['old_sticker_format'] ) return result @@ -1821,17 +1995,21 @@ class TelegramBot: send_email_to_provider: bool = None, is_flexible: bool = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Send an invoice. On success, the sent Message is returned. See https://core.telegram.org/bots/api#sendinvoice for details. """ + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendInvoice', - parameters=locals() + parameters=parameters ) async def answerShippingQuery(self, shipping_query_id, ok, @@ -1895,18 +2073,22 @@ class TelegramBot: message_thread_id: int = None, protect_content: bool = None, disable_notification: bool = None, - reply_to_message_id: int = None, + reply_parameters: ReplyParameters = None, reply_markup=None, - allow_sending_without_reply: bool = None): + **kwargs): """Send a game. On success, the sent Message is returned. See https://core.telegram.org/bots/api#sendgame for details. """ + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendGame', - parameters=locals() + parameters=parameters ) async def setGameScore(self, user_id: int, score: int, @@ -1954,11 +2136,11 @@ class TelegramBot: chat_id: Union[int, str] = None, emoji: str = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, message_thread_id: int = None, protect_content: bool = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Send a dice. Use this method to send a dice, which will have a random value from 1 @@ -1969,9 +2151,13 @@ class TelegramBot: See https://core.telegram.org/bots/api#senddice for details. """ + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'sendDice', - parameters=locals() + parameters=parameters ) async def setChatAdministratorCustomTitle(self, @@ -2100,9 +2286,9 @@ class TelegramBot: parse_mode: str = None, caption_entities: list = None, disable_notification: bool = None, - reply_to_message_id: int = None, - allow_sending_without_reply: bool = None, - reply_markup=None): + reply_parameters: ReplyParameters = None, + reply_markup=None, + **kwargs): """Use this method to copy messages of any kind. The method is analogous to the method forwardMessages, but the copied @@ -2110,9 +2296,13 @@ class TelegramBot: Returns the MessageId of the sent message on success. See https://core.telegram.org/bots/api#copymessage for details. """ + parameters = handle_deprecated_reply_parameters( + parameters=locals().copy(), + kwargs=kwargs + ) return await self.api_request( 'copyMessage', - parameters=locals() + parameters=parameters ) async def unpinAllChatMessages(self, chat_id: Union[int, str]): @@ -2700,3 +2890,79 @@ class TelegramBot: 'unpinAllGeneralForumTopicMessages', parameters=locals() ) + + async def getUserChatBoosts(self, chat_id: Union[int, str], user_id: int): + """Get the list of boosts added to a chat by a user. + + Requires administrator rights in the chat. + Returns a UserChatBoosts object. + See https://core.telegram.org/bots/api#getuserchatboosts for details. + """ + return await self.api_request( + 'getUserChatBoosts', + parameters=locals() + ) + + async def forwardMessages(self, chat_id: Union[int, str], + from_chat_id: Union[int, str], + message_ids: List[int], + message_thread_id: int = None, + disable_notification: bool = None, + protect_content: bool = None): + """Forward multiple messages of any kind. + + If some of the specified messages can't be found or forwarded, they are + skipped. + Service messages and messages with protected content can't be + forwarded. + Album grouping is kept for forwarded messages. + On success, an array of MessageId of the sent messages is returned. + See https://core.telegram.org/bots/api#forwardmessages for details. + """ + return await self.api_request( + 'forwardMessages', + parameters=locals() + ) + + async def copyMessages(self, chat_id: Union[int, str], + from_chat_id: Union[int, str], + message_ids: List[int], + message_thread_id: int = None, + disable_notification: bool = None, + protect_content: bool = None, + remove_caption: bool = None): + """Copy messages of any kind. + + If some of the specified messages can't be found or copied, they are + skipped. + Service messages, giveaway messages, giveaway winners messages, and + invoice messages can't be copied. + A quiz poll can be copied only if the value of the field + correct_option_id is known to the bot. + The method is analogous to the method forwardMessages, but the copied + messages don't have a link to the original message. + Album grouping is kept for copied messages. + On success, an array of MessageId of the sent messages is returned. + See https://core.telegram.org/bots/api#copymessages for details. + """ + return await self.api_request( + 'copyMessages', + parameters=locals() + ) + + async def setMessageReaction(self, chat_id: Union[int, str], + message_id: int, + reaction: List[ReactionType] = None, + is_big: bool = None): + """Change the chosen reactions on a message. + + Service messages can't be reacted to. + Automatically forwarded messages from a channel to its discussion group + have the same available reactions as messages in the channel. + Returns True on success. + See https://core.telegram.org/bots/api#setmessagereaction for details. + """ + return await self.api_request( + 'setMessageReaction', + parameters=locals() + ) diff --git a/davtelepot/bot.py b/davtelepot/bot.py index ed2cfc7..d10180f 100644 --- a/davtelepot/bot.py +++ b/davtelepot/bot.py @@ -2,34 +2,6 @@ camelCase methods mirror API directly, while snake_case ones act as middleware someway. - -Usage - ``` - import sys - - from davtelepot.bot import Bot - - from data.passwords import my_token, my_other_token - - long_polling_bot = Bot(token=my_token, database_url='my_db') - webhook_bot = Bot(token=my_other_token, hostname='example.com', - certificate='path/to/certificate.pem', - database_url='my_other_db') - - @long_polling_bot.command('/foo') - async def foo_command(bot, update, user_record, language): - return "Bar!" - - @webhook_bot.command('/bar') - async def bar_command(bot, update, user_record, language): - return "Foo!" - - exit_state = Bot.run( - local_host='127.0.0.5', - port=8552 - ) - sys.exit(exit_state) - ``` """ # Standard library modules @@ -49,7 +21,9 @@ from typing import Callable, List, Union, Dict import aiohttp.web # Project modules -from davtelepot.api import TelegramBot, TelegramError +from davtelepot.api import ( + LinkPreviewOptions, ReplyParameters, TelegramBot, TelegramError +) from davtelepot.database import ObjectWithDatabase from davtelepot.languages import MultiLanguageObject from davtelepot.messages import davtelepot_messages @@ -1319,19 +1293,21 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): async def send_message(self, chat_id: Union[int, str] = None, text: str = None, + message_thread_id: int = None, entities: List[dict] = None, parse_mode: str = 'HTML', - message_thread_id: int = None, + link_preview_options: LinkPreviewOptions = None, + disable_notification: bool = None, protect_content: bool = None, disable_web_page_preview: bool = None, - disable_notification: bool = None, reply_to_message_id: int = None, allow_sending_without_reply: bool = None, - reply_markup=None, update: dict = None, reply_to_update: bool = False, send_default_keyboard: bool = True, - user_record: OrderedDict = None): + user_record: OrderedDict = None, + reply_parameters: ReplyParameters = None, + reply_markup=None): """Send text via message(s). This method wraps lower-level `TelegramBot.sendMessage` method. @@ -1352,6 +1328,10 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): user_record = self.db['users'].find_one(telegram_id=chat_id) if reply_to_update and 'message_id' in update: reply_to_message_id = update['message_id'] + if disable_web_page_preview: + if link_preview_options is None: + link_preview_options = LinkPreviewOptions() + link_preview_options['is_disabled'] = True if ( send_default_keyboard and reply_markup is None @@ -1395,19 +1375,27 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): limit=self.__class__.TELEGRAM_MESSAGES_MAX_LEN - 100, parse_mode=parse_mode ) + if reply_to_message_id: + if reply_parameters is None: + reply_parameters = ReplyParameters(message_id=reply_to_message_id) + reply_parameters['message_id'] = reply_to_message_id + if allow_sending_without_reply: + reply_parameters['allow_sending_without_reply'] = allow_sending_without_reply + if reply_to_update and 'chat' in update and 'id' in update['chat']: + if update['chat']['id'] != chat_id: + reply_parameters['chat_id'] = update['chat']['id'] for text_chunk, is_last in text_chunks: _reply_markup = (reply_markup if is_last else None) sent_message_update = await self.sendMessage( chat_id=chat_id, text=text_chunk, + message_thread_id=message_thread_id, parse_mode=parse_mode, entities=entities, - message_thread_id=message_thread_id, - protect_content=protect_content, - disable_web_page_preview=disable_web_page_preview, + link_preview_options=link_preview_options, disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, + reply_parameters=reply_parameters, reply_markup=_reply_markup ) return sent_message_update @@ -1431,6 +1419,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): parse_mode: str = 'HTML', entities: List[dict] = None, disable_web_page_preview: bool = None, + link_preview_options: LinkPreviewOptions = None, allow_sending_without_reply: bool = None, reply_markup=None, update: dict = None): @@ -1463,6 +1452,10 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) ): if i == 0: + if disable_web_page_preview: + if link_preview_options is None: + link_preview_options = LinkPreviewOptions() + link_preview_options['is_disabled'] = True edited_message = await self.editMessageText( text=text_chunk, chat_id=chat_id, @@ -1470,7 +1463,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): inline_message_id=inline_message_id, parse_mode=parse_mode, entities=entities, - disable_web_page_preview=disable_web_page_preview, + link_preview_options=link_preview_options, reply_markup=(reply_markup if is_last else None) ) if chat_id is None: @@ -1576,6 +1569,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): reply_markup=None, update: dict = None, reply_to_update: bool = False, + reply_parameters: ReplyParameters = None, send_default_keyboard: bool = True, use_stored_file_id: bool = True): """Send photos. @@ -1599,6 +1593,15 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): chat_id = self.get_chat_id(update) if reply_to_update and 'message_id' in update: reply_to_message_id = update['message_id'] + if reply_to_message_id: + if reply_parameters is None: + reply_parameters = ReplyParameters(message_id=reply_to_message_id) + reply_parameters['message_id'] = reply_to_message_id + if allow_sending_without_reply: + reply_parameters['allow_sending_without_reply'] = allow_sending_without_reply + if reply_to_update and 'chat' in update and 'id' in update['chat']: + if update['chat']['id'] != chat_id: + reply_parameters['chat_id'] = update['chat']['id'] if ( send_default_keyboard and reply_markup is None @@ -1652,8 +1655,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): parse_mode=parse_mode, caption_entities=caption_entities, disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, + reply_parameters=reply_parameters, reply_markup=reply_markup ) if isinstance(sent_update, Exception): @@ -1701,6 +1703,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): reply_markup=None, update: dict = None, reply_to_update: bool = False, + reply_parameters: ReplyParameters = None, send_default_keyboard: bool = True, use_stored_file_id: bool = True): """Send audio files. @@ -1724,6 +1727,15 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): chat_id = self.get_chat_id(update) if reply_to_update and 'message_id' in update: reply_to_message_id = update['message_id'] + if reply_to_message_id: + if reply_parameters is None: + reply_parameters = ReplyParameters(message_id=reply_to_message_id) + reply_parameters['message_id'] = reply_to_message_id + if allow_sending_without_reply: + reply_parameters['allow_sending_without_reply'] = allow_sending_without_reply + if reply_to_update and 'chat' in update and 'id' in update['chat']: + if update['chat']['id'] != chat_id: + reply_parameters['chat_id'] = update['chat']['id'] if ( send_default_keyboard and reply_markup is None @@ -1781,8 +1793,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): title=title, thumbnail=thumbnail, disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, + reply_parameters=reply_parameters, reply_markup=reply_markup ) if isinstance(sent_update, Exception): @@ -1826,6 +1837,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): reply_markup=None, update: dict = None, reply_to_update: bool = False, + reply_parameters: ReplyParameters = None, send_default_keyboard: bool = True, use_stored_file_id: bool = True): """Send voice messages. @@ -1849,6 +1861,15 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): chat_id = self.get_chat_id(update) if reply_to_update and 'message_id' in update: reply_to_message_id = update['message_id'] + if reply_to_message_id: + if reply_parameters is None: + reply_parameters = ReplyParameters(message_id=reply_to_message_id) + reply_parameters['message_id'] = reply_to_message_id + if allow_sending_without_reply: + reply_parameters['allow_sending_without_reply'] = allow_sending_without_reply + if reply_to_update and 'chat' in update and 'id' in update['chat']: + if update['chat']['id'] != chat_id: + reply_parameters['chat_id'] = update['chat']['id'] if ( send_default_keyboard and reply_markup is None @@ -1903,8 +1924,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): caption_entities=caption_entities, duration=duration, disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, + reply_parameters=reply_parameters, reply_markup=reply_markup ) if isinstance(sent_update, Exception): @@ -1951,6 +1971,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): document_name: str = None, update: dict = None, reply_to_update: bool = False, + reply_parameters: ReplyParameters = None, send_default_keyboard: bool = True, use_stored_file_id: bool = False): """Send a document. @@ -1983,6 +2004,15 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): return if reply_to_update and 'message_id' in update: reply_to_message_id = update['message_id'] + if reply_to_message_id: + if reply_parameters is None: + reply_parameters = ReplyParameters(message_id=reply_to_message_id) + reply_parameters['message_id'] = reply_to_message_id + if allow_sending_without_reply: + reply_parameters['allow_sending_without_reply'] = allow_sending_without_reply + if reply_to_update and 'chat' in update and 'id' in update['chat']: + if update['chat']['id'] != chat_id: + reply_parameters['chat_id'] = update['chat']['id'] if chat_id > 0: user_record = self.db['users'].find_one(telegram_id=chat_id) language = self.get_language(update=update, user_record=user_record) @@ -2061,7 +2091,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): caption=caption, parse_mode=parse_mode, disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, + reply_parameters=reply_parameters, reply_markup=reply_markup, update=update, reply_to_update=reply_to_update, @@ -2092,8 +2122,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): caption_entities=caption_entities, disable_content_type_detection=disable_content_type_detection, disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, + reply_parameters=reply_parameters, reply_markup=reply_markup ) if isinstance(sent_update, Exception): @@ -3505,7 +3534,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): Each bot will receive updates via long polling or webhook according to its initialization parameters. A single aiohttp.web.Application instance will be run (cls.app) on - local_host:port and it may serve custom-defined routes as well. + local_host:port, and it may serve custom-defined routes as well. """ if local_host is not None: cls.local_host = local_host