From 98044d6faa8f8b34df46f596e4442b57d98c9434 Mon Sep 17 00:00:00 2001 From: _run Date: Wed, 13 Oct 2021 18:34:36 +0500 Subject: [PATCH 01/19] File support for states File support. Now states can be saved in pickle file --- examples/custom_states.py | 4 ++ telebot/__init__.py | 16 ++++- telebot/custom_filters.py | 6 +- telebot/handler_backends.py | 131 ++++++++++++++++++++++++++++++++++-- 4 files changed, 148 insertions(+), 9 deletions(-) diff --git a/examples/custom_states.py b/examples/custom_states.py index ac70bb96e..c7acfe558 100644 --- a/examples/custom_states.py +++ b/examples/custom_states.py @@ -61,4 +61,8 @@ def age_incorrect(message): bot.add_custom_filter(custom_filters.StateFilter(bot)) bot.add_custom_filter(custom_filters.IsDigitFilter()) + +# set saving states into file. +bot.enable_saving_states() # you can delete this if you do not need to save states + bot.infinity_polling(skip_pending=True) \ No newline at end of file diff --git a/telebot/__init__.py b/telebot/__init__.py index 8f46afc85..b8b0c6e43 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -27,7 +27,7 @@ logger.setLevel(logging.ERROR) from telebot import apihelper, util, types -from telebot.handler_backends import MemoryHandlerBackend, FileHandlerBackend, State +from telebot.handler_backends import MemoryHandlerBackend, FileHandlerBackend, StateMemory, StateFile REPLY_MARKUP_TYPES = Union[ @@ -188,7 +188,8 @@ def __init__( self.custom_filters = {} self.state_handlers = [] - self.current_states = State() + self.current_states = StateMemory() + if apihelper.ENABLE_MIDDLEWARE: self.typed_middleware_handlers = { @@ -237,6 +238,17 @@ def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/s """ self.next_step_backend = FileHandlerBackend(self.next_step_backend.handlers, filename, delay) + def enable_saving_states(self, filename="./.state-save/states.pkl"): + """ + Enable saving states (by default saving disabled) + + :param filename: Filename of saving file + + """ + + self.current_states = StateFile(filename=filename) + self.current_states._create_dir() + def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"): """ Enable saving reply handlers (by default saving disable) diff --git a/telebot/custom_filters.py b/telebot/custom_filters.py index bce2399af..0b9552359 100644 --- a/telebot/custom_filters.py +++ b/telebot/custom_filters.py @@ -158,9 +158,9 @@ def __init__(self, bot): key = 'state' def check(self, message, text): - if self.bot.current_states.current_state(message.from_user.id) is False:return False - elif text == '*':return True - elif type(text) is list:return self.bot.current_states.current_state(message.from_user.id) in text + if self.bot.current_states.current_state(message.from_user.id) is False: return False + elif text == '*': return True + elif type(text) is list: return self.bot.current_states.current_state(message.from_user.id) in text return self.bot.current_states.current_state(message.from_user.id) == text class IsDigitFilter(SimpleCustomFilter): diff --git a/telebot/handler_backends.py b/telebot/handler_backends.py index d695b8212..45b903beb 100644 --- a/telebot/handler_backends.py +++ b/telebot/handler_backends.py @@ -143,7 +143,7 @@ def get_handlers(self, handler_group_id): return handlers -class State: +class StateMemory: def __init__(self): self._states = {} @@ -166,7 +166,7 @@ def current_state(self, chat_id): def delete_state(self, chat_id): """Delete a state""" - return self._states.pop(chat_id) + self._states.pop(chat_id) def _get_data(self, chat_id): return self._states[chat_id]['data'] @@ -195,7 +195,7 @@ def retrieve_data(self, chat_id): Save input text. Usage: - with state.retrieve_data(message.chat.id) as data: + with bot.retrieve_data(message.chat.id) as data: data['name'] = message.text Also, at the end of your 'Form' you can get the name: @@ -203,11 +203,114 @@ def retrieve_data(self, chat_id): """ return StateContext(self, chat_id) + +class StateFile: + """ + Class to save states in a file. + """ + def __init__(self, filename): + self.file_path = filename + + def add_state(self, chat_id, state): + """ + Add a state. + :param chat_id: + :param state: new state + """ + states_data = self._read_data() + if chat_id in states_data: + states_data[chat_id]['state'] = state + return self._save_data(states_data) + else: + new_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() + 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.pop(chat_id) + self._save_data(states_data) + + def _read_data(self): + """ + Read the data from file. + """ + file = open(self.file_path, 'rb') + states_data = pickle.load(file) + file.close() + return states_data + + def _create_dir(self): + """ + Create directory .save-handlers. + """ + dirs = self.file_path.rsplit('/', maxsplit=1)[0] + os.makedirs(dirs, exist_ok=True) + if not os.path.isfile(self.file_path): + with open(self.file_path,'wb') as file: + pickle.dump({}, file) + + def _save_data(self, new_data): + """ + Save data after editing. + :param new_data: + """ + with open(self.file_path, 'wb+') as state_file: + 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 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() + result = states_data[chat_id]['data'][key] = value + self._save_data(result) + + return result + + def finish(self, chat_id): + """ + Finish(delete) state of a user. + :param chat_id: + """ + self.delete_state(chat_id) + + def retrieve_data(self, chat_id): + """ + Save input text. + + Usage: + with bot.retrieve_data(message.chat.id) as data: + data['name'] = message.text + + Also, at the end of your 'Form' you can get the name: + data['name'] + """ + return StateFileContext(self, chat_id) + + class StateContext: """ Class for data. """ - def __init__(self , obj: State, chat_id) -> None: + def __init__(self , obj: StateMemory, chat_id) -> None: self.obj = obj self.chat_id = chat_id self.data = obj._get_data(chat_id) @@ -217,3 +320,23 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): return + +class StateFileContext: + """ + Class for data. + """ + 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) + + def __enter__(self): + return self.data + + def __exit__(self, exc_type, exc_val, exc_tb): + 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) + + return From b6625baec63038cb2733c1ba5a951a3275713457 Mon Sep 17 00:00:00 2001 From: _run Date: Wed, 13 Oct 2021 19:02:17 +0500 Subject: [PATCH 02/19] Update __init__.py --- telebot/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index b8b0c6e43..64cf2c75e 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -357,7 +357,7 @@ def delete_webhook(self, drop_pending_updates=None, timeout=None): """ return apihelper.delete_webhook(self.token, drop_pending_updates, timeout) - def get_webhook_info(self, timeout=None): + def get_webhook_info(self, timeout: Optional[int]=None): """ Use this method to get current webhook status. Requires no parameters. If the bot is using getUpdates, will return an object with the url field empty. @@ -2381,7 +2381,7 @@ def register_next_step_handler( chat_id = message.chat.id self.register_next_step_handler_by_chat_id(chat_id, callback, *args, **kwargs) - def set_state(self, chat_id, state): + def set_state(self, chat_id: int, state: Union[int, str]): """ Sets a new state of a user. :param chat_id: @@ -2389,7 +2389,7 @@ def set_state(self, chat_id, state): """ self.current_states.add_state(chat_id, state) - def delete_state(self, chat_id): + def delete_state(self, chat_id: int): """ Delete the current state of a user. :param chat_id: @@ -2397,10 +2397,10 @@ def delete_state(self, chat_id): """ self.current_states.delete_state(chat_id) - def retrieve_data(self, chat_id): + def retrieve_data(self, chat_id: int): return self.current_states.retrieve_data(chat_id) - def get_state(self, chat_id): + def get_state(self, chat_id: int): """ Get current state of a user. :param chat_id: @@ -2408,7 +2408,7 @@ def get_state(self, chat_id): """ return self.current_states.current_state(chat_id) - def add_data(self, chat_id, **kwargs): + def add_data(self, chat_id: int, **kwargs): """ Add data to states. :param chat_id: From 5fb48e68a05324b2acda4733a4f792d5136f61e2 Mon Sep 17 00:00:00 2001 From: Andrea Barbagallo Date: Sat, 16 Oct 2021 17:45:15 +0200 Subject: [PATCH 03/19] Added description of the ApiTelegramException as attribute of the class --- .gitignore | 1 + telebot/apihelper.py | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c9919ab0a..e2bc744b6 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ var/ .idea/ venv/ +.venv/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 9588c4e23..07e052842 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1661,4 +1661,5 @@ def __init__(self, function_name, result, result_json): result) self.result_json = result_json self.error_code = result_json['error_code'] + self.description = result_json['description'] From bb58d3fead6b399cf1d2784ecb01b7874fac6971 Mon Sep 17 00:00:00 2001 From: resinprotein2333 Date: Sun, 24 Oct 2021 16:45:49 +0800 Subject: [PATCH 04/19] Update README.md Add my bot into the bot list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 39406d4c1..e870e7ef6 100644 --- a/README.md +++ b/README.md @@ -761,6 +761,7 @@ Get help. Discuss. Chat. * [oneIPO bot](https://github.com/aaditya2200/IPO-proj) by [Aadithya](https://github.com/aaditya2200) & [Amol Soans](https://github.com/AmolDerickSoans) This Telegram bot provides live updates , data and documents on current and upcoming IPOs(Initial Public Offerings) * [CoronaGraphsBot](https://t.me/CovidGraph_bot) ([source](https://github.com/TrevorWinstral/CoronaGraphsBot)) by *TrevorWinstral* - Gets live COVID Country data, plots it, and briefs the user * [ETHLectureBot](https://t.me/ETHLectureBot) ([source](https://github.com/TrevorWinstral/ETHLectureBot)) by *TrevorWinstral* - Notifies ETH students when their lectures have been uploaded +* [Vlun Finder Bot](https://github.com/resinprotein2333/Vlun-Finder-bot) by [Resinprotein2333](https://github.com/resinprotein2333). This bot can help you to find The information of CVE vulnerabilities. From 1a351bc8c76756792c79cf0fc5aeb1eb42def157 Mon Sep 17 00:00:00 2001 From: Advik Singh Somvanshi Date: Sun, 24 Oct 2021 20:38:15 +0530 Subject: [PATCH 05/19] Added A New Bot --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e870e7ef6..10682a71e 100644 --- a/README.md +++ b/README.md @@ -762,6 +762,7 @@ Get help. Discuss. Chat. * [CoronaGraphsBot](https://t.me/CovidGraph_bot) ([source](https://github.com/TrevorWinstral/CoronaGraphsBot)) by *TrevorWinstral* - Gets live COVID Country data, plots it, and briefs the user * [ETHLectureBot](https://t.me/ETHLectureBot) ([source](https://github.com/TrevorWinstral/ETHLectureBot)) by *TrevorWinstral* - Notifies ETH students when their lectures have been uploaded * [Vlun Finder Bot](https://github.com/resinprotein2333/Vlun-Finder-bot) by [Resinprotein2333](https://github.com/resinprotein2333). This bot can help you to find The information of CVE vulnerabilities. +* [ETHGasFeeTrackerBot](https://t.me/ETHGasFeeTrackerBot) ([Source](https://github.com/DevAdvik/ETHGasFeeTrackerBot]) by *DevAdvik* - Get Live Ethereum Gas Fees in GWEI From 558b37b1c3b0beba02e15d24db0ed6b3bf6a91f4 Mon Sep 17 00:00:00 2001 From: Andrea Barbagallo Date: Wed, 3 Nov 2021 15:30:10 +0100 Subject: [PATCH 06/19] New antiflood function --- telebot/util.py | 22 ++++++++++++++++++++++ tests/test_telebot.py | 7 +++++++ 2 files changed, 29 insertions(+) diff --git a/telebot/util.py b/telebot/util.py index f871f09e7..b33aaa1ba 100644 --- a/telebot/util.py +++ b/telebot/util.py @@ -455,3 +455,25 @@ def webhook_google_functions(bot, request): return 'Bot FAIL', 400 else: return 'Bot ON' + +def antiflood(function, *args, **kwargs): + """ + Use this function inside loops in order to avoid getting TooManyRequests error. + Example: + + from telebot.util import antiflood + for chat_id in chat_id_list: + msg = antiflood(bot.send_message, chat_id, text) + + You want get the + """ + from telebot.apihelper import ApiTelegramException + from time import sleep + try: + msg = function(*args, **kwargs) + except ApiTelegramException as ex: + if ex.error_code == 429: + sleep(ex.result_json['parameters']['retry_after']) + msg = function(*args, **kwargs) + finally: + return msg \ No newline at end of file diff --git a/tests/test_telebot.py b/tests/test_telebot.py index 6e8f341e1..2426b0f71 100644 --- a/tests/test_telebot.py +++ b/tests/test_telebot.py @@ -455,6 +455,13 @@ def test_edit_markup(self): new_msg = tb.edit_message_reply_markup(chat_id=CHAT_ID, message_id=ret_msg.message_id, reply_markup=markup) assert new_msg.message_id + def test_antiflood(self): + text = "Flooding" + tb = telebot.TeleBot(TOKEN) + for _ in range(0,100): + util.antiflood(tb.send_message, CHAT_ID, text) + assert _ + @staticmethod def create_text_message(text): params = {'text': text} From 06c878212704cc5e41622d9d18a2faaa2028da83 Mon Sep 17 00:00:00 2001 From: _run Date: Fri, 5 Nov 2021 23:22:03 +0500 Subject: [PATCH 07/19] Little update Allowed other handlers, checked methods and other things --- telebot/__init__.py | 63 +++++++++++++++++++++++++++++++++++++++----- telebot/apihelper.py | 12 +++++++-- telebot/types.py | 31 +++++++++++++++++++--- 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 64cf2c75e..185d1ee98 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -185,6 +185,7 @@ def __init__( self.poll_answer_handlers = [] self.my_chat_member_handlers = [] self.chat_member_handlers = [] + self.chat_join_request_handlers = [] self.custom_filters = {} self.state_handlers = [] @@ -205,7 +206,8 @@ def __init__( 'poll': [], 'poll_answer': [], 'my_chat_member': [], - 'chat_member': [] + 'chat_member': [], + 'chat_join_request': [] } self.default_middleware_handlers = [] @@ -426,6 +428,7 @@ def process_new_updates(self, updates): new_poll_answers = None new_my_chat_members = None new_chat_members = None + chat_join_request = None for update in updates: if apihelper.ENABLE_MIDDLEWARE: @@ -480,6 +483,9 @@ def process_new_updates(self, updates): if update.chat_member: if new_chat_members is None: new_chat_members = [] new_chat_members.append(update.chat_member) + if update.chat_join_request: + if chat_join_request is None: chat_join_request = [] + chat_join_request.append(update.chat_join_request) if new_messages: self.process_new_messages(new_messages) @@ -507,6 +513,9 @@ def process_new_updates(self, updates): self.process_new_my_chat_member(new_my_chat_members) if new_chat_members: self.process_new_chat_member(new_chat_members) + if chat_join_request: + self.process_chat_join_request(chat_join_request) + def process_new_messages(self, new_messages): self._notify_next_handlers(new_messages) @@ -550,6 +559,9 @@ def process_new_my_chat_member(self, my_chat_members): def process_new_chat_member(self, chat_members): self._notify_command_handlers(self.chat_member_handlers, chat_members) + def process_chat_join_request(self, chat_join_request): + self._notify_command_handlers(self.chat_join_request_handlers, chat_join_request) + def process_middlewares(self, update): for update_type, middlewares in self.typed_middleware_handlers.items(): if getattr(update, update_type) is not None: @@ -1667,9 +1679,11 @@ def set_chat_permissions( return apihelper.set_chat_permissions(self.token, chat_id, permissions) def create_chat_invite_link( - self, chat_id: Union[int, str], + self, chat_id: Union[int, str], + name: Optional[str]=None, expire_date: Optional[Union[int, datetime]]=None, - member_limit: Optional[int]=None) -> types.ChatInviteLink: + member_limit: Optional[int]=None, + creates_join_request: Optional[bool]=None) -> types.ChatInviteLink: """ Use this method to create an additional invite link for a chat. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. @@ -1681,13 +1695,15 @@ def create_chat_invite_link( :return: """ return types.ChatInviteLink.de_json( - apihelper.create_chat_invite_link(self.token, chat_id, expire_date, member_limit) + apihelper.create_chat_invite_link(self.token, chat_id, name, expire_date, member_limit, creates_join_request) ) def edit_chat_invite_link( - self, chat_id: Union[int, str], invite_link: str, + self, chat_id: Union[int, str], name: Optional[str]=None, + invite_link: Optional[str] = None, expire_date: Optional[Union[int, datetime]]=None, - member_limit: Optional[int]=None) -> types.ChatInviteLink: + member_limit: Optional[int]=None , + creates_join_request: Optional[bool]=None) -> types.ChatInviteLink: """ Use this method to edit a non-primary invite link created by the bot. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. @@ -1701,7 +1717,7 @@ def edit_chat_invite_link( :return: """ return types.ChatInviteLink.de_json( - apihelper.edit_chat_invite_link(self.token, chat_id, invite_link, expire_date, member_limit) + apihelper.edit_chat_invite_link(self.token, chat_id, name, invite_link, expire_date, member_limit, creates_join_request) ) def revoke_chat_invite_link( @@ -3148,6 +3164,39 @@ def register_chat_member_handler(self, callback, func=None, **kwargs): handler_dict = self._build_handler_dict(callback, func=func, **kwargs) self.add_chat_member_handler(handler_dict) + def chat_join_request_handler(self, func=None, **kwargs): + """ + chat_join_request handler + :param func: + :param kwargs: + :return: + """ + + def decorator(handler): + handler_dict = self._build_handler_dict(handler, func=func, **kwargs) + self.add_chat_join_request_handler(handler_dict) + return handler + + return decorator + + def add_chat_join_request_handler(self, handler_dict): + """ + Adds a chat_join_request handler + :param handler_dict: + :return: + """ + self.chat_join_request_handlers.append(handler_dict) + + def register_chat_join_request_handler(self, callback, func=None, **kwargs): + """ + Registers chat join request handler. + :param callback: function to be called + :param func: + :return: decorated function + """ + handler_dict = self._build_handler_dict(callback, func=func, **kwargs) + self.add_chat_join_request_handler(handler_dict) + def _test_message_handler(self, message_handler, message): """ Test message handler diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 7b7ee9671..4d4f919dc 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -973,7 +973,7 @@ def set_chat_permissions(token, chat_id, permissions): return _make_request(token, method_url, params=payload, method='post') -def create_chat_invite_link(token, chat_id, expire_date, member_limit): +def create_chat_invite_link(token, chat_id, name, expire_date, member_limit, creates_join_request): method_url = 'createChatInviteLink' payload = { 'chat_id': chat_id @@ -986,11 +986,15 @@ def create_chat_invite_link(token, chat_id, expire_date, member_limit): payload['expire_date'] = expire_date if member_limit: payload['member_limit'] = member_limit + if creates_join_request: + payload['creates_join_request'] = creates_join_request + if name: + payload['name'] = name return _make_request(token, method_url, params=payload, method='post') -def edit_chat_invite_link(token, chat_id, invite_link, expire_date, member_limit): +def edit_chat_invite_link(token, chat_id, invite_link, name, expire_date, member_limit, creates_join_request): method_url = 'editChatInviteLink' payload = { 'chat_id': chat_id, @@ -1005,6 +1009,10 @@ def edit_chat_invite_link(token, chat_id, invite_link, expire_date, member_limit if member_limit is not None: payload['member_limit'] = member_limit + if name: + payload['name'] = name + if creates_join_request: + payload['creates_join_request'] = creates_join_request return _make_request(token, method_url, params=payload, method='post') diff --git a/telebot/types.py b/telebot/types.py index 8f6821824..99b25591a 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -107,6 +107,7 @@ def de_json(cls, json_string): poll_answer = PollAnswer.de_json(obj.get('poll_answer')) my_chat_member = ChatMemberUpdated.de_json(obj.get('my_chat_member')) chat_member = ChatMemberUpdated.de_json(obj.get('chat_member')) + chat_join_request = ChatJoinRequest.de_json(obj.get('chat_join_request')) return cls(update_id, message, edited_message, channel_post, edited_channel_post, inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer, my_chat_member, chat_member) @@ -166,6 +167,22 @@ def difference(self) -> Dict[str, List]: dif[key] = [old[key], new[key]] return dif +class ChatJoinRequest(JsonDeserializable): + @classmethod + def de_json(cls, json_string): + if json_string is None: return None + obj = cls.check_json(json_string) + obj['chat'] = Chat.de_json(obj['chat']) + obj['from'] = User.de_json(obj['from']) + + return cls(**obj) + + def __init__(self, chat, from_user, date, bio=None, invite_link=None, **kwargs): + self.chat = Chat = chat + self.from_user: User = from_user + self.date: int = date + self.bio: Optional[str] = bio + self.invite_link: Optional[ChatInviteLink] = invite_link class WebhookInfo(JsonDeserializable): @classmethod @@ -2752,14 +2769,17 @@ def de_json(cls, json_string): obj['creator'] = User.de_json(obj['creator']) return cls(**obj) - def __init__(self, invite_link, creator, is_primary, is_revoked, - expire_date=None, member_limit=None, **kwargs): + def __init__(self, invite_link, creator, creates_join_request , is_primary, is_revoked, + name=None, expire_date=None, member_limit=None, pending_join_request_count=None, **kwargs): self.invite_link: str = invite_link self.creator: User = creator + self.creates_join_request: bool = creates_join_request self.is_primary: bool = is_primary self.is_revoked: bool = is_revoked + self.name: str = name self.expire_date: int = expire_date self.member_limit: int = member_limit + self.pending_join_request_count: int = pending_join_request_count def to_json(self): return json.dumps(self.to_dict()) @@ -2769,12 +2789,17 @@ def to_dict(self): "invite_link": self.invite_link, "creator": self.creator.to_dict(), "is_primary": self.is_primary, - "is_revoked": self.is_revoked + "is_revoked": self.is_revoked, + "creates_join_request": self.creates_join_request } if self.expire_date: json_dict["expire_date"] = self.expire_date if self.member_limit: json_dict["member_limit"] = self.member_limit + if self.pending_join_request_count: + json_dict["pending_join_request_count"] = self.pending_join_request_count + if self.name: + json_dict["name"] = self.name return json_dict From 953e2286b854a84364f3b50ae2793841e408f8a9 Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 12:15:28 +0500 Subject: [PATCH 08/19] Bot API 5.4 --- examples/chat_join_request.py | 11 +++++++++++ telebot/__init__.py | 28 ++++++++++++++++++++++++++++ telebot/apihelper.py | 15 ++++++++++++++- telebot/types.py | 7 ++++--- telebot/util.py | 2 +- 5 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 examples/chat_join_request.py diff --git a/examples/chat_join_request.py b/examples/chat_join_request.py new file mode 100644 index 000000000..6ab29ed9f --- /dev/null +++ b/examples/chat_join_request.py @@ -0,0 +1,11 @@ +import telebot + + +bot = telebot.TeleBot('TOKEN') + +@bot.chat_join_request_handler() +def make_some(message: telebot.types.ChatJoinRequest): + bot.send_message(message.chat.id, 'I accepted a new user!') + bot.approve_chat_join_request(message.chat.id, message.from_user.id) + +bot.infinity_polling(allowed_updates=telebot.util.update_types) \ No newline at end of file diff --git a/telebot/__init__.py b/telebot/__init__.py index 185d1ee98..4aba9f98a 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -484,6 +484,7 @@ def process_new_updates(self, updates): if new_chat_members is None: new_chat_members = [] new_chat_members.append(update.chat_member) if update.chat_join_request: + print('we received1') if chat_join_request is None: chat_join_request = [] chat_join_request.append(update.chat_join_request) @@ -514,6 +515,7 @@ def process_new_updates(self, updates): if new_chat_members: self.process_new_chat_member(new_chat_members) if chat_join_request: + print('we received2') self.process_chat_join_request(chat_join_request) @@ -1747,6 +1749,32 @@ def export_chat_invite_link(self, chat_id: Union[int, str]) -> str: """ return apihelper.export_chat_invite_link(self.token, chat_id) + def approve_chat_join_request(self, chat_id: Union[str, int], user_id: Union[int, str]) -> bool: + """ + Use this method to approve a chat join request. + The bot must be an administrator in the chat for this to work and must have + the can_invite_users administrator right. Returns True on success. + + :param chat_id: Unique identifier for the target chat or username of the target supergroup + (in the format @supergroupusername) + :param user_id: Unique identifier of the target user + :return: True on success. + """ + return apihelper.approve_chat_join_request(self.token, chat_id, user_id) + + def decline_chat_join_request(self, chat_id: Union[str, int], user_id: Union[int, str]) -> bool: + """ + Use this method to decline a chat join request. + The bot must be an administrator in the chat for this to work and must have + the can_invite_users administrator right. Returns True on success. + + :param chat_id: Unique identifier for the target chat or username of the target supergroup + (in the format @supergroupusername) + :param user_id: Unique identifier of the target user + :return: True on success. + """ + return apihelper.decline_chat_join_request(self.token, chat_id, user_id) + def set_chat_photo(self, chat_id: Union[int, str], photo: Any) -> bool: """ Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 4d4f919dc..f66db8af6 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1031,7 +1031,20 @@ def export_chat_invite_link(token, chat_id): payload = {'chat_id': chat_id} return _make_request(token, method_url, params=payload, method='post') - +def approve_chat_join_request(token, chat_id, user_id): + method_url = 'approveChatJoinRequest' + payload = { + 'chat_id': chat_id, + 'user_id': user_id + } + return _make_request(token, method_url, params=payload, method='post') +def decline_chat_join_request(token, chat_id, user_id): + method_url = 'declineChatJoinRequest' + payload = { + 'chat_id': chat_id, + 'user_id': user_id + } + return _make_request(token, method_url, params=payload, method='post') def set_chat_photo(token, chat_id, photo): method_url = 'setChatPhoto' payload = {'chat_id': chat_id} diff --git a/telebot/types.py b/telebot/types.py index 99b25591a..972e2fd11 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -110,11 +110,11 @@ def de_json(cls, json_string): chat_join_request = ChatJoinRequest.de_json(obj.get('chat_join_request')) return cls(update_id, message, edited_message, channel_post, edited_channel_post, inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer, - my_chat_member, chat_member) + my_chat_member, chat_member, chat_join_request) def __init__(self, update_id, message, edited_message, channel_post, edited_channel_post, inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer, - my_chat_member, chat_member): + my_chat_member, chat_member, chat_join_request): self.update_id = update_id self.message = message self.edited_message = edited_message @@ -129,6 +129,7 @@ def __init__(self, update_id, message, edited_message, channel_post, edited_chan self.poll_answer = poll_answer self.my_chat_member = my_chat_member self.chat_member = chat_member + self.chat_join_request = chat_join_request class ChatMemberUpdated(JsonDeserializable): @@ -173,7 +174,7 @@ def de_json(cls, json_string): if json_string is None: return None obj = cls.check_json(json_string) obj['chat'] = Chat.de_json(obj['chat']) - obj['from'] = User.de_json(obj['from']) + obj['from_user'] = User.de_json(obj['from']) return cls(**obj) diff --git a/telebot/util.py b/telebot/util.py index 5eb99bcbd..1ab620128 100644 --- a/telebot/util.py +++ b/telebot/util.py @@ -46,7 +46,7 @@ update_types = [ "update_id", "message", "edited_message", "channel_post", "edited_channel_post", "inline_query", "chosen_inline_result", "callback_query", "shipping_query", "pre_checkout_query", "poll", "poll_answer", - "my_chat_member", "chat_member" + "my_chat_member", "chat_member", "chat_join_request" ] class WorkerThread(threading.Thread): From ed6616e4c72aba45ed9a53c52de57acbf8dda29f Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 12:21:02 +0500 Subject: [PATCH 09/19] Bot API 5.4 --- telebot/__init__.py | 2 -- telebot/types.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 4aba9f98a..0919f6d33 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -484,7 +484,6 @@ def process_new_updates(self, updates): if new_chat_members is None: new_chat_members = [] new_chat_members.append(update.chat_member) if update.chat_join_request: - print('we received1') if chat_join_request is None: chat_join_request = [] chat_join_request.append(update.chat_join_request) @@ -515,7 +514,6 @@ def process_new_updates(self, updates): if new_chat_members: self.process_new_chat_member(new_chat_members) if chat_join_request: - print('we received2') self.process_chat_join_request(chat_join_request) diff --git a/telebot/types.py b/telebot/types.py index 972e2fd11..fdf64672c 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -175,6 +175,7 @@ def de_json(cls, json_string): obj = cls.check_json(json_string) obj['chat'] = Chat.de_json(obj['chat']) obj['from_user'] = User.de_json(obj['from']) + obj['invite_link'] = ChatInviteLink.de_json(obj['invite_link']) return cls(**obj) From d49c57699eb470f5c4a299ee23e5b42cd2b168b9 Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 12:27:19 +0500 Subject: [PATCH 10/19] Tests --- tests/test_handler_backends.py | 6 ++++-- tests/test_telebot.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_handler_backends.py b/tests/test_handler_backends.py index 638cb2758..21cf8f9b2 100644 --- a/tests/test_handler_backends.py +++ b/tests/test_handler_backends.py @@ -64,9 +64,10 @@ def update_type(message): poll_answer = None my_chat_member = None chat_member = None + chat_join_request = None return types.Update(1001234038283, message, edited_message, channel_post, edited_channel_post, inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer, - my_chat_member, chat_member) + my_chat_member, chat_member, chat_join_request) @pytest.fixture() @@ -83,9 +84,10 @@ def reply_to_message_update_type(reply_to_message): poll_answer = None my_chat_member = None chat_member = None + chat_join_request = None return types.Update(1001234038284, reply_to_message, edited_message, channel_post, edited_channel_post, inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query, - poll, poll_answer, my_chat_member, chat_member) + poll, poll_answer, my_chat_member, chat_member, chat_join_request) def next_handler(message): diff --git a/tests/test_telebot.py b/tests/test_telebot.py index 2426b0f71..2976a9ab9 100644 --- a/tests/test_telebot.py +++ b/tests/test_telebot.py @@ -485,9 +485,10 @@ def create_message_update(text): poll_answer = None my_chat_member = None chat_member = None + chat_join_request = None return types.Update(-1001234038283, message, edited_message, channel_post, edited_channel_post, inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer, - my_chat_member, chat_member) + my_chat_member, chat_member, chat_join_request) def test_is_string_unicode(self): s1 = u'string' From 31097c5380cb8fde8b6999685ce82246be94d720 Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 12:34:49 +0500 Subject: [PATCH 11/19] Update test_types.py --- tests/test_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_types.py b/tests/test_types.py index 417a67827..4669f8264 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -222,7 +222,7 @@ def test_KeyboardButtonPollType(): def test_json_chat_invite_link(): - json_string = r'{"invite_link": "https://t.me/joinchat/z-abCdEFghijKlMn", "creator": {"id": 329343347, "is_bot": false, "first_name": "Test", "username": "test_user", "last_name": "User", "language_code": "en"}, "is_primary": false, "is_revoked": false, "expire_date": 1624119999, "member_limit": 10}' + json_string = r'{"invite_link":{"invite_link":"https://t.me/joinchat/MeASP-Wi...","creator":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"ru"},"pending_join_request_count":1,"creates_join_request":true,"is_primary":false,"is_revoked":false }}' invite_link = types.ChatInviteLink.de_json(json_string) assert invite_link.invite_link == 'https://t.me/joinchat/z-abCdEFghijKlMn' assert isinstance(invite_link.creator, types.User) From 6808ab3ebeb2da79e0b4bffc6f6bb6e6a375878d Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 12:42:48 +0500 Subject: [PATCH 12/19] Update test_types.py --- tests/test_types.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_types.py b/tests/test_types.py index 4669f8264..d23f8fa3f 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -222,14 +222,18 @@ def test_KeyboardButtonPollType(): def test_json_chat_invite_link(): - json_string = r'{"invite_link":{"invite_link":"https://t.me/joinchat/MeASP-Wi...","creator":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"ru"},"pending_join_request_count":1,"creates_join_request":true,"is_primary":false,"is_revoked":false }}' + json_string = r'{"invite_link":"https://t.me/joinchat/MeASP-Wi...","creator":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"ru"},"pending_join_request_count":1,"creates_join_request":true,"is_primary":false,"is_revoked":false}' invite_link = types.ChatInviteLink.de_json(json_string) - assert invite_link.invite_link == 'https://t.me/joinchat/z-abCdEFghijKlMn' + assert invite_link.invite_link == 'https://t.me/joinchat/MeASP-Wi...' assert isinstance(invite_link.creator, types.User) assert not invite_link.is_primary assert not invite_link.is_revoked - assert invite_link.expire_date == 1624119999 - assert invite_link.member_limit == 10 + assert invite_link.expire_date is None + assert invite_link.member_limit is None + assert invite_link.name is None + assert invite_link.creator.id == 927266710 + assert invite_link.pending_join_request_count == 1 + assert invite_link.creates_join_request def test_chat_member_updated(): json_string = r'{"chat": {"id": -1234567890123, "type": "supergroup", "title": "No Real Group", "username": "NoRealGroup"}, "from": {"id": 133869498, "is_bot": false, "first_name": "Vincent"}, "date": 1624119999, "old_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "member"}, "new_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "administrator"}}' From 8dcfa0c2826caa210f625ae918d011b4a092970f Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 12:52:41 +0500 Subject: [PATCH 13/19] Little fix for states --- examples/custom_states.py | 14 ++++++++++---- tests/.state-save/states.pkl | 1 + tests/test_types.py | 1 + 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 tests/.state-save/states.pkl diff --git a/examples/custom_states.py b/examples/custom_states.py index c7acfe558..3d16b5ad8 100644 --- a/examples/custom_states.py +++ b/examples/custom_states.py @@ -5,6 +5,12 @@ bot = telebot.TeleBot("") +class MyStates: + name = 1 + surname = 2 + age = 3 + + @bot.message_handler(commands=['start']) def start_ex(message): @@ -24,7 +30,7 @@ def any_state(message): bot.send_message(message.chat.id, "Your state was cancelled.") bot.delete_state(message.chat.id) -@bot.message_handler(state=1) +@bot.message_handler(state=MyStates.name) def name_get(message): """ State 1. Will process when user's state is 1. @@ -35,7 +41,7 @@ def name_get(message): data['name'] = message.text -@bot.message_handler(state=2) +@bot.message_handler(state=MyStates.surname) def ask_age(message): """ State 2. Will process when user's state is 2. @@ -46,14 +52,14 @@ def ask_age(message): data['surname'] = message.text # result -@bot.message_handler(state=3, is_digit=True) +@bot.message_handler(state=MyStates.age, is_digit=True) def ready_for_answer(message): with bot.retrieve_data(message.chat.id) as data: bot.send_message(message.chat.id, "Ready, take a look:\nName: {name}\nSurname: {surname}\nAge: {age}".format(name=data['name'], surname=data['surname'], age=message.text), parse_mode="html") bot.delete_state(message.chat.id) #incorrect number -@bot.message_handler(state=3, is_digit=False) +@bot.message_handler(state=MyStates.age, is_digit=False) def age_incorrect(message): bot.send_message(message.chat.id, 'Looks like you are submitting a string in the field age. Please enter a number') diff --git a/tests/.state-save/states.pkl b/tests/.state-save/states.pkl new file mode 100644 index 000000000..e2ecf720d --- /dev/null +++ b/tests/.state-save/states.pkl @@ -0,0 +1 @@ +€}”. \ No newline at end of file diff --git a/tests/test_types.py b/tests/test_types.py index d23f8fa3f..7f9b32fed 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -235,6 +235,7 @@ def test_json_chat_invite_link(): assert invite_link.pending_join_request_count == 1 assert invite_link.creates_join_request + def test_chat_member_updated(): json_string = r'{"chat": {"id": -1234567890123, "type": "supergroup", "title": "No Real Group", "username": "NoRealGroup"}, "from": {"id": 133869498, "is_bot": false, "first_name": "Vincent"}, "date": 1624119999, "old_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "member"}, "new_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "administrator"}}' cm_updated = types.ChatMemberUpdated.de_json(json_string) From fc347ae166f2c9aed80601a21383150f38b7384e Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 13:06:43 +0500 Subject: [PATCH 14/19] Update custom_states.py --- examples/custom_states.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/custom_states.py b/examples/custom_states.py index 3d16b5ad8..22691b244 100644 --- a/examples/custom_states.py +++ b/examples/custom_states.py @@ -17,7 +17,7 @@ def start_ex(message): """ Start command. Here we are starting state """ - bot.set_state(message.chat.id, 1) + bot.set_state(message.from_user.id, 1) bot.send_message(message.chat.id, 'Hi, write me a name') @@ -28,7 +28,7 @@ def any_state(message): Cancel state """ bot.send_message(message.chat.id, "Your state was cancelled.") - bot.delete_state(message.chat.id) + bot.delete_state(message.from_user.id) @bot.message_handler(state=MyStates.name) def name_get(message): @@ -36,8 +36,8 @@ def name_get(message): State 1. Will process when user's state is 1. """ bot.send_message(message.chat.id, f'Now write me a surname') - bot.set_state(message.chat.id, 2) - with bot.retrieve_data(message.chat.id) as data: + bot.set_state(message.from_user.id, 2) + with bot.retrieve_data(message.from_user.id) as data: data['name'] = message.text @@ -47,16 +47,16 @@ def ask_age(message): State 2. Will process when user's state is 2. """ bot.send_message(message.chat.id, "What is your age?") - bot.set_state(message.chat.id, 3) - with bot.retrieve_data(message.chat.id) as data: + bot.set_state(message.from_user.id, 3) + with bot.retrieve_data(message.from_user.id) as data: data['surname'] = message.text # result @bot.message_handler(state=MyStates.age, is_digit=True) def ready_for_answer(message): - with bot.retrieve_data(message.chat.id) as data: + with bot.retrieve_data(message.from_user.id) as data: bot.send_message(message.chat.id, "Ready, take a look:\nName: {name}\nSurname: {surname}\nAge: {age}".format(name=data['name'], surname=data['surname'], age=message.text), parse_mode="html") - bot.delete_state(message.chat.id) + bot.delete_state(message.from_user.id) #incorrect number @bot.message_handler(state=MyStates.age, is_digit=False) From 3a6073e3a0bcf9779eeab53a69aa49baf5de7392 Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 13:08:49 +0500 Subject: [PATCH 15/19] Update custom_states.py --- examples/custom_states.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/custom_states.py b/examples/custom_states.py index 22691b244..5acc8f2fd 100644 --- a/examples/custom_states.py +++ b/examples/custom_states.py @@ -17,7 +17,7 @@ def start_ex(message): """ Start command. Here we are starting state """ - bot.set_state(message.from_user.id, 1) + bot.set_state(message.from_user.id, MyStates.name) bot.send_message(message.chat.id, 'Hi, write me a name') @@ -36,7 +36,7 @@ def name_get(message): State 1. Will process when user's state is 1. """ bot.send_message(message.chat.id, f'Now write me a surname') - bot.set_state(message.from_user.id, 2) + bot.set_state(message.from_user.id, MyStates.surname) with bot.retrieve_data(message.from_user.id) as data: data['name'] = message.text @@ -47,7 +47,7 @@ def ask_age(message): State 2. Will process when user's state is 2. """ bot.send_message(message.chat.id, "What is your age?") - bot.set_state(message.from_user.id, 3) + bot.set_state(message.from_user.id, MyStates.age) with bot.retrieve_data(message.from_user.id) as data: data['surname'] = message.text From becce1f580d5d1bb1ca6c8ef84e497f420781223 Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 19:51:05 +0500 Subject: [PATCH 16/19] Update apihelper.py --- telebot/apihelper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index f66db8af6..3ae004d14 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -986,7 +986,7 @@ def create_chat_invite_link(token, chat_id, name, expire_date, member_limit, cre payload['expire_date'] = expire_date if member_limit: payload['member_limit'] = member_limit - if creates_join_request: + if creates_join_request is not None: payload['creates_join_request'] = creates_join_request if name: payload['name'] = name From 8003ff5e5937d1946a5f241c3763aa0d26c744aa Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 19:51:29 +0500 Subject: [PATCH 17/19] Delete states.pkl --- tests/.state-save/states.pkl | 1 - 1 file changed, 1 deletion(-) delete mode 100644 tests/.state-save/states.pkl diff --git a/tests/.state-save/states.pkl b/tests/.state-save/states.pkl deleted file mode 100644 index e2ecf720d..000000000 --- a/tests/.state-save/states.pkl +++ /dev/null @@ -1 +0,0 @@ -€}”. \ No newline at end of file From e412d2f08402da0b3e414419300f15a9dd2023a1 Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 19:56:10 +0500 Subject: [PATCH 18/19] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 54cc769cb..41ce353df 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ * [Poll Answer Handler](#poll-answer-handler) * [My Chat Member Handler](#my-chat-member-handler) * [Chat Member Handler](#chat-member-handler) + * [Chat Join request handler](#chat-join-request-handler) * [Inline Mode](#inline-mode) * [Inline handler](#inline-handler) * [Chosen Inline handler](#chosen-inline-handler) @@ -272,6 +273,10 @@ Handle updates of a chat member's status in a chat `@bot.chat_member_handler() # <- passes a ChatMemberUpdated type object to your function` *Note: "chat_member" updates are not requested by default. If you want to allow all update types, set `allowed_updates` in `bot.polling()` / `bot.infinity_polling()` to `util.update_types`* +#### Chat Join Request Handler +Handle chat join requests using: +`@bot.chat_join_request_handler() # <- passes ChatInviteLink type object to your function` + ### Inline Mode More information about [Inline mode](https://core.telegram.org/bots/inline). From 62b1ec04ab47c232697835e5567a9ab7b2c16b78 Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 6 Nov 2021 19:59:44 +0500 Subject: [PATCH 19/19] Update __init__.py --- telebot/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 0919f6d33..80b38188c 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -514,7 +514,7 @@ def process_new_updates(self, updates): if new_chat_members: self.process_new_chat_member(new_chat_members) if chat_join_request: - self.process_chat_join_request(chat_join_request) + self.process_new_chat_join_request(chat_join_request) def process_new_messages(self, new_messages): @@ -559,7 +559,7 @@ def process_new_my_chat_member(self, my_chat_members): def process_new_chat_member(self, chat_members): self._notify_command_handlers(self.chat_member_handlers, chat_members) - def process_chat_join_request(self, chat_join_request): + def process_new_chat_join_request(self, chat_join_request): self._notify_command_handlers(self.chat_join_request_handlers, chat_join_request) def process_middlewares(self, update):