diff --git a/README.md b/README.md index 19343ee50..24a6e7845 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ ## Getting started -This API is tested with Python 3.9-3.13 and Pypy 3. +This API is tested with Python 3.10-3.14 and PyPy 3. There are two ways to install the library: * Installation using pip (a Python package manager): diff --git a/docs/source/conf.py b/docs/source/conf.py index a1afa9b6c..ab5b4fb44 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,7 +22,7 @@ copyright = f'2022-{datetime.now().year}, {author}' # The full version, including alpha/beta/rc tags -release = '4.32.0' +release = '4.33.0' # -- General configuration --------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 94b615dde..eb5074691 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,12 +4,12 @@ build-backend = "hatchling.build" [project] name = "pyTelegramBotAPI" -version = "4.32.0" +version = "4.33.0" description = "Python Telegram bot API." authors = [{name = "eternnoir", email = "eternnoir@gmail.com"}] license = {text = "GPL2"} readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" keywords = ["telegram", "bot", "api", "tools"] classifiers = [ "Development Status :: 5 - Production/Stable", diff --git a/telebot/apihelper.py b/telebot/apihelper.py index fa04fc9b7..849537ad1 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1903,7 +1903,9 @@ def send_invoice( :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for forum supergroups only :param reply_parameters: A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button. :param message_effect_id: Unique identifier of the message effect to be added to the message; for private chats only - :param allow_paid_broadcast: + :param allow_paid_broadcast: Pass True to allow up to 1000 messages per second, ignoring broadcasting limits for a fee of 0.1 Telegram Stars per message. The relevant Stars will be withdrawn from the bot's balance + :param direct_messages_topic_id: Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat + :param suggested_post_parameters: A JSON-serialized object containing the parameters of the suggested post to send; for direct messages chats only. If the message is sent as a reply to another suggested post, then that suggested post is automatically declined. :return: """ method_url = r'sendInvoice' diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index 54957cf6b..418cd39ee 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -192,6 +192,8 @@ def __init__(self, token: str, parse_mode: Optional[str]=None, offset: Optional[ self.middlewares = [] self._user = None # set during polling + self._polling = None + self.webhook_listener = None if validate_token: util.validate_token(self.token) @@ -203,7 +205,8 @@ def __init__(self, token: str, parse_mode: Optional[str]=None, offset: Optional[ def user(self): return self._user - async def close_session(self): + @staticmethod + async def close_session(): """ Closes existing session of aiohttp. Use this function if you stop polling/webhooks. @@ -554,7 +557,7 @@ async def _run_middlewares_and_handlers(self, message, handlers, middlewares, up elif isinstance(middleware_result, SkipHandler): skip_handlers = True - if handlers and not(skip_handlers): + if handlers and (not skip_handlers): try: for handler in handlers: params = [] @@ -4531,7 +4534,7 @@ async def send_document( protect_content = self.protect_content if (protect_content is None) else protect_content - if data and not(document): + if data and (not document): # function typo miss compatibility logger.warning('The parameter "data" is deprecated. Use "document" instead.') document = data @@ -4664,7 +4667,7 @@ async def send_sticker( protect_content = self.protect_content if (protect_content is None) else protect_content - if data and not(sticker): + if data and (not sticker): # function typo miss compatibility logger.warning('The parameter "data" is deprecated. Use "sticker" instead.') sticker = data @@ -4856,12 +4859,12 @@ async def send_video( if reply_parameters and (reply_parameters.allow_sending_without_reply is None): reply_parameters.allow_sending_without_reply = self.allow_sending_without_reply - if data and not(video): + if data and (not video): # function typo miss compatibility logger.warning('The parameter "data" is deprecated. Use "video" instead.') video = data - if thumb and not(thumbnail): + if thumb and (not thumbnail): logger.warning('The parameter "thumb" is deprecated. Use "thumbnail" instead.') thumbnail = thumb @@ -5824,7 +5827,9 @@ async def send_contact( await asyncio_helper.send_contact( self.token, chat_id, phone_number, first_name, last_name, vcard, disable_notification, reply_markup, timeout, - protect_content, message_thread_id, reply_parameters, business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast)) + protect_content, message_thread_id, reply_parameters, business_connection_id, + message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters)) async def send_message_draft( self, chat_id: int, @@ -8214,6 +8219,9 @@ async def set_sticker_set_thumbnail(self, name: str, user_id: int, thumbnail: Un HTTP URL. If omitted, then the thumbnail is dropped and the first sticker is used as the thumbnail. :type thumbnail: :obj:`filelike object` + :param format: Format of the thumbnail, must be one of “static” for a .WEBP or .PNG image, “animated” for a .TGS animation, or “video” for a WEBM video + :type format: :obj:`str` + :return: On success, True is returned. :rtype: :obj:`bool` """ diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 0ba07277c..7f00b319c 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -111,6 +111,7 @@ async def _process_request(token, url, method='get', params=None, files=None, ** logger.error(f'Unknown error: {e.__class__.__name__}') if not got_result: raise RequestTimeout("Request timeout. Request: method={0} url={1} params={2} files={3} request_timeout={4}".format(method, url, params, files, request_timeout, current_try)) + return None def _prepare_file(obj): """ @@ -119,6 +120,7 @@ def _prepare_file(obj): name = getattr(obj, 'name', None) if name and isinstance(name, str) and name[0] != '<' and name[-1] != '>': return os.path.basename(name) + return None def _prepare_data(params=None, files=None): """ @@ -1251,6 +1253,7 @@ async def get_method_by_type(data_type): return r'sendDocument' if data_type == 'sticker': return r'sendSticker' + return None async def ban_chat_member(token, chat_id, user_id, until_date=None, revoke_messages=None): @@ -1914,7 +1917,9 @@ async def send_invoice( :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for forum supergroups only :param reply_parameters: A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button. :param message_effect_id: Unique identifier of the message effect to be added to the message; for private chats only - :param allow_paid_broadcast: + :param allow_paid_broadcast: Pass True to allow up to 1000 messages per second, ignoring broadcasting limits for a fee of 0.1 Telegram Stars per message. The relevant Stars will be withdrawn from the bot's balance + :param direct_messages_topic_id: Identifier of the direct messages topic to which the message will be sent; required if the message is sent to a direct messages chat + :param suggested_post_parameters: A JSON-serialized object containing the parameters of the suggested post to send; for direct messages chats only. If the message is sent as a reply to another suggested post, then that suggested post is automatically declined. :return: """ method_url = r'sendInvoice' @@ -2740,6 +2745,7 @@ async def convert_input_media(media): async def convert_input_media_array(array): media = [] files = {} + key = "" for input_media in array: if isinstance(input_media, types.InputMedia) or isinstance(input_media, types.InputPaidMedia): media_dict = input_media.to_dict() @@ -2831,5 +2837,3 @@ class RequestTimeout(Exception): This class represents a request timeout. """ pass - - diff --git a/telebot/handler_backends.py b/telebot/handler_backends.py index 12f8c89bf..a15d20765 100644 --- a/telebot/handler_backends.py +++ b/telebot/handler_backends.py @@ -103,7 +103,7 @@ def dump_handlers(handlers, filename, file_mode="wb"): os.makedirs(dirs, exist_ok=True) with open(filename + ".tmp", file_mode) as file: - if (apihelper.CUSTOM_SERIALIZER is None): + if apihelper.CUSTOM_SERIALIZER is None: pickle.dump(handlers, file) else: apihelper.CUSTOM_SERIALIZER.dump(handlers, file) @@ -117,7 +117,7 @@ def dump_handlers(handlers, filename, file_mode="wb"): def return_load_handlers(filename, del_file_after_loading=True): if os.path.isfile(filename) and os.path.getsize(filename) > 0: with open(filename, "rb") as file: - if (apihelper.CUSTOM_SERIALIZER is None): + if apihelper.CUSTOM_SERIALIZER is None: handlers = pickle.load(file) else: handlers = apihelper.CUSTOM_SERIALIZER.load(file) @@ -126,6 +126,7 @@ def return_load_handlers(filename, del_file_after_loading=True): os.remove(filename) return handlers + return None class RedisHandlerBackend(HandlerBackend): @@ -255,4 +256,4 @@ def start2(message): """ def __init__(self) -> None: - pass \ No newline at end of file + pass diff --git a/telebot/types.py b/telebot/types.py index 9a2ca255f..84f8eef8b 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -5796,7 +5796,6 @@ def __init__(self): self.input_message_content: Optional[InputMessageContent] = None self.parse_mode: Optional[str] = None self.caption_entities: Optional[List[MessageEntity]] = None - # noinspection PyTypeChecker self.payload_dic: Dict[str] = {} self.show_caption_above_media: Optional[bool] = None @@ -7653,17 +7652,16 @@ def correct_option_id(self) -> Optional[int]: return self.correct_option_ids[0] return None - def add(self, option): + def add(self, option, persistent_id = None): """ - Add an option to the poll. - - :param option: Option to add - :type option: :class:`telebot.types.PollOption` or :obj:`str` + Deprecated """ + logger.warning("Poll.add is deprecated and will be removed in future versions.") + if type(option) is PollOption: self.options.append(option) else: - self.options.append(PollOption(option)) + self.options.append(PollOption(option, persistent_id)) class PollAnswer(JsonSerializable, JsonDeserializable, Dictionaryable): @@ -10177,8 +10175,7 @@ class BusinessConnection(JsonDeserializable): :param date: Date the connection was established in Unix time :type date: :obj:`int` - :param can_reply: Deprecated, use :attr:`rights` instead. True, if the bot can reply to messages from the business account - :type can_reply: :obj:`bool` + :param can_reply: Deprecated, use :attr:`rights` instead. :param rights: Optional. Rights of the business bot :type rights: :class:`BusinessBotRights` @@ -10198,7 +10195,7 @@ def de_json(cls, json_string): obj['rights'] = BusinessBotRights.de_json(obj.get('rights')) return cls(**obj) - def __init__(self, id, user, user_chat_id, date, can_reply, is_enabled, + def __init__(self, id, user, user_chat_id, date, is_enabled, rights=None, **kwargs): self.id: str = id self.user: User = user @@ -13219,6 +13216,7 @@ def to_dict(self): data['others_can_add_tasks'] = self.others_can_add_tasks if self.others_can_mark_tasks_as_done is not None: data['others_can_mark_tasks_as_done'] = self.others_can_mark_tasks_as_done + return data class ChecklistTasksDone(JsonDeserializable): @@ -13905,8 +13903,9 @@ def de_json(cls, json_string): obj['bot'] = User.de_json(obj['bot']) return cls(**obj) - + +# noinspection PyShadowingBuiltins class PreparedKeyboardButton(JsonDeserializable): """ Describes a keyboard button to be used by a user of a Mini App. @@ -14017,4 +14016,3 @@ def de_json(cls, json_string): if 'option_text_entities' in obj: obj['option_text_entities'] = [MessageEntity.de_json(entity) for entity in obj['option_text_entities']] return cls(**obj) - diff --git a/telebot/version.py b/telebot/version.py index d8353cdd7..00526689f 100644 --- a/telebot/version.py +++ b/telebot/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '4.32.0' +__version__ = '4.33.0'