diff --git a/telebot/__init__.py b/telebot/__init__.py index ca93487..5fed5c1 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -252,7 +252,7 @@ class TeleBot: """ self.current_states = StateFile(filename=filename) - self.current_states._create_dir() + self.current_states.create_dir() def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"): """ @@ -1221,11 +1221,13 @@ class TeleBot: reply_to_message_id = reply_to_message_id, reply_markup = reply_markup, parse_mode = parse_mode, disable_notification = disable_notification, timeout = timeout, caption = caption, thumb = thumb, caption_entities = caption_entities, allow_sending_without_reply = allow_sending_without_reply, - disable_content_type_detection = disable_content_type_detection, visible_file_name = visible_file_name, protect_content = protect_content)) + disable_content_type_detection = disable_content_type_detection, visible_file_name = visible_file_name, + protect_content = protect_content)) # TODO: Rewrite this method like in API. def send_sticker( - self, chat_id: Union[int, str], sticker: Union[Any, str], + self, chat_id: Union[int, str], + sticker: Union[Any, str], reply_to_message_id: Optional[int]=None, reply_markup: Optional[REPLY_MARKUP_TYPES]=None, disable_notification: Optional[bool]=None, @@ -1236,6 +1238,7 @@ class TeleBot: """ Use this method to send .webp stickers. :param chat_id: + :param sticker: :param data: :param reply_to_message_id: :param reply_markup: @@ -1254,7 +1257,8 @@ class TeleBot: self.token, chat_id, sticker, 'sticker', reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, disable_notification=disable_notification, timeout=timeout, - allow_sending_without_reply=allow_sending_without_reply, protect_content=protect_content)) + allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content)) def send_video( self, chat_id: Union[int, str], video: Union[Any, str], @@ -1330,6 +1334,7 @@ class TeleBot: :param thumb: InputFile or String : Thumbnail of the file sent :param caption: String : Animation caption (may also be used when resending animation by file_id). :param parse_mode: + :param protect_content: :param reply_to_message_id: :param reply_markup: :param disable_notification: @@ -1924,7 +1929,7 @@ class TeleBot: return apihelper.set_my_commands(self.token, commands, scope, language_code) def delete_my_commands(self, scope: Optional[types.BotCommandScope]=None, - language_code: Optional[int]=None) -> bool: + language_code: Optional[str]=None) -> bool: """ Use this method to delete the list of the bot's commands for the given scope and user language. After deletion, higher level commands will be shown to affected users. @@ -2559,7 +2564,7 @@ class TeleBot: :param chat_id: """ for key, value in kwargs.items(): - self.current_states._add_data(chat_id, key, value) + self.current_states.add_data(chat_id, key, value) def register_next_step_handler_by_chat_id( self, chat_id: Union[int, str], callback: Callable, *args, **kwargs) -> None: diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 3f9562d..870c609 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -144,6 +144,7 @@ def _make_request(token, method_name, method='get', params=None, files=None): method, request_url, params=params, files=files, timeout=(connect_timeout, read_timeout), proxies=proxy) elif CUSTOM_REQUEST_SENDER: + # noinspection PyCallingNonCallable result = CUSTOM_REQUEST_SENDER( method, request_url, params=params, files=files, timeout=(connect_timeout, read_timeout), proxies=proxy) @@ -246,6 +247,7 @@ def send_message( :param timeout: :param entities: :param allow_sending_without_reply: + :param protect_content: :return: """ method_url = r'sendMessage' @@ -861,7 +863,8 @@ def send_audio(token, chat_id, audio, caption=None, duration=None, performer=Non def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, caption=None, thumb=None, caption_entities=None, - allow_sending_without_reply=None, disable_content_type_detection=None, visible_file_name=None): + allow_sending_without_reply=None, disable_content_type_detection=None, visible_file_name=None, + protect_content = None): method_url = get_method_by_type(data_type) payload = {'chat_id': chat_id} files = None @@ -896,6 +899,8 @@ def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_m payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities)) if allow_sending_without_reply is not None: payload['allow_sending_without_reply'] = allow_sending_without_reply + if protect_content is not None: + payload['protect_content'] = protect_content if method_url == 'sendDocument' and disable_content_type_detection is not None: payload['disable_content_type_detection'] = disable_content_type_detection return _make_request(token, method_url, params=payload, files=files, method='post') @@ -1382,6 +1387,7 @@ def send_invoice( :param max_tip_amount: The maximum accepted amount for tips in the smallest units of the currency :param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the smallest units of the currency. At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed max_tip_amount. + :param protect_content: :return: """ method_url = r'sendInvoice' diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index 011ff46..0e1deae 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -1335,7 +1335,7 @@ class AsyncTeleBot: """ self.current_states = asyncio_handler_backends.StateFile(filename=filename) - self.current_states._create_dir() + self.current_states.create_dir() async def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None, drop_pending_updates = None, timeout=None): @@ -1790,6 +1790,7 @@ class AsyncTeleBot: :param timeout: timeout :param allow_sending_without_reply: :param protect_content: + :param data: deprecated, for backward compatibility :return: API reply. """ if data and not(sticker): @@ -1837,13 +1838,16 @@ class AsyncTeleBot: :param allow_sending_without_reply: :param reply_markup: :param timeout: - :param data: function typo miss compatibility: do not use it + :param data: deprecated, for backward compatibility """ parse_mode = self.parse_mode if (parse_mode is None) else parse_mode + if data and not(video): + # function typo miss compatibility + video = data return types.Message.de_json( await asyncio_helper.send_video( - self.token, chat_id, data, duration, caption, reply_to_message_id, reply_markup, + self.token, chat_id, video, duration, caption, reply_to_message_id, reply_markup, parse_mode, supports_streaming, disable_notification, timeout, thumb, width, height, caption_entities, allow_sending_without_reply, protect_content)) @@ -1873,6 +1877,7 @@ class AsyncTeleBot: :param thumb: InputFile or String : Thumbnail of the file sent :param caption: String : Animation caption (may also be used when resending animation by file_id). :param parse_mode: + :param protect_content: :param reply_to_message_id: :param reply_markup: :param disable_notification: @@ -3057,4 +3062,4 @@ class AsyncTeleBot: :param chat_id: """ for key, value in kwargs.items(): - await self.current_states._add_data(chat_id, key, value) \ No newline at end of file + await self.current_states.add_data(chat_id, key, value) diff --git a/telebot/asyncio_handler_backends.py b/telebot/asyncio_handler_backends.py index d3c452f..0a78a90 100644 --- a/telebot/asyncio_handler_backends.py +++ b/telebot/asyncio_handler_backends.py @@ -28,7 +28,7 @@ class StateMemory: """Delete a state""" self._states.pop(chat_id) - def _get_data(self, chat_id): + def get_data(self, chat_id): return self._states[chat_id]['data'] async def set(self, chat_id, new_state): @@ -39,7 +39,7 @@ class StateMemory: """ await self.add_state(chat_id,new_state) - async def _add_data(self, chat_id, key, value): + async def add_data(self, chat_id, key, value): result = self._states[chat_id]['data'][key] = value return result @@ -77,28 +77,28 @@ class StateFile: :param chat_id: :param state: new state """ - states_data = self._read_data() + states_data = self.read_data() if chat_id in states_data: states_data[chat_id]['state'] = state - return await self._save_data(states_data) + return await self.save_data(states_data) else: - new_data = states_data[chat_id] = {'state': state,'data': {}} - return await self._save_data(states_data) + states_data[chat_id] = {'state': state,'data': {}} + return await self.save_data(states_data) async def current_state(self, chat_id): """Current state.""" - states_data = self._read_data() + states_data = self.read_data() if chat_id in states_data: return states_data[chat_id]['state'] else: return False async def delete_state(self, chat_id): """Delete a state""" - states_data = self._read_data() + states_data = self.read_data() states_data.pop(chat_id) - await self._save_data(states_data) + await self.save_data(states_data) - def _read_data(self): + def read_data(self): """ Read the data from file. """ @@ -107,7 +107,7 @@ class StateFile: file.close() return states_data - def _create_dir(self): + def create_dir(self): """ Create directory .save-handlers. """ @@ -117,7 +117,7 @@ class StateFile: with open(self.file_path,'wb') as file: pickle.dump({}, file) - async def _save_data(self, new_data): + async def save_data(self, new_data): """ Save data after editing. :param new_data: @@ -126,8 +126,8 @@ class StateFile: pickle.dump(new_data, state_file, protocol=pickle.HIGHEST_PROTOCOL) return True - def _get_data(self, chat_id): - return self._read_data()[chat_id]['data'] + def get_data(self, chat_id): + return self.read_data()[chat_id]['data'] async def set(self, chat_id, new_state): """ @@ -138,10 +138,10 @@ class StateFile: """ await self.add_state(chat_id,new_state) - async def _add_data(self, chat_id, key, value): - states_data = self._read_data() + async def add_data(self, chat_id, key, value): + states_data = self.read_data() result = states_data[chat_id]['data'][key] = value - await self._save_data(result) + await self.save_data(result) return result @@ -173,7 +173,7 @@ class StateContext: def __init__(self , obj: StateMemory, chat_id) -> None: self.obj = obj self.chat_id = chat_id - self.data = obj._get_data(chat_id) + self.data = obj.get_data(chat_id) async def __aenter__(self): return self.data @@ -191,14 +191,14 @@ class StateFileContext: self.data = None async def __aenter__(self): - self.data = self.obj._get_data(self.chat_id) + self.data = self.obj.get_data(self.chat_id) return self.data async def __aexit__(self, exc_type, exc_val, exc_tb): - old_data = self.obj._read_data() + old_data = self.obj.read_data() for i in self.data: old_data[self.chat_id]['data'][i] = self.data.get(i) - await self.obj._save_data(old_data) + await self.obj.save_data(old_data) return diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index fc02775..e36a974 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -1,8 +1,6 @@ import asyncio # for future uses -from time import time import aiohttp from telebot import types -import json try: import ujson as json @@ -853,6 +851,8 @@ async def send_data(token, chat_id, data, data_type, reply_to_message_id=None, r payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities)) if allow_sending_without_reply is not None: payload['allow_sending_without_reply'] = allow_sending_without_reply + if protect_content is not None: + payload['protect_content'] = protect_content if method_url == 'sendDocument' and disable_content_type_detection is not None: payload['disable_content_type_detection'] = disable_content_type_detection return await _process_request(token, method_url, params=payload, files=files, method='post') diff --git a/telebot/handler_backends.py b/telebot/handler_backends.py index 45b903b..1e67870 100644 --- a/telebot/handler_backends.py +++ b/telebot/handler_backends.py @@ -168,7 +168,7 @@ class StateMemory: """Delete a state""" self._states.pop(chat_id) - def _get_data(self, chat_id): + def get_data(self, chat_id): return self._states[chat_id]['data'] def set(self, chat_id, new_state): @@ -179,7 +179,7 @@ class StateMemory: """ self.add_state(chat_id,new_state) - def _add_data(self, chat_id, key, value): + def add_data(self, chat_id, key, value): result = self._states[chat_id]['data'][key] = value return result @@ -217,28 +217,27 @@ class StateFile: :param chat_id: :param state: new state """ - states_data = self._read_data() + states_data = self.read_data() if chat_id in states_data: states_data[chat_id]['state'] = state - return self._save_data(states_data) + return self.save_data(states_data) else: - new_data = states_data[chat_id] = {'state': state,'data': {}} - return self._save_data(states_data) - + states_data[chat_id] = {'state': state,'data': {}} + return self.save_data(states_data) def current_state(self, chat_id): """Current state.""" - states_data = self._read_data() + states_data = self.read_data() if chat_id in states_data: return states_data[chat_id]['state'] else: return False def delete_state(self, chat_id): """Delete a state""" - states_data = self._read_data() + states_data = self.read_data() states_data.pop(chat_id) - self._save_data(states_data) + self.save_data(states_data) - def _read_data(self): + def read_data(self): """ Read the data from file. """ @@ -247,7 +246,7 @@ class StateFile: file.close() return states_data - def _create_dir(self): + def create_dir(self): """ Create directory .save-handlers. """ @@ -257,7 +256,7 @@ class StateFile: with open(self.file_path,'wb') as file: pickle.dump({}, file) - def _save_data(self, new_data): + def save_data(self, new_data): """ Save data after editing. :param new_data: @@ -266,23 +265,21 @@ class StateFile: pickle.dump(new_data, state_file, protocol=pickle.HIGHEST_PROTOCOL) return True - def _get_data(self, chat_id): - return self._read_data()[chat_id]['data'] + def get_data(self, chat_id): + return self.read_data()[chat_id]['data'] def set(self, chat_id, new_state): """ Set a new state for a user. :param chat_id: :param new_state: new_state of a user - """ self.add_state(chat_id,new_state) - def _add_data(self, chat_id, key, value): - states_data = self._read_data() + def add_data(self, chat_id, key, value): + states_data = self.read_data() result = states_data[chat_id]['data'][key] = value - self._save_data(result) - + self.save_data(result) return result def finish(self, chat_id): @@ -313,7 +310,7 @@ class StateContext: def __init__(self , obj: StateMemory, chat_id) -> None: self.obj = obj self.chat_id = chat_id - self.data = obj._get_data(chat_id) + self.data = obj.get_data(chat_id) def __enter__(self): return self.data @@ -321,6 +318,7 @@ class StateContext: def __exit__(self, exc_type, exc_val, exc_tb): return + class StateFileContext: """ Class for data. @@ -328,15 +326,14 @@ class StateFileContext: def __init__(self , obj: StateFile, chat_id) -> None: self.obj = obj self.chat_id = chat_id - self.data = self.obj._get_data(self.chat_id) + self.data = self.obj.get_data(self.chat_id) def __enter__(self): return self.data def __exit__(self, exc_type, exc_val, exc_tb): - old_data = self.obj._read_data() + old_data = self.obj.read_data() for i in self.data: old_data[self.chat_id]['data'][i] = self.data.get(i) - self.obj._save_data(old_data) - + self.obj.save_data(old_data) return diff --git a/telebot/util.py b/telebot/util.py index 1ab6201..8ec6be6 100644 --- a/telebot/util.py +++ b/telebot/util.py @@ -4,6 +4,7 @@ import re import string import threading import traceback +import warnings from typing import Any, Callable, List, Dict, Optional, Union # noinspection PyPep8Naming @@ -436,7 +437,7 @@ def generate_random_token(): return ''.join(random.sample(string.ascii_letters, 16)) -def deprecated(warn: bool=False, alternative: Optional[Callable]=None): +def deprecated(warn: bool=True, alternative: Optional[Callable]=None): """ Use this decorator to mark functions as deprecated. When the function is used, an info (or warning if `warn` is True) is logged. @@ -445,12 +446,11 @@ def deprecated(warn: bool=False, alternative: Optional[Callable]=None): """ def decorator(function): def wrapper(*args, **kwargs): + info = f"`{function.__name__}` is deprecated." + (f" Use `{alternative.__name__}` instead" if alternative else "") if not warn: - logger.info(f"`{function.__name__}` is deprecated." - + (f" Use `{alternative.__name__}` instead" if alternative else "")) + logger.info(info) else: - logger.warn(f"`{function.__name__}` is deprecated." - + (f" Use `{alternative.__name__}` instead" if alternative else "")) + logger.warning(info) return function(*args, **kwargs) return wrapper return decorator @@ -484,6 +484,7 @@ def antiflood(function, *args, **kwargs): """ from telebot.apihelper import ApiTelegramException from time import sleep + msg = None try: msg = function(*args, **kwargs) except ApiTelegramException as ex: @@ -491,4 +492,4 @@ def antiflood(function, *args, **kwargs): sleep(ex.result_json['parameters']['retry_after']) msg = function(*args, **kwargs) finally: - return msg \ No newline at end of file + return msg diff --git a/telebot/version.py b/telebot/version.py index 42af8f6..531afbd 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.3.0' +__version__ = '4.3.1' diff --git a/tests/test_telebot.py b/tests/test_telebot.py index 2976a9a..54d3807 100644 --- a/tests/test_telebot.py +++ b/tests/test_telebot.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import sys +import warnings sys.path.append('../') @@ -19,14 +20,19 @@ if not should_skip: CHAT_ID = os.environ['CHAT_ID'] GROUP_ID = os.environ['GROUP_ID'] -def _new_test(): - pass -@util.deprecated(alternative=_new_test) -def _test(): - pass - +def deprecated1_new_function(): + print("deprecated1_new_function") +def deprecated1_old_function(): + print("deprecated1_old_function") + warnings.warn("The 'deprecated1_old_function' is deprecated. Use `deprecated1_new_function` instead", DeprecationWarning, 2) + deprecated1_new_function() +def deprecated2_new_function(): + print("deprecated2_new_function") +@util.deprecated(alternative=deprecated2_new_function) +def deprecated2_old_function(): + print("deprecated2_old_function") @pytest.mark.skipif(should_skip, reason="No environment variables configured") class TestTeleBot: @@ -216,7 +222,7 @@ class TestTeleBot: def test_send_audio(self): file_data = open('./test_data/record.mp3', 'rb') tb = telebot.TeleBot(TOKEN) - ret_msg = tb.send_audio(CHAT_ID, file_data, 1, performer='eternnoir', title='pyTelegram') + ret_msg = tb.send_audio(CHAT_ID, file_data, duration = 1, performer='eternnoir', title='pyTelegram') assert ret_msg.content_type == 'audio' assert ret_msg.audio.performer == 'eternnoir' assert ret_msg.audio.title == 'pyTelegram' @@ -224,7 +230,7 @@ class TestTeleBot: def test_send_audio_dis_noti(self): file_data = open('./test_data/record.mp3', 'rb') tb = telebot.TeleBot(TOKEN) - ret_msg = tb.send_audio(CHAT_ID, file_data, 1, performer='eternnoir', title='pyTelegram', + ret_msg = tb.send_audio(CHAT_ID, file_data, duration = 1, performer='eternnoir', title='pyTelegram', disable_notification=True) assert ret_msg.content_type == 'audio' assert ret_msg.audio.performer == 'eternnoir' @@ -433,8 +439,10 @@ class TestTeleBot: def test_create_revoke_detailed_chat_invite_link(self): tb = telebot.TeleBot(TOKEN) - cil = tb.create_chat_invite_link(GROUP_ID, - (datetime.now() + timedelta(minutes=1)).timestamp(), member_limit=5) + cil = tb.create_chat_invite_link( + GROUP_ID, + expire_date = datetime.now() + timedelta(minutes=1), + member_limit=5) assert isinstance(cil.invite_link, str) assert cil.creator.id == tb.get_me().id assert isinstance(cil.expire_date, (float, int)) @@ -458,9 +466,10 @@ class TestTeleBot: def test_antiflood(self): text = "Flooding" tb = telebot.TeleBot(TOKEN) - for _ in range(0,100): + i = -1 + for i in range(0,100): util.antiflood(tb.send_message, CHAT_ID, text) - assert _ + assert i @staticmethod def create_text_message(text): @@ -579,14 +588,14 @@ class TestTeleBot: ret_msg = tb.set_my_commands([telebot.types.BotCommand(command, description)], scope, lang) assert ret_msg is True - ret_msg = tb.get_my_commands(scope, lang) + ret_msg = tb.get_my_commands(scope = scope, language_code = lang) assert ret_msg[0].command == command assert ret_msg[0].description == description - ret_msg = tb.delete_my_commands(scope, lang) + ret_msg = tb.delete_my_commands(scope = scope, language_code = lang) assert ret_msg is True - ret_msg = tb.get_my_commands(scope, lang) + ret_msg = tb.get_my_commands(scope = scope, language_code = lang) assert ret_msg == [] @@ -633,7 +642,8 @@ class TestTeleBot: assert update.message.text == 'got' * 2 def test_deprecated_dec(self): - _test() + deprecated1_old_function() + deprecated2_old_function() def test_chat_permissions(self): return # CHAT_ID is private chat, no permissions can be set