From 49aee14fcaeb2fa17f6e58c7ae9ee3d993bdb8c7 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 19 May 2018 00:42:06 +0300 Subject: [PATCH 01/51] Make _test_filter method static and a bit clear doc strings --- telebot/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 8f9b60b..511cb73 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -984,7 +984,6 @@ class TeleBot: def get_sticker_set(self, name): """ Use this method to get a sticker set. On success, a StickerSet object is returned. - :param token: :param name: :return: """ @@ -1135,7 +1134,7 @@ class TeleBot: """ Clears all callback functions registered by register_for_reply() and register_for_reply_by_message_id(). - :param message_id: The message for which we want to clear reply handlers + :param message: The message for which we want to clear reply handlers """ message_id = message.message_id self.clear_reply_handlers_by_message_id(message_id) @@ -1328,7 +1327,8 @@ class TeleBot: return True - def _test_filter(self, filter, filter_value, message): + @staticmethod + def _test_filter(filter, filter_value, message): test_cases = { 'content_types': lambda msg: msg.content_type in filter_value, 'regexp': lambda msg: msg.content_type == 'text' and re.search(filter_value, msg.text, re.IGNORECASE), From e99fb8f84ffc5e0af98819b89b517f7f55336e1d Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 20 May 2018 23:40:25 +0300 Subject: [PATCH 02/51] Add methods to save (reply|next step) handlers [WIP] --- telebot/__init__.py | 85 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/telebot/__init__.py b/telebot/__init__.py index 8f9b60b..9b33dbc 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -7,6 +7,9 @@ import re import sys import six +import os +import json + import logging logger = logging.getLogger('TeleBot') @@ -85,6 +88,16 @@ class TeleBot: # key: chat_id, value: handler list self.next_step_handlers = {} + self.save_reply_handlers = False + self.reply_handlers_save_file = None + self.reply_save_delay = None + self.save_reply_timer = None + + self.save_step_handlers = False + self.step_handlers_save_file = None + self.step_save_delay = None + self.save_step_timer = None + self.message_handlers = [] self.edited_message_handlers = [] self.channel_post_handlers = [] @@ -99,6 +112,67 @@ class TeleBot: if self.threaded: self.worker_pool = util.ThreadPool(num_threads=num_threads) + @staticmethod + def dump_handlers(handlers, filename="./handler-saves/step.save", file_mode="w"): + dirs = filename.rsplit('/', maxsplit=1)[0] + os.makedirs(dirs, exist_ok=True) + with open(filename + ".tmp", file_mode) as file: + for mid, handler in handlers.items(): + name = handler['callback'].__name__ + module = handler['callback'].__module__ + + tmp = {str(mid): {"callback": {"module": module, "name": name}, "args": handler["args"], + "kwargs": handler["kwargs"]}} + + json.dump(tmp, file) + + if os.path.isfile(filename): + os.remove(filename) + + os.rename(filename + ".tmp", filename) + + def save_next_step_handlers(self): + self.dump_handlers(self.next_step_handlers, self.step_handlers_save_file) + + def save_reply_handlers_method(self): + self.dump_handlers(self.next_step_handlers, self.step_handlers_save_file) + + def start_save_next_step_timer(self): + if self.save_step_timer.is_alive(): return + + self.save_step_timer.start() + + def start_save_reply_timer(self): + if self.save_reply_timer.is_alive(): return + + self.save_reply_timer.start() + + def enable_save_next_step_handlers(self, delay=120, filename="./handler-saves/step.save"): + self.save_step_handlers = True + self.step_handlers_save_file = filename + self.step_save_delay = delay + self.save_step_timer = threading.Timer(self.step_save_delay, self.save_next_step_handlers) + + def enable_save_reply_handlers(self, delay=120, filename="./handler-saves/reply.save"): + self.save_reply_handlers = True + self.reply_handlers_save_file = filename + self.reply_save_delay = delay + self.save_reply_timer = threading.Timer(self.reply_save_delay, self.save_reply_handlers_method) + + @staticmethod + def load_handlers(filename): + if os.path.isfile(filename) and os.path.getsize(filename) > 0: + with open(filename, "r") as file: + handlers = json.load(file) + + return handlers + + def load_next_step_handlers(self, filename="./handler-saves/step.save"): + self.next_step_handlers.update(self.load_handlers(filename)) + + def load_reply_handlers(self, filename="./handler-saves/reply.save"): + self.reply_handlers.update(self.load_handlers(filename)) + def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None): return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates) @@ -1078,6 +1152,8 @@ class TeleBot: else: self.reply_handlers[message_id] = [{"callback": callback, "args": args, "kwargs": kwargs}] + self.start_save_reply_timer() + def _notify_reply_handlers(self, new_messages): for message in new_messages: if hasattr(message, "reply_to_message") and message.reply_to_message is not None: @@ -1087,6 +1163,7 @@ class TeleBot: for handler in handlers: self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) self.reply_handlers.pop(reply_msg_id) + self.start_save_reply_timer() def register_next_step_handler(self, message, callback, *args, **kwargs): """ @@ -1114,6 +1191,8 @@ class TeleBot: else: self.next_step_handlers[chat_id] = [{"callback": callback, "args": args, "kwargs": kwargs}] + self.start_save_next_step_timer() + def clear_step_handler(self, message): """ Clears all callback functions registered by register_next_step_handler(). @@ -1131,6 +1210,8 @@ class TeleBot: """ self.next_step_handlers[chat_id] = [] + self.start_save_next_step_timer() + def clear_reply_handlers(self, message): """ Clears all callback functions registered by register_for_reply() and register_for_reply_by_message_id(). @@ -1148,6 +1229,8 @@ class TeleBot: """ self.reply_handlers[message_id] = [] + self.start_save_next_step_timer() + def _notify_next_handlers(self, new_messages): i = 0 while i < len(new_messages): @@ -1161,6 +1244,8 @@ class TeleBot: new_messages.pop(i) # removing message that detects with next_step_handler i += 1 + self.start_save_next_step_timer() + @staticmethod def _build_handler_dict(handler, **filters): return { From ed7e33b4c65a6a0d73edb56861cd7b742aae289d Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 May 2018 02:48:46 +0300 Subject: [PATCH 03/51] Fix loadings funcs --- telebot/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 9b33dbc..a877b69 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -113,7 +113,7 @@ class TeleBot: self.worker_pool = util.ThreadPool(num_threads=num_threads) @staticmethod - def dump_handlers(handlers, filename="./handler-saves/step.save", file_mode="w"): + def dump_handlers(handlers, filename, file_mode="w"): dirs = filename.rsplit('/', maxsplit=1)[0] os.makedirs(dirs, exist_ok=True) with open(filename + ".tmp", file_mode) as file: @@ -165,6 +165,12 @@ class TeleBot: with open(filename, "r") as file: handlers = json.load(file) + for handler in handlers: + name = handler["callback"]["name"] + module = handler["callback"]["module"] + + handler = getattr(sys.modules[module], name) + return handlers def load_next_step_handlers(self, filename="./handler-saves/step.save"): From 00c8dcc19b966d842a4e6437e7460832820492ac Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 May 2018 03:10:40 +0300 Subject: [PATCH 04/51] Add async methods --- telebot/__init__.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/telebot/__init__.py b/telebot/__init__.py index a877b69..541a844 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1441,6 +1441,48 @@ class AsyncTeleBot(TeleBot): def __init__(self, *args, **kwargs): TeleBot.__init__(self, *args, **kwargs) + @staticmethod + @util.async() + def dump_handlers(handlers, filename, file_mode="w"): + return TeleBot.dump_handlers(handlers, filename, file_mode) + + @util.async() + def save_next_step_handlers(self): + return TeleBot.save_next_step_handlers(self) + + @util.async() + def save_reply_handlers_method(self): + return TeleBot.save_reply_handlers_method(self) + + @util.async() + def start_save_next_step_timer(self): + return TeleBot.start_save_next_step_timer(self) + + @util.async() + def start_save_reply_timer(self): + return TeleBot.start_save_reply_timer(self) + + @util.async() + def enable_save_next_step_handlers(self, delay=120, filename="./handler-saves/step.save"): + return TeleBot.enable_save_next_step_handlers(self, delay, filename) + + @util.async() + def enable_save_reply_handlers(self, delay=120, filename="./handler-saves/reply.save"): + return TeleBot.enable_save_reply_handlers(self, delay, filename) + + @staticmethod + @util.async() + def load_handlers(filename): + return TeleBot.load_handlers(filename) + + @util.async() + def load_next_step_handlers(self, filename="./handler-saves/step.save"): + return TeleBot.load_next_step_handlers(self, filename) + + @util.async() + def load_reply_handlers(self, filename="./handler-saves/reply.save"): + return TeleBot.load_reply_handlers(self, filename) + @util.async() def get_me(self): return TeleBot.get_me(self) From b1d5cb2129d8b44838ab8ce3984e6320b71157ad Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 May 2018 08:56:31 +0300 Subject: [PATCH 05/51] Rewrite. Add class 'Saver' that provides methods for saving (next step|reply) handlers. Add methods enable_save_next_step_handlers, enable_save_reply_handlers, disable_save_next_step_handlers, disable_save_reply_handlers, load_next_step_handlers, load_reply_handlers to Telebot and AsyncTelebot. update telebot/__init__.py --- telebot/__init__.py | 196 ++++++++++++++++++++++---------------------- 1 file changed, 100 insertions(+), 96 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 541a844..73ab328 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -30,6 +30,84 @@ Module : telebot """ +class Saver: + def __init__(self, handlers, filename, delay): + self.handlers = handlers + self.filename = filename + self.delay = delay + self.timer = threading.Timer(delay, self.save_handlers) + + def start_save_timer(self): + if not self.timer.is_alive(): + self.timer = threading.Timer(self.delay, self.save_handlers) + self.timer.start() + + def save_handlers(self): + self.dump_handlers(self.handlers, self.filename) + + def load_handlers(self, filename): + tmp = self.return_load_handlers(filename) + if tmp is not None: + self.handlers.update(tmp) + + @staticmethod + def dump_handlers(handlers, filename, file_mode="w"): + dirs = filename.rsplit('/', maxsplit=1)[0] + os.makedirs(dirs, exist_ok=True) + to_dump = {} + with open(filename + ".tmp", file_mode) as file: + for id_, handlers_ in handlers.items(): + for handler in handlers_: + name = handler['callback'].__name__ + module = handler['callback'].__module__ + + tmp = {"callback": {"module": module, "name": name}, "args": handler["args"], + "kwargs": handler["kwargs"]} + if id_ in to_dump.keys(): + to_dump[id_].append(tmp) + else: + to_dump[id_] = [tmp] + + json.dump(to_dump, file) + + if os.path.isfile(filename): + os.remove(filename) + + os.rename(filename + ".tmp", filename) + + @staticmethod + def return_load_handlers(filename): + if os.path.isfile(filename) and os.path.getsize(filename) > 0: + with open(filename, "r") as file: + handlers = json.load(file) + + result = {} + for id_, handlers_ in handlers.items(): + for handler in handlers_: + name = handler['callback']["name"] + module = handler['callback']["module"] + callback = getattr(sys.modules[module], name) + + tmp = {"callback": callback, "args": handler["args"], "kwargs": handler["kwargs"]} + + if int(id_) in result.keys(): + result[int(id_)].append(tmp) + else: + result[int(id_)] = [tmp] + + a = """ for key, handlers_ in handlers.items(): + for handler in handlers_: + name = handler["callback"]["name"] + module = handler["callback"]["module"] + + callback = getattr(sys.modules["__main__"], "next_") + handler["callback"] = callback""" + + return result + + + + class TeleBot: """ This is TeleBot Class Methods: @@ -88,15 +166,8 @@ class TeleBot: # key: chat_id, value: handler list self.next_step_handlers = {} - self.save_reply_handlers = False - self.reply_handlers_save_file = None - self.reply_save_delay = None - self.save_reply_timer = None - - self.save_step_handlers = False - self.step_handlers_save_file = None - self.step_save_delay = None - self.save_step_timer = None + self.next_step_saver = None + self.reply_saver = None self.message_handlers = [] self.edited_message_handlers = [] @@ -112,72 +183,23 @@ class TeleBot: if self.threaded: self.worker_pool = util.ThreadPool(num_threads=num_threads) - @staticmethod - def dump_handlers(handlers, filename, file_mode="w"): - dirs = filename.rsplit('/', maxsplit=1)[0] - os.makedirs(dirs, exist_ok=True) - with open(filename + ".tmp", file_mode) as file: - for mid, handler in handlers.items(): - name = handler['callback'].__name__ - module = handler['callback'].__module__ - - tmp = {str(mid): {"callback": {"module": module, "name": name}, "args": handler["args"], - "kwargs": handler["kwargs"]}} - - json.dump(tmp, file) - - if os.path.isfile(filename): - os.remove(filename) - - os.rename(filename + ".tmp", filename) - - def save_next_step_handlers(self): - self.dump_handlers(self.next_step_handlers, self.step_handlers_save_file) - - def save_reply_handlers_method(self): - self.dump_handlers(self.next_step_handlers, self.step_handlers_save_file) - - def start_save_next_step_timer(self): - if self.save_step_timer.is_alive(): return - - self.save_step_timer.start() - - def start_save_reply_timer(self): - if self.save_reply_timer.is_alive(): return - - self.save_reply_timer.start() - def enable_save_next_step_handlers(self, delay=120, filename="./handler-saves/step.save"): - self.save_step_handlers = True - self.step_handlers_save_file = filename - self.step_save_delay = delay - self.save_step_timer = threading.Timer(self.step_save_delay, self.save_next_step_handlers) + self.next_step_saver = Saver(self.next_step_handlers, filename, delay) def enable_save_reply_handlers(self, delay=120, filename="./handler-saves/reply.save"): - self.save_reply_handlers = True - self.reply_handlers_save_file = filename - self.reply_save_delay = delay - self.save_reply_timer = threading.Timer(self.reply_save_delay, self.save_reply_handlers_method) + self.reply_saver = Saver(self.reply_handlers, filename, delay) - @staticmethod - def load_handlers(filename): - if os.path.isfile(filename) and os.path.getsize(filename) > 0: - with open(filename, "r") as file: - handlers = json.load(file) + def disable_save_next_step_handlers(self): + self.next_step_saver = None - for handler in handlers: - name = handler["callback"]["name"] - module = handler["callback"]["module"] - - handler = getattr(sys.modules[module], name) - - return handlers + def disable_save_reply_handlers(self): + self.reply_saver = None def load_next_step_handlers(self, filename="./handler-saves/step.save"): - self.next_step_handlers.update(self.load_handlers(filename)) + self.next_step_saver.load_handlers(filename) def load_reply_handlers(self, filename="./handler-saves/reply.save"): - self.reply_handlers.update(self.load_handlers(filename)) + self.reply_saver.load_handlers(filename) def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None): return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates) @@ -1158,7 +1180,7 @@ class TeleBot: else: self.reply_handlers[message_id] = [{"callback": callback, "args": args, "kwargs": kwargs}] - self.start_save_reply_timer() + self.reply_saver.start_save_timer() def _notify_reply_handlers(self, new_messages): for message in new_messages: @@ -1169,7 +1191,7 @@ class TeleBot: for handler in handlers: self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) self.reply_handlers.pop(reply_msg_id) - self.start_save_reply_timer() + self.reply_saver.start_save_timer() def register_next_step_handler(self, message, callback, *args, **kwargs): """ @@ -1197,7 +1219,7 @@ class TeleBot: else: self.next_step_handlers[chat_id] = [{"callback": callback, "args": args, "kwargs": kwargs}] - self.start_save_next_step_timer() + self.next_step_saver.start_save_timer() def clear_step_handler(self, message): """ @@ -1216,7 +1238,7 @@ class TeleBot: """ self.next_step_handlers[chat_id] = [] - self.start_save_next_step_timer() + self.next_step_saver.start_save_timer() def clear_reply_handlers(self, message): """ @@ -1235,7 +1257,7 @@ class TeleBot: """ self.reply_handlers[message_id] = [] - self.start_save_next_step_timer() + self.reply_saver.start_save_timer() def _notify_next_handlers(self, new_messages): i = 0 @@ -1250,7 +1272,7 @@ class TeleBot: new_messages.pop(i) # removing message that detects with next_step_handler i += 1 - self.start_save_next_step_timer() + self.next_step_saver.start_save_timer() @staticmethod def _build_handler_dict(handler, **filters): @@ -1441,27 +1463,6 @@ class AsyncTeleBot(TeleBot): def __init__(self, *args, **kwargs): TeleBot.__init__(self, *args, **kwargs) - @staticmethod - @util.async() - def dump_handlers(handlers, filename, file_mode="w"): - return TeleBot.dump_handlers(handlers, filename, file_mode) - - @util.async() - def save_next_step_handlers(self): - return TeleBot.save_next_step_handlers(self) - - @util.async() - def save_reply_handlers_method(self): - return TeleBot.save_reply_handlers_method(self) - - @util.async() - def start_save_next_step_timer(self): - return TeleBot.start_save_next_step_timer(self) - - @util.async() - def start_save_reply_timer(self): - return TeleBot.start_save_reply_timer(self) - @util.async() def enable_save_next_step_handlers(self, delay=120, filename="./handler-saves/step.save"): return TeleBot.enable_save_next_step_handlers(self, delay, filename) @@ -1470,10 +1471,13 @@ class AsyncTeleBot(TeleBot): def enable_save_reply_handlers(self, delay=120, filename="./handler-saves/reply.save"): return TeleBot.enable_save_reply_handlers(self, delay, filename) - @staticmethod @util.async() - def load_handlers(filename): - return TeleBot.load_handlers(filename) + def disable_save_next_step_handlers(self): + return TeleBot.disable_save_next_step_handlers(self) + + @util.async() + def disable_save_reply_handlers(self): + return TeleBot.enable_save_reply_handlers(self) @util.async() def load_next_step_handlers(self, filename="./handler-saves/step.save"): From 4bcfc34a50f2096d1729711e71c0eba2aabb7734 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 May 2018 09:07:59 +0300 Subject: [PATCH 06/51] Update _notify_next_handlers and _notify_reply_handlers methods: Now if there wasn't any handler updates, timer willn't start. --- telebot/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 73ab328..f08ebb4 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1191,7 +1191,7 @@ class TeleBot: for handler in handlers: self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) self.reply_handlers.pop(reply_msg_id) - self.reply_saver.start_save_timer() + self.reply_saver.start_save_timer() def register_next_step_handler(self, message, callback, *args, **kwargs): """ @@ -1270,9 +1270,9 @@ class TeleBot: self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) self.next_step_handlers.pop(chat_id, None) new_messages.pop(i) # removing message that detects with next_step_handler + self.next_step_saver.start_save_timer() i += 1 - self.next_step_saver.start_save_timer() @staticmethod def _build_handler_dict(handler, **filters): From 4facc5f7d736e42e52e5bb532e1cbad028205004 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 May 2018 09:30:10 +0300 Subject: [PATCH 07/51] fix unenabled saving handlers. Updated telebot/__init__.py --- telebot/__init__.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index f08ebb4..ab87189 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1179,8 +1179,8 @@ class TeleBot: self.reply_handlers[message_id].append({"callback": callback, "args": args, "kwargs": kwargs}) else: self.reply_handlers[message_id] = [{"callback": callback, "args": args, "kwargs": kwargs}] - - self.reply_saver.start_save_timer() + if self.reply_saver is not None: + self.reply_saver.start_save_timer() def _notify_reply_handlers(self, new_messages): for message in new_messages: @@ -1191,7 +1191,8 @@ class TeleBot: for handler in handlers: self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) self.reply_handlers.pop(reply_msg_id) - self.reply_saver.start_save_timer() + if self.reply_saver is not None: + self.reply_saver.start_save_timer() def register_next_step_handler(self, message, callback, *args, **kwargs): """ @@ -1219,7 +1220,8 @@ class TeleBot: else: self.next_step_handlers[chat_id] = [{"callback": callback, "args": args, "kwargs": kwargs}] - self.next_step_saver.start_save_timer() + if self.next_step_saver is not None: + self.next_step_saver.start_save_timer() def clear_step_handler(self, message): """ @@ -1238,7 +1240,8 @@ class TeleBot: """ self.next_step_handlers[chat_id] = [] - self.next_step_saver.start_save_timer() + if self.next_step_saver is not None: + self.next_step_saver.start_save_timer() def clear_reply_handlers(self, message): """ @@ -1257,7 +1260,8 @@ class TeleBot: """ self.reply_handlers[message_id] = [] - self.reply_saver.start_save_timer() + if self.reply_saver is not None: + self.reply_saver.start_save_timer() def _notify_next_handlers(self, new_messages): i = 0 @@ -1270,7 +1274,8 @@ class TeleBot: self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) self.next_step_handlers.pop(chat_id, None) new_messages.pop(i) # removing message that detects with next_step_handler - self.next_step_saver.start_save_timer() + if self.next_step_saver is not None: + self.next_step_saver.start_save_timer() i += 1 From 7df6b3d4c98167cbed95bf6cfe2cdff88481dd9f Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 May 2018 09:35:39 +0300 Subject: [PATCH 08/51] Fix situation where delay <= 0. Update telebot/__init__.py --- telebot/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index ab87189..59ae6ff 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -39,8 +39,11 @@ class Saver: def start_save_timer(self): if not self.timer.is_alive(): - self.timer = threading.Timer(self.delay, self.save_handlers) - self.timer.start() + if self.delay <= 0: + self.save_handlers() + else: + self.timer = threading.Timer(self.delay, self.save_handlers) + self.timer.start() def save_handlers(self): self.dump_handlers(self.handlers, self.filename) From 018e4597a2d6b6e777c19d475b90186fa1bc07f2 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 May 2018 09:40:29 +0300 Subject: [PATCH 09/51] Add del_file_after_loading param to Saver.return_load_handlers and Saver.load_handlers methods. Update telebot/__init__.py --- telebot/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 59ae6ff..f89c339 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -48,8 +48,8 @@ class Saver: def save_handlers(self): self.dump_handlers(self.handlers, self.filename) - def load_handlers(self, filename): - tmp = self.return_load_handlers(filename) + def load_handlers(self, filename, del_file_after_loading=True): + tmp = self.return_load_handlers(filename, del_file_after_loading=del_file_after_loading) if tmp is not None: self.handlers.update(tmp) @@ -79,7 +79,7 @@ class Saver: os.rename(filename + ".tmp", filename) @staticmethod - def return_load_handlers(filename): + def return_load_handlers(filename, del_file_after_loading=True): if os.path.isfile(filename) and os.path.getsize(filename) > 0: with open(filename, "r") as file: handlers = json.load(file) @@ -105,7 +105,8 @@ class Saver: callback = getattr(sys.modules["__main__"], "next_") handler["callback"] = callback""" - + if del_file_after_loading: + os.remove(filename) return result From 7e5f51e4ab6436c8f989b04ad7d16d5f2e175c77 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 May 2018 09:44:43 +0300 Subject: [PATCH 10/51] Remove old thing. Update telebot/__init__.py --- telebot/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index f89c339..06bbe30 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -98,13 +98,6 @@ class Saver: else: result[int(id_)] = [tmp] - a = """ for key, handlers_ in handlers.items(): - for handler in handlers_: - name = handler["callback"]["name"] - module = handler["callback"]["module"] - - callback = getattr(sys.modules["__main__"], "next_") - handler["callback"] = callback""" if del_file_after_loading: os.remove(filename) return result From b989b7601b9f53c3ad630072d971a92865cb3831 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 May 2018 20:57:22 +0300 Subject: [PATCH 11/51] Add new class: Handler Change type of (next step|reply) handlers from dict to Handler [WIP] update: telebot/__init__.py --- telebot/__init__.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index ca830e5..f15d8d7 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -30,6 +30,25 @@ Module : telebot """ +class Handler: + def __init__(self, callback: {dict, function}, *args, **kwargs): + if type(callback) == dict: + self.callback = getattr(sys.modules[callback["module"]], callback["name"]) + else: + self.callback = callback + + self.args = args + self.kwargs = kwargs + + def __getitem__(self, item): + return getattr(self, item) + + def copy_to_dump(self): + module_ = self.callback.__module__ + name = self.callback.__name__ + return Handler({"module": module_, "name": name}, *self.args, **self.kwargs) + + class Saver: def __init__(self, handlers, filename, delay): self.handlers = handlers @@ -61,15 +80,10 @@ class Saver: with open(filename + ".tmp", file_mode) as file: for id_, handlers_ in handlers.items(): for handler in handlers_: - name = handler['callback'].__name__ - module = handler['callback'].__module__ - - tmp = {"callback": {"module": module, "name": name}, "args": handler["args"], - "kwargs": handler["kwargs"]} if id_ in to_dump.keys(): - to_dump[id_].append(tmp) + to_dump[id_].append(handler.copy_to_dump()) else: - to_dump[id_] = [tmp] + to_dump[id_] = [handler.copy_to_dump()] json.dump(to_dump, file) @@ -87,11 +101,8 @@ class Saver: result = {} for id_, handlers_ in handlers.items(): for handler in handlers_: - name = handler['callback']["name"] - module = handler['callback']["module"] - callback = getattr(sys.modules[module], name) - tmp = {"callback": callback, "args": handler["args"], "kwargs": handler["kwargs"]} + tmp = Handler(handler['callback'], handler["args"], handler["kwargs"]) if int(id_) in result.keys(): result[int(id_)].append(tmp) @@ -1172,9 +1183,9 @@ class TeleBot: parameter, which will contain the replied message. """ if message_id in self.reply_handlers.keys(): - self.reply_handlers[message_id].append({"callback": callback, "args": args, "kwargs": kwargs}) + self.reply_handlers[message_id].append(Handler(callback, *args, **kwargs)) else: - self.reply_handlers[message_id] = [{"callback": callback, "args": args, "kwargs": kwargs}] + self.reply_handlers[message_id] = [Handler(callback, *args, **kwargs)] if self.reply_saver is not None: self.reply_saver.start_save_timer() @@ -1212,9 +1223,9 @@ class TeleBot: :param kwargs: Args to pass in callback func """ if chat_id in self.next_step_handlers.keys(): - self.next_step_handlers[chat_id].append({"callback": callback, "args": args, "kwargs": kwargs}) + self.next_step_handlers[chat_id].append(Handler(callback, *args, **kwargs)) else: - self.next_step_handlers[chat_id] = [{"callback": callback, "args": args, "kwargs": kwargs}] + self.next_step_handlers[chat_id] = [Handler(callback, *args, **kwargs)] if self.next_step_saver is not None: self.next_step_saver.start_save_timer() From 17971ff48b39f2a4695829bc9c776c0a711d57bc Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 May 2018 12:19:01 +0300 Subject: [PATCH 12/51] Move from json to pickle. Update: relebot/__init__.py --- telebot/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index f15d8d7..838bbd1 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -9,6 +9,7 @@ import six import os import json +import pickle import logging @@ -31,7 +32,7 @@ Module : telebot class Handler: - def __init__(self, callback: {dict, function}, *args, **kwargs): + def __init__(self, callback: {dict, 'function'}, *args, **kwargs): if type(callback) == dict: self.callback = getattr(sys.modules[callback["module"]], callback["name"]) else: @@ -73,7 +74,7 @@ class Saver: self.handlers.update(tmp) @staticmethod - def dump_handlers(handlers, filename, file_mode="w"): + def dump_handlers(handlers, filename, file_mode="wb"): dirs = filename.rsplit('/', maxsplit=1)[0] os.makedirs(dirs, exist_ok=True) to_dump = {} @@ -85,7 +86,8 @@ class Saver: else: to_dump[id_] = [handler.copy_to_dump()] - json.dump(to_dump, file) + # json.dump(to_dump, file) + pickle.dump(to_dump, file) if os.path.isfile(filename): os.remove(filename) @@ -95,14 +97,14 @@ class Saver: @staticmethod def return_load_handlers(filename, del_file_after_loading=True): if os.path.isfile(filename) and os.path.getsize(filename) > 0: - with open(filename, "r") as file: - handlers = json.load(file) - + with open(filename, "rb") as file: + # handlers = json.load(file) + handlers = pickle.load(file) result = {} for id_, handlers_ in handlers.items(): for handler in handlers_: - tmp = Handler(handler['callback'], handler["args"], handler["kwargs"]) + tmp = Handler(handler['callback'], *handler["args"], **handler["kwargs"]) if int(id_) in result.keys(): result[int(id_)].append(tmp) From 3c890a7846abbd7113f9eef92e8d9e6ecde12a73 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 May 2018 16:37:25 +0300 Subject: [PATCH 13/51] Remove 2 spaces up to TeleBot class. Update: telebot/__init__.py --- telebot/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 838bbd1..14cfe3f 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -116,8 +116,6 @@ class Saver: return result - - class TeleBot: """ This is TeleBot Class Methods: From 47e6dfd6bce65d5ee8dbbc6930107cfcc203ca99 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 May 2018 16:52:30 +0300 Subject: [PATCH 14/51] Remove rudiment json things Update: telebot/__init__.py --- telebot/__init__.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 14cfe3f..2f9d1c9 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -8,7 +8,6 @@ import sys import six import os -import json import pickle import logging @@ -86,7 +85,6 @@ class Saver: else: to_dump[id_] = [handler.copy_to_dump()] - # json.dump(to_dump, file) pickle.dump(to_dump, file) if os.path.isfile(filename): @@ -98,7 +96,6 @@ class Saver: 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: - # handlers = json.load(file) handlers = pickle.load(file) result = {} for id_, handlers_ in handlers.items(): @@ -106,10 +103,10 @@ class Saver: tmp = Handler(handler['callback'], *handler["args"], **handler["kwargs"]) - if int(id_) in result.keys(): - result[int(id_)].append(tmp) + if id_ in result.keys(): + result[id_].append(tmp) else: - result[int(id_)] = [tmp] + result[id_] = [tmp] if del_file_after_loading: os.remove(filename) From 1de356dcc337751d0ae25a3dbf82aaec401d8a08 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 May 2018 17:10:00 +0300 Subject: [PATCH 15/51] Change default save directory to "./.handler-saves/". Add del_file_after_loading param to load methods. Update: telebot/__init__.py --- telebot/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 2f9d1c9..ce3963e 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -188,10 +188,10 @@ class TeleBot: if self.threaded: self.worker_pool = util.ThreadPool(num_threads=num_threads) - def enable_save_next_step_handlers(self, delay=120, filename="./handler-saves/step.save"): + def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save", del_file_after_loading=True): self.next_step_saver = Saver(self.next_step_handlers, filename, delay) - def enable_save_reply_handlers(self, delay=120, filename="./handler-saves/reply.save"): + def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save", del_file_after_loading=True): self.reply_saver = Saver(self.reply_handlers, filename, delay) def disable_save_next_step_handlers(self): @@ -200,10 +200,10 @@ class TeleBot: def disable_save_reply_handlers(self): self.reply_saver = None - def load_next_step_handlers(self, filename="./handler-saves/step.save"): + def load_next_step_handlers(self, filename="./.handler-saves/step.save"): self.next_step_saver.load_handlers(filename) - def load_reply_handlers(self, filename="./handler-saves/reply.save"): + def load_reply_handlers(self, filename="./.handler-saves/reply.save"): self.reply_saver.load_handlers(filename) def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None): @@ -1474,12 +1474,12 @@ class AsyncTeleBot(TeleBot): TeleBot.__init__(self, *args, **kwargs) @util.async() - def enable_save_next_step_handlers(self, delay=120, filename="./handler-saves/step.save"): - return TeleBot.enable_save_next_step_handlers(self, delay, filename) + def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save", del_file_after_loading=True): + return TeleBot.enable_save_next_step_handlers(self, delay, filename, del_file_after_loading) @util.async() - def enable_save_reply_handlers(self, delay=120, filename="./handler-saves/reply.save"): - return TeleBot.enable_save_reply_handlers(self, delay, filename) + def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save", del_file_after_loading=True): + return TeleBot.enable_save_reply_handlers(self, delay, filename, del_file_after_loading) @util.async() def disable_save_next_step_handlers(self): @@ -1490,11 +1490,11 @@ class AsyncTeleBot(TeleBot): return TeleBot.enable_save_reply_handlers(self) @util.async() - def load_next_step_handlers(self, filename="./handler-saves/step.save"): + def load_next_step_handlers(self, filename="./.handler-saves/step.save"): return TeleBot.load_next_step_handlers(self, filename) @util.async() - def load_reply_handlers(self, filename="./handler-saves/reply.save"): + def load_reply_handlers(self, filename="./.handler-saves/reply.save"): return TeleBot.load_reply_handlers(self, filename) @util.async() From 13df7b59081411bd7f9e5ce05ee9357e35e4845c Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 May 2018 17:21:39 +0300 Subject: [PATCH 16/51] Add enable_save_next_step_handlers and load_next_step_handlers methods to step_example/ Update: examples/step_example.py --- examples/step_example.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/examples/step_example.py b/examples/step_example.py index fd8d07d..0fc0ef1 100644 --- a/examples/step_example.py +++ b/examples/step_example.py @@ -75,4 +75,14 @@ def process_sex_step(message): bot.reply_to(message, 'oooops') +# Enable saving next step handlers to file "./.handlers-saves/step.save". +# Delay=2 means that after any change in next step handlers (e.g. calling register_next_step_handler()) +# saving will hapen after delay 2 seconds. +bot.enable_save_next_step_handlers(delay=2) + +# Load next_step_handlers from save file (default "./.handlers-saves/step.save") +# WARNING It will work only if enable_save_next_step_handlers was called! +bot.load_next_step_handlers() + + bot.polling() From d61de35a32272e31843d8bad3b3f37dba073907e Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 May 2018 01:23:20 +0300 Subject: [PATCH 17/51] Remove rudiment json things, again! Update: telebot/__init__.py --- telebot/__init__.py | 40 +++++++++------------------------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index ce3963e..9669d87 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -31,23 +31,17 @@ Module : telebot class Handler: - def __init__(self, callback: {dict, 'function'}, *args, **kwargs): - if type(callback) == dict: - self.callback = getattr(sys.modules[callback["module"]], callback["name"]) - else: - self.callback = callback - + """ + Class for (next step|reply) handlers + """ + def __init__(self, callback: 'function', *args, **kwargs): + self.callback = callback self.args = args self.kwargs = kwargs def __getitem__(self, item): return getattr(self, item) - def copy_to_dump(self): - module_ = self.callback.__module__ - name = self.callback.__name__ - return Handler({"module": module_, "name": name}, *self.args, **self.kwargs) - class Saver: def __init__(self, handlers, filename, delay): @@ -76,16 +70,9 @@ class Saver: def dump_handlers(handlers, filename, file_mode="wb"): dirs = filename.rsplit('/', maxsplit=1)[0] os.makedirs(dirs, exist_ok=True) - to_dump = {} - with open(filename + ".tmp", file_mode) as file: - for id_, handlers_ in handlers.items(): - for handler in handlers_: - if id_ in to_dump.keys(): - to_dump[id_].append(handler.copy_to_dump()) - else: - to_dump[id_] = [handler.copy_to_dump()] - pickle.dump(to_dump, file) + with open(filename + ".tmp", file_mode) as file: + pickle.dump(handlers, file) if os.path.isfile(filename): os.remove(filename) @@ -97,20 +84,11 @@ class Saver: if os.path.isfile(filename) and os.path.getsize(filename) > 0: with open(filename, "rb") as file: handlers = pickle.load(file) - result = {} - for id_, handlers_ in handlers.items(): - for handler in handlers_: - - tmp = Handler(handler['callback'], *handler["args"], **handler["kwargs"]) - - if id_ in result.keys(): - result[id_].append(tmp) - else: - result[id_] = [tmp] if del_file_after_loading: os.remove(filename) - return result + + return handlers class TeleBot: From fa038c2e4286fdeefcc1678de8f26dab9ee32942 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 May 2018 01:30:14 +0300 Subject: [PATCH 18/51] Move del_file_after_loading param to right methods :face_palm: Update: telebot/__init__.py --- telebot/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 9669d87..c06b977 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -166,10 +166,10 @@ class TeleBot: if self.threaded: self.worker_pool = util.ThreadPool(num_threads=num_threads) - def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save", del_file_after_loading=True): + def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save"): self.next_step_saver = Saver(self.next_step_handlers, filename, delay) - def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save", del_file_after_loading=True): + def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"): self.reply_saver = Saver(self.reply_handlers, filename, delay) def disable_save_next_step_handlers(self): @@ -178,10 +178,10 @@ class TeleBot: def disable_save_reply_handlers(self): self.reply_saver = None - def load_next_step_handlers(self, filename="./.handler-saves/step.save"): - self.next_step_saver.load_handlers(filename) + def load_next_step_handlers(self, filename="./.handler-saves/step.save", del_file_after_loading=True): + self.next_step_saver.load_handlers(filename, del_file_after_loading) - def load_reply_handlers(self, filename="./.handler-saves/reply.save"): + def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True): self.reply_saver.load_handlers(filename) def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None): From 333949683f51018c21bc957a46d144d108cf888f Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 May 2018 01:37:06 +0300 Subject: [PATCH 19/51] Add doc strings to new TeleBot methods Update telebot/__init__.py --- telebot/__init__.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/telebot/__init__.py b/telebot/__init__.py index c06b977..422ac36 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -44,6 +44,9 @@ class Handler: class Saver: + """ + Class for saving (next step|reply) handlers + """ def __init__(self, handlers, filename, delay): self.handlers = handlers self.filename = filename @@ -167,21 +170,51 @@ class TeleBot: self.worker_pool = util.ThreadPool(num_threads=num_threads) def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save"): + """ + Enable saving next step handlers (by default saving disable) + + :param delay: Delay between changes in handlers and saving + :param filename: Filename of save file + """ self.next_step_saver = Saver(self.next_step_handlers, filename, delay) def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"): + """ + Enable saving reply handlers (by default saving disable) + + :param delay: Delay between changes in handlers and saving + :param filename: Filename of save file + """ self.reply_saver = Saver(self.reply_handlers, filename, delay) def disable_save_next_step_handlers(self): + """ + Disable saving next step handlers (by default saving disable) + """ self.next_step_saver = None def disable_save_reply_handlers(self): + """ + Disable saving next step handlers (by default saving disable) + """ self.reply_saver = None def load_next_step_handlers(self, filename="./.handler-saves/step.save", del_file_after_loading=True): + """ + Load next step handlers from save file + + :param filename: Filename of the file where handlers was saved + :param del_file_after_loading: Is passed True, after loading save file will be deleted + """ self.next_step_saver.load_handlers(filename, del_file_after_loading) def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True): + """ + Load reply handlers from save file + + :param filename: Filename of the file where handlers was saved + :param del_file_after_loading: Is passed True, after loading save file will be deleted + """ self.reply_saver.load_handlers(filename) def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None): From 424c77fd2c1077c538c1d032b11fbd87b1c8332a Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 May 2018 16:54:56 +0300 Subject: [PATCH 20/51] Remove type hint for 2.x and PyPy python compatibility --- telebot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 422ac36..ca7e92f 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -34,7 +34,7 @@ class Handler: """ Class for (next step|reply) handlers """ - def __init__(self, callback: 'function', *args, **kwargs): + def __init__(self, callback, *args, **kwargs): self.callback = callback self.args = args self.kwargs = kwargs From 909d570dca83baf40e4734db218596ca02341744 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 May 2018 17:01:07 +0300 Subject: [PATCH 21/51] Add warning about lambda functions in callbacks --- telebot/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/telebot/__init__.py b/telebot/__init__.py index ca7e92f..e52711a 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1172,6 +1172,8 @@ class TeleBot: Warning: `message` must be sent with reply_markup=types.ForceReply(), otherwise TeleBot will not be able to see the difference between a reply to `message` and an ordinary message. + Warning: In case `callback` as lambda function, saving reply handlers will not work. + :param message: The message for which we are awaiting a reply. :param callback: The callback function to be called when a reply arrives. Must accept one `message` parameter, which will contain the replied message. @@ -1186,6 +1188,8 @@ class TeleBot: Warning: `message` must be sent with reply_markup=types.ForceReply(), otherwise TeleBot will not be able to see the difference between a reply to `message` and an ordinary message. + Warning: In case `callback` as lambda function, saving reply handlers will not work. + :param message: The message for which we are awaiting a reply. :param callback: The callback function to be called when a reply arrives. Must accept one `message` parameter, which will contain the replied message. @@ -1213,6 +1217,8 @@ class TeleBot: """ Registers a callback function to be notified when new message arrives after `message`. + Warning: In case `callback` as lambda function, saving next step handlers will not work. + :param message: The message for which we want to handle new message in the same chat. :param callback: The callback function which next new message arrives. :param args: Args to pass in callback func @@ -1225,6 +1231,8 @@ class TeleBot: """ Registers a callback function to be notified when new message arrives after `message`. + Warning: In case `callback` as lambda function, saving next step handlers will not work. + :param chat_id: The chat for which we want to handle new message. :param callback: The callback function which next new message arrives. :param args: Args to pass in callback func From 893d5386c5721e18b310e118ddee87fd81d4d19e Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 May 2018 17:02:04 +0300 Subject: [PATCH 22/51] Fix register_for_reply_by_message_id doc strings. --- telebot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index e52711a..2545f5f 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1190,7 +1190,7 @@ class TeleBot: Warning: In case `callback` as lambda function, saving reply handlers will not work. - :param message: The message for which we are awaiting a reply. + :param message_id: The id of the message for which we are awaiting a reply. :param callback: The callback function to be called when a reply arrives. Must accept one `message` parameter, which will contain the replied message. """ From bc855f7610e777691d5e68bc38badc4324684843 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 May 2018 17:05:01 +0300 Subject: [PATCH 23/51] Fix register_for_reply_by_message_id and register_for_reply doc strings. --- telebot/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 2545f5f..4c4996a 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1169,9 +1169,6 @@ class TeleBot: """ Registers a callback function to be notified when a reply to `message` arrives. - Warning: `message` must be sent with reply_markup=types.ForceReply(), otherwise TeleBot will not be able to see - the difference between a reply to `message` and an ordinary message. - Warning: In case `callback` as lambda function, saving reply handlers will not work. :param message: The message for which we are awaiting a reply. @@ -1185,9 +1182,6 @@ class TeleBot: """ Registers a callback function to be notified when a reply to `message` arrives. - Warning: `message` must be sent with reply_markup=types.ForceReply(), otherwise TeleBot will not be able to see - the difference between a reply to `message` and an ordinary message. - Warning: In case `callback` as lambda function, saving reply handlers will not work. :param message_id: The id of the message for which we are awaiting a reply. From 2b822f782d8916492fd347f7d0970f296a01b9f3 Mon Sep 17 00:00:00 2001 From: Andru1999 Date: Sun, 22 Jul 2018 00:31:02 +1000 Subject: [PATCH 24/51] Update __init__.py I find bug when I use your library without threading. If call bot.register_next_step_handler in function that register next_handler in next_step_handlers but in function _notify_next_handlers this delete and bot don`t have handler, but in threading mode function self.next_step_handlers.pop(chat_id, None) has time to eval self.next_step_handlers.pop(chat_id, None) and bug disappear. Sorry for my English --- telebot/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index fb7770b..551f09f 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -6,6 +6,7 @@ import time import re import sys import six +import copy import logging @@ -1155,13 +1156,13 @@ class TeleBot: chat_id = message.chat.id was_poped = False if chat_id in self.next_step_handlers.keys(): - handlers = self.next_step_handlers[chat_id] + handlers = copy.deepcopy(self.next_step_handlers[chat_id]) + self.next_step_handlers.pop(chat_id, None) if (handlers): for handler in handlers: self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) new_messages.pop(i) # removing message that detects with next_step_handler was_poped = True - self.next_step_handlers.pop(chat_id, None) if (not was_poped): i += 1 From 44dd89881d37b760942eb6af94aed0d6c5ac81ee Mon Sep 17 00:00:00 2001 From: FrankWang Date: Thu, 26 Jul 2018 21:41:08 +0800 Subject: [PATCH 25/51] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2ea415f..52c49d3 100644 --- a/README.md +++ b/README.md @@ -584,5 +584,6 @@ Get help. Discuss. Chat. * [SmartySBot](http://t.me/ZDU_bot)([link](https://github.com/0xVK/SmartySBot)) by *0xVK* - Telegram timetable bot, for Zhytomyr Ivan Franko State University students. * [yandex_music_bot](http://t.me/yandex_music_bot)- Downloads tracks/albums/public playlists from Yandex.Music streaming service for free. * [LearnIt](https://t.me/LearnItbot)([link](https://github.com/tiagonapoli/LearnIt)) - A Telegram Bot created to help people to memorize other languages’ vocabulary. +* [MusicQuiz_bot](https://t.me/MusicQuiz_bot) by [Etoneja](https://github.com/Etoneja) - Music quiz game with big music database which was manually collected. Want to have your bot listed here? Send a Telegram message to @eternnoir or @pevdh. From 5c199bd24679af85a2e222178fcb28ebbf8f9e5e Mon Sep 17 00:00:00 2001 From: FrankWang Date: Thu, 26 Jul 2018 22:17:55 +0800 Subject: [PATCH 26/51] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52c49d3..e1ec2a8 100644 --- a/README.md +++ b/README.md @@ -584,6 +584,6 @@ Get help. Discuss. Chat. * [SmartySBot](http://t.me/ZDU_bot)([link](https://github.com/0xVK/SmartySBot)) by *0xVK* - Telegram timetable bot, for Zhytomyr Ivan Franko State University students. * [yandex_music_bot](http://t.me/yandex_music_bot)- Downloads tracks/albums/public playlists from Yandex.Music streaming service for free. * [LearnIt](https://t.me/LearnItbot)([link](https://github.com/tiagonapoli/LearnIt)) - A Telegram Bot created to help people to memorize other languages’ vocabulary. -* [MusicQuiz_bot](https://t.me/MusicQuiz_bot) by [Etoneja](https://github.com/Etoneja) - Music quiz game with big music database which was manually collected. +* [MusicQuiz_bot](https://t.me/MusicQuiz_bot) by [Etoneja](https://github.com/Etoneja) - Listen to audiosamles and try to name the performer of the song. Want to have your bot listed here? Send a Telegram message to @eternnoir or @pevdh. From 7061091c1cbf5ed9221103a3e783094c13910ee8 Mon Sep 17 00:00:00 2001 From: eternnoir Date: Tue, 31 Jul 2018 08:58:04 +0800 Subject: [PATCH 27/51] Update version. * Fix python 3.7 async --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1e76d03..01e10a1 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def readme(): return f.read() setup(name='pyTelegramBotAPI', - version='3.6.3', + version='3.6.4', description='Python Telegram bot api. ', long_description=readme(), author='eternnoir', From 5035e0ce80e86e24b489c99cd78a02fff280b717 Mon Sep 17 00:00:00 2001 From: heyyyoyy Date: Thu, 2 Aug 2018 21:15:33 +0300 Subject: [PATCH 28/51] Added parse mode for objects in Inline mode --- telebot/types.py | 70 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 5ca1b4e..27866d9 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -1191,7 +1191,7 @@ class InlineQueryResultArticle(JsonSerializable): class InlineQueryResultPhoto(JsonSerializable): def __init__(self, id, photo_url, thumb_url, photo_width=None, photo_height=None, title=None, - description=None, caption=None, reply_markup=None, input_message_content=None): + description=None, caption=None, parse_mode=None, reply_markup=None, input_message_content=None): """ Represents a link to a photo. :param id: Unique identifier for this result, 1-64 bytes @@ -1202,6 +1202,8 @@ class InlineQueryResultPhoto(JsonSerializable): :param title: Title for the result. :param description: Short description of the result. :param caption: Caption of the photo to be sent, 0-200 characters. + :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or + inline URLs in the media caption. :param reply_markup: InlineKeyboardMarkup : Inline keyboard attached to the message :param input_message_content: InputMessageContent : Content of the message to be sent instead of the photo :return: @@ -1215,6 +1217,7 @@ class InlineQueryResultPhoto(JsonSerializable): self.title = title self.description = description self.caption = caption + self.parse_mode = parse_mode self.reply_markup = reply_markup self.input_message_content = input_message_content @@ -1230,6 +1233,8 @@ class InlineQueryResultPhoto(JsonSerializable): json_dict['description'] = self.description if self.caption: json_dict['caption'] = self.caption + if self.parse_mode: + json_dict['parse_mode'] = self.parse_mode if self.reply_markup: json_dict['reply_markup'] = self.reply_markup.to_dic() if self.input_message_content: @@ -1286,7 +1291,7 @@ class InlineQueryResultGif(JsonSerializable): class InlineQueryResultMpeg4Gif(JsonSerializable): def __init__(self, id, mpeg4_url, thumb_url, mpeg4_width=None, mpeg4_height=None, title=None, caption=None, - reply_markup=None, input_message_content=None, mpeg4_duration=None): + parse_mode=None, reply_markup=None, input_message_content=None, mpeg4_duration=None): """ Represents a link to a video animation (H.264/MPEG-4 AVC video without sound). :param id: Unique identifier for this result, 1-64 bytes @@ -1296,6 +1301,8 @@ class InlineQueryResultMpeg4Gif(JsonSerializable): :param mpeg4_height: Video height :param title: Title for the result :param caption: Caption of the MPEG-4 file to be sent, 0-200 characters + :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text + or inline URLs in the media caption. :param reply_markup: InlineKeyboardMarkup : Inline keyboard attached to the message :param input_message_content: InputMessageContent : Content of the message to be sent instead of the photo :return: @@ -1308,6 +1315,7 @@ class InlineQueryResultMpeg4Gif(JsonSerializable): self.thumb_url = thumb_url self.title = title self.caption = caption + self.parse_mode = parse_mode self.reply_markup = reply_markup self.input_message_content = input_message_content self.mpeg4_duration = mpeg4_duration @@ -1322,6 +1330,8 @@ class InlineQueryResultMpeg4Gif(JsonSerializable): json_dict['title'] = self.title if self.caption: json_dict['caption'] = self.caption + if self.parse_mode: + json_dict['parse_mode'] = self.parse_mode if self.reply_markup: json_dict['reply_markup'] = self.reply_markup.to_dic() if self.input_message_content: @@ -1333,8 +1343,8 @@ class InlineQueryResultMpeg4Gif(JsonSerializable): class InlineQueryResultVideo(JsonSerializable): def __init__(self, id, video_url, mime_type, thumb_url, title, - caption=None, video_width=None, video_height=None, video_duration=None, description=None, - reply_markup=None, input_message_content=None): + caption=None, parse_mode=None, video_width=None, video_height=None, video_duration=None, + description=None, reply_markup=None, input_message_content=None): """ Represents link to a page containing an embedded video player or a video file. :param id: Unique identifier for this result, 1-64 bytes @@ -1342,6 +1352,8 @@ class InlineQueryResultVideo(JsonSerializable): :param mime_type: Mime type of the content of video url, “text/html” or “video/mp4” :param thumb_url: URL of the thumbnail (jpeg only) for the video :param title: Title for the result + :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or + inline URLs in the media caption. :param video_width: Video width :param video_height: Video height :param video_duration: Video duration in seconds @@ -1358,6 +1370,7 @@ class InlineQueryResultVideo(JsonSerializable): self.thumb_url = thumb_url self.title = title self.caption = caption + self.parse_mode = parse_mode self.description = description self.input_message_content = input_message_content self.reply_markup = reply_markup @@ -1375,6 +1388,8 @@ class InlineQueryResultVideo(JsonSerializable): json_dict['description'] = self.description if self.caption: json_dict['caption'] = self.caption + if self.parse_mode: + json_dict['parse_mode'] = self.parse_mode if self.reply_markup: json_dict['reply_markup'] = self.reply_markup.to_dic() if self.input_message_content: @@ -1383,13 +1398,14 @@ class InlineQueryResultVideo(JsonSerializable): class InlineQueryResultAudio(JsonSerializable): - def __init__(self, id, audio_url, title, caption=None, performer=None, audio_duration=None, reply_markup=None, - input_message_content=None): + def __init__(self, id, audio_url, title, caption=None, parse_mode=None, performer=None, audio_duration=None, + reply_markup=None, input_message_content=None): self.type = 'audio' self.id = id self.audio_url = audio_url self.title = title self.caption = caption + self.parse_mode = parse_mode self.performer = performer self.audio_duration = audio_duration self.reply_markup = reply_markup @@ -1399,6 +1415,8 @@ class InlineQueryResultAudio(JsonSerializable): json_dict = {'type': self.type, 'id': self.id, 'audio_url': self.audio_url, 'title': self.title} if self.caption: json_dict['caption'] = self.caption + if self.parse_mode: + json_dict['parse_mode'] = self.parse_mode if self.performer: json_dict['performer'] = self.performer if self.audio_duration: @@ -1411,13 +1429,14 @@ class InlineQueryResultAudio(JsonSerializable): class InlineQueryResultVoice(JsonSerializable): - def __init__(self, id, voice_url, title, caption=None, performer=None, voice_duration=None, reply_markup=None, - input_message_content=None): + def __init__(self, id, voice_url, title, caption=None, parse_mode=None, performer=None, voice_duration=None, + reply_markup=None, input_message_content=None): self.type = 'voice' self.id = id self.voice_url = voice_url self.title = title self.caption = caption + self.parse_mode = parse_mode self.performer = performer self.voice_duration = voice_duration self.reply_markup = reply_markup @@ -1427,6 +1446,8 @@ class InlineQueryResultVoice(JsonSerializable): json_dict = {'type': self.type, 'id': self.id, 'voice_url': self.voice_url, 'title': self.title} if self.caption: json_dict['caption'] = self.caption + if self.parse_mode: + json_dict['parse_mode'] = self.parse_mode if self.performer: json_dict['performer'] = self.performer if self.voice_duration: @@ -1439,14 +1460,15 @@ class InlineQueryResultVoice(JsonSerializable): class InlineQueryResultDocument(JsonSerializable): - def __init__(self, id, title, document_url, mime_type, caption=None, description=None, reply_markup=None, - input_message_content=None, thumb_url=None, thumb_width=None, thumb_height=None): + def __init__(self, id, title, document_url, mime_type, caption=None, parse_mode=None, description=None, + reply_markup=None, input_message_content=None, thumb_url=None, thumb_width=None, thumb_height=None): self.type = 'document' self.id = id self.title = title self.document_url = document_url self.mime_type = mime_type self.caption = caption + self.parse_mode = parse_mode self.description = description self.reply_markup = reply_markup self.input_message_content = input_message_content @@ -1459,6 +1481,8 @@ class InlineQueryResultDocument(JsonSerializable): 'mime_type': self.mime_type} if self.caption: json_dict['caption'] = self.caption + if self.parse_mode: + json_dict['parse_mode'] = self.parse_mode if self.description: json_dict['description'] = self.description if self.thumb_url: @@ -1601,8 +1625,8 @@ class BaseInlineQueryResultCached(JsonSerializable): class InlineQueryResultCachedPhoto(BaseInlineQueryResultCached): - def __init__(self, id, photo_file_id, title=None, description=None, caption=None, reply_markup=None, - input_message_content=None): + def __init__(self, id, photo_file_id, title=None, description=None, caption=None, parse_mode=None, + reply_markup=None, input_message_content=None): BaseInlineQueryResultCached.__init__(self) self.type = 'photo' self.id = id @@ -1613,10 +1637,11 @@ class InlineQueryResultCachedPhoto(BaseInlineQueryResultCached): self.reply_markup = reply_markup self.input_message_content = input_message_content self.payload_dic['photo_file_id'] = photo_file_id + self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedGif(BaseInlineQueryResultCached): - def __init__(self, id, gif_file_id, title=None, description=None, caption=None, reply_markup=None, + def __init__(self, id, gif_file_id, title=None, description=None, caption=None, parse_mode=None, reply_markup=None, input_message_content=None): BaseInlineQueryResultCached.__init__(self) self.type = 'gif' @@ -1628,11 +1653,12 @@ class InlineQueryResultCachedGif(BaseInlineQueryResultCached): self.reply_markup = reply_markup self.input_message_content = input_message_content self.payload_dic['gif_file_id'] = gif_file_id + self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedMpeg4Gif(BaseInlineQueryResultCached): - def __init__(self, id, mpeg4_file_id, title=None, description=None, caption=None, reply_markup=None, - input_message_content=None): + def __init__(self, id, mpeg4_file_id, title=None, description=None, caption=None, parse_mode=None, + reply_markup=None, input_message_content=None): BaseInlineQueryResultCached.__init__(self) self.type = 'mpeg4_gif' self.id = id @@ -1643,6 +1669,7 @@ class InlineQueryResultCachedMpeg4Gif(BaseInlineQueryResultCached): self.reply_markup = reply_markup self.input_message_content = input_message_content self.payload_dic['mpeg4_file_id'] = mpeg4_file_id + self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedSticker(BaseInlineQueryResultCached): @@ -1657,7 +1684,7 @@ class InlineQueryResultCachedSticker(BaseInlineQueryResultCached): class InlineQueryResultCachedDocument(BaseInlineQueryResultCached): - def __init__(self, id, document_file_id, title, description=None, caption=None, reply_markup=None, + def __init__(self, id, document_file_id, title, description=None, caption=None, parse_mode=None, reply_markup=None, input_message_content=None): BaseInlineQueryResultCached.__init__(self) self.type = 'document' @@ -1669,10 +1696,11 @@ class InlineQueryResultCachedDocument(BaseInlineQueryResultCached): self.reply_markup = reply_markup self.input_message_content = input_message_content self.payload_dic['document_file_id'] = document_file_id + self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedVideo(BaseInlineQueryResultCached): - def __init__(self, id, video_file_id, title, description=None, caption=None, reply_markup=None, + def __init__(self, id, video_file_id, title, description=None, caption=None, parse_mode=None, reply_markup=None, input_message_content=None): BaseInlineQueryResultCached.__init__(self) self.type = 'video' @@ -1684,10 +1712,12 @@ class InlineQueryResultCachedVideo(BaseInlineQueryResultCached): self.reply_markup = reply_markup self.input_message_content = input_message_content self.payload_dic['video_file_id'] = video_file_id + self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedVoice(BaseInlineQueryResultCached): - def __init__(self, id, voice_file_id, title, caption=None, reply_markup=None, input_message_content=None): + def __init__(self, id, voice_file_id, title, caption=None, parse_mode=None, reply_markup=None, + input_message_content=None): BaseInlineQueryResultCached.__init__(self) self.type = 'voice' self.id = id @@ -1697,10 +1727,11 @@ class InlineQueryResultCachedVoice(BaseInlineQueryResultCached): self.reply_markup = reply_markup self.input_message_content = input_message_content self.payload_dic['voice_file_id'] = voice_file_id + self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedAudio(BaseInlineQueryResultCached): - def __init__(self, id, audio_file_id, caption=None, reply_markup=None, input_message_content=None): + def __init__(self, id, audio_file_id, caption=None, parse_mode=None, reply_markup=None, input_message_content=None): BaseInlineQueryResultCached.__init__(self) self.type = 'audio' self.id = id @@ -1709,6 +1740,7 @@ class InlineQueryResultCachedAudio(BaseInlineQueryResultCached): self.reply_markup = reply_markup self.input_message_content = input_message_content self.payload_dic['audio_file_id'] = audio_file_id + self.payload_dic['parse_mode'] = parse_mode # Games From 35ea2a2b7e768c13da6841490109e09dd56b5f65 Mon Sep 17 00:00:00 2001 From: eternnoir Date: Fri, 3 Aug 2018 08:34:21 +0800 Subject: [PATCH 29/51] Fix #548 --- telebot/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index fd8ed16..f520ead 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1295,7 +1295,6 @@ class TeleBot: self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) new_messages.pop(i) # removing message that detects with next_step_handler was_poped = True - new_messages.pop(i) # removing message that detects with next_step_handler if self.next_step_saver is not None: self.next_step_saver.start_save_timer() if (not was_poped): From 41f7c079593bb7d4eedc6f44112a8966e8fbd11a Mon Sep 17 00:00:00 2001 From: eternnoir Date: Fri, 3 Aug 2018 08:35:04 +0800 Subject: [PATCH 30/51] Update version. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 01e10a1..7b737e9 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def readme(): return f.read() setup(name='pyTelegramBotAPI', - version='3.6.4', + version='3.6.5', description='Python Telegram bot api. ', long_description=readme(), author='eternnoir', From 4eae4695282d871bbdc8267b8765be06635370b7 Mon Sep 17 00:00:00 2001 From: Ramzan Bekbulatov Date: Mon, 6 Aug 2018 13:54:46 +0300 Subject: [PATCH 31/51] Use last version of README.md for PyPI docs with pretty formatting https://packaging.python.org/specifications/core-metadata/#description-content-type https://pypi.org/project/pyTelegramBotAPI/ --- setup.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 7b737e9..9a401bc 100644 --- a/setup.py +++ b/setup.py @@ -2,14 +2,15 @@ from setuptools import setup from io import open -def readme(): - with open('README.rst', encoding='utf-8') as f: - return f.read() +def read(filename): + with open(filename, encoding='utf-8') as file: + return file.read() setup(name='pyTelegramBotAPI', version='3.6.5', description='Python Telegram bot api. ', - long_description=readme(), + long_description=read('README.md'), + long_description_content_type="text/markdown", author='eternnoir', author_email='eternnoir@gmail.com', url='https://github.com/eternnoir/pyTelegramBotAPI', From 74f75884f30854e507240bf8f413a1e48ef5e094 Mon Sep 17 00:00:00 2001 From: Ramzan Bekbulatov Date: Mon, 6 Aug 2018 13:56:38 +0300 Subject: [PATCH 32/51] Delete now unused deprecated README.rst --- README.rst | 807 ----------------------------------------------------- 1 file changed, 807 deletions(-) delete mode 100644 README.rst diff --git a/README.rst b/README.rst deleted file mode 100644 index d776571..0000000 --- a/README.rst +++ /dev/null @@ -1,807 +0,0 @@ -# - -.. raw:: html - -

- -pyTelegramBotAPI - -.. raw:: html - -

- -A simple, but extensible Python implementation for the `Telegram Bot -API `__. - -|Download Month| |Build Status| |Download Month| - -- `Getting started. <#getting-started>`__ -- `Writing your first bot <#writing-your-first-bot>`__ - - - `Prerequisites <#prerequisites>`__ - - `A simple echo bot <#a-simple-echo-bot>`__ - -- `General API Documentation <#general-api-documentation>`__ - - - `Types <#types>`__ - - `Methods <#methods>`__ - - `General use of the API <#general-use-of-the-api>`__ - - `Message handlers <#message-handlers>`__ - - `Callback Query handlers <#callback-query-handler>`__ - - `TeleBot <#telebot>`__ - - `Reply markup <#reply-markup>`__ - - `Inline Mode <#inline-mode>`__ - -- `Advanced use of the API <#advanced-use-of-the-api>`__ - - - `Asynchronous delivery of - messages <#asynchronous-delivery-of-messages>`__ - - `Sending large text messages <#sending-large-text-messages>`__ - - `Controlling the amount of Threads used by - TeleBot <#controlling-the-amount-of-threads-used-by-telebot>`__ - - `The listener mechanism <#the-listener-mechanism>`__ - - `Using web hooks <#using-web-hooks>`__ - - `Logging <#logging>`__ - -- `F.A.Q. <#faq>`__ - - - `Bot 2.0 <#bot-20>`__ - - `How can I distinguish a User and a GroupChat in - message.chat? <#how-can-i-distinguish-a-user-and-a-groupchat-in-messagechat>`__ - -- `The Telegram Chat Group <#the-telegram-chat-group>`__ -- `More examples <#more-examples>`__ -- `Bots using this API <#bots-using-this-api>`__ - -Getting started. ----------------- - -This API is tested with Python 2.6, Python 2.7, Python 3.4, Pypy and -Pypy 3. There are two ways to install the library: - -- Installation using pip (a Python package manager)\*: - -:: - - $ pip install pyTelegramBotAPI - -- Installation from source (requires git): - -:: - - $ git clone https://github.com/eternnoir/pyTelegramBotAPI.git - $ cd pyTelegramBotAPI - $ python setup.py install - -It is generally recommended to use the first option. - -\*\*While the API is production-ready, it is still under development and -it has regular updates, do not forget to update it regularly by calling -``pip install pytelegrambotapi --upgrade``\ \* - -Writing your first bot ----------------------- - -Prerequisites -~~~~~~~~~~~~~ - -It is presumed that you [have obtained an API token with -@BotFather](https://core.telegram.org/bots#botfather). We will call this -token ``TOKEN``. Furthermore, you have basic knowledge of the Python -programming language and more importantly `the Telegram Bot -API `__. - -A simple echo bot -~~~~~~~~~~~~~~~~~ - -The TeleBot class (defined in \_\_init\_\_.py) encapsulates all API -calls in a single class. It provides functions such as ``send_xyz`` -(``send_message``, ``send_document`` etc.) and several ways to listen -for incoming messages. - -Create a file called ``echo_bot.py``. Then, open the file and create an -instance of the TeleBot class. - -.. code:: python - - import telebot - - bot = telebot.TeleBot("TOKEN") - -*Note: Make sure to actually replace TOKEN with your own API token.* - -After that declaration, we need to register some so-called message -handlers. Message handlers define filters which a message must pass. If -a message passes the filter, the decorated function is called and the -incoming message is passed as an argument. - -Let's define a message handler which handles incoming ``/start`` and -``/help`` commands. - -.. code:: python - - @bot.message_handler(commands=['start', 'help']) - def send_welcome(message): - bot.reply_to(message, "Howdy, how are you doing?") - -A function which is decorated by a message handler **can have an -arbitrary name, however, it must have only one parameter (the -message)**. - -Let's add another handler: - -.. code:: python - - @bot.message_handler(func=lambda m: True) - def echo_all(message): - bot.reply_to(message, message.text) - -This one echoes all incoming text messages back to the sender. It uses a -lambda function to test a message. If the lambda returns True, the -message is handled by the decorated function. Since we want all messages -to be handled by this function, we simply always return True. - -*Note: all handlers are tested in the order in which they were declared* - -We now have a basic bot which replies a static message to "/start" and -"/help" commands and which echoes the rest of the sent messages. To -start the bot, add the following to our source file: - -.. code:: python - - bot.polling() - -Alright, that's it! Our source file now looks like this: - -.. code:: python - - import telebot - - bot = telebot.TeleBot("TOKEN") - - @bot.message_handler(commands=['start', 'help']) - def send_welcome(message): - bot.reply_to(message, "Howdy, how are you doing?") - - @bot.message_handler(func=lambda message: True) - def echo_all(message): - bot.reply_to(message, message.text) - - bot.polling() - -To start the bot, simply open up a terminal and enter -``python echo_bot.py`` to run the bot! Test it by sending commands -('/start' and '/help') and arbitrary text messages. - -General API Documentation -------------------------- - -Types -~~~~~ - -All types are defined in types.py. They are all completely in line with -the `Telegram API's definition of the -types `__, except -for the Message's ``from`` field, which is renamed to ``from_user`` -(because ``from`` is a Python reserved token). Thus, attributes such as -``message_id`` can be accessed directly with ``message.message_id``. -Note that ``message.chat`` can be either an instance of ``User`` or -``GroupChat`` (see `How can I distinguish a User and a GroupChat in -message.chat? <#how-can-i-distinguish-a-user-and-a-groupchat-in-messagechat>`__). - -The Message object also has a ``content_types``\ attribute, which -defines the type of the Message. ``content_types`` can be one of the -following strings: 'text', 'audio', 'document', 'photo', 'sticker', -'video', 'voice', 'location', 'contact', 'new\_chat\_participant', -'left\_chat\_participant', 'new\_chat\_title', 'new\_chat\_photo', -'delete\_chat\_photo', 'group\_chat\_created'. - -Methods -~~~~~~~ - -All `API -methods `__ are -located in the TeleBot class. They are renamed to follow common Python -naming conventions. E.g. ``getMe`` is renamed to ``get_me`` and -``sendMessage`` to ``send_message``. - -General use of the API -~~~~~~~~~~~~~~~~~~~~~~ - -Outlined below are some general use cases of the API. - -Message handlers -^^^^^^^^^^^^^^^^ - -A message handler is a function that is decorated with the -``message_handler`` decorator of a TeleBot instance. Message handlers -consist of one or multiple filters. Each filter much return True for a -certain message in order for a message handler to become eligible to -handle that message. A message handler is declared in the following way -(provided ``bot`` is an instance of TeleBot): - -.. code:: python - - @bot.message_handler(filters) - def function_name(message): - bot.reply_to(message, "This is a message handler") - -``function_name`` is not bound to any restrictions. Any function name is -permitted with message handlers. The function must accept at most one -argument, which will be the message that the function must handle. -``filters`` is a list of keyword arguments. A filter is declared in the -following manner: ``name=argument``. One handler may have multiple -filters. TeleBot supports the following filters: - -+--------+------+------+ -| name | argu | Cond | -| | ment | itio | -| | (s) | n | -+========+======+======+ -| conten | list | ``Tr | -| t\_typ | of | ue`` | -| es | stri | if | -| | ngs | mess | -| | (def | age. | -| | ault | cont | -| | ``[' | ent\ | -| | text | _typ | -| | ']`` | e | -| | ) | is | -| | | in | -| | | the | -| | | list | -| | | of | -| | | stri | -| | | ngs. | -+--------+------+------+ -| regexp | a | ``Tr | -| | regu | ue`` | -| | lar | if | -| | expr | ``re | -| | essi | .sea | -| | on | rch( | -| | as a | rege | -| | stri | xp_a | -| | ng | rg)` | -| | | ` | -| | | retu | -| | | rns | -| | | ``Tr | -| | | ue`` | -| | | and | -| | | ``me | -| | | ssag | -| | | e.co | -| | | nten | -| | | t_ty | -| | | pe = | -| | | = 't | -| | | ext' | -| | | `` | -| | | (See | -| | | `Pyt | -| | | hon | -| | | Regu | -| | | lar | -| | | Expr | -| | | essi | -| | | ons | -| | | ` | -| | | __ | -+--------+------+------+ -| comman | list | ``Tr | -| ds | of | ue`` | -| | stri | if | -| | ngs | ``me | -| | | ssag | -| | | e.co | -| | | nten | -| | | t_ty | -| | | pe = | -| | | = 't | -| | | ext' | -| | | `` | -| | | and | -| | | ``me | -| | | ssag | -| | | e.te | -| | | xt`` | -| | | star | -| | | ts | -| | | with | -| | | a | -| | | comm | -| | | and | -| | | that | -| | | is | -| | | in | -| | | the | -| | | list | -| | | of | -| | | stri | -| | | ngs. | -+--------+------+------+ -| func | a | ``Tr | -| | func | ue`` | -| | tion | if | -| | (lam | the | -| | bda | lamb | -| | or | da | -| | func | or | -| | tion | func | -| | refe | tion | -| | renc | refe | -| | e) | renc | -| | | e | -| | | retu | -| | | rns | -| | | ``Tr | -| | | ue`` | -+--------+------+------+ - -Here are some examples of using the filters and message handlers: - -.. code:: python - - import telebot - bot = telebot.TeleBot("TOKEN") - - # Handles all text messages that contains the commands '/start' or '/help'. - @bot.message_handler(commands=['start', 'help']) - def handle_start_help(message): - pass - - # Handles all sent documents and audio files - @bot.message_handler(content_types=['document', 'audio']) - def handle_docs_audio(message): - pass - - # Handles all text messages that match the regular expression - @bot.message_handler(regexp="SOME_REGEXP") - def handle_message(message): - pass - - #Handles all messages for which the lambda returns True - @bot.message_handler(func=lambda message: message.document.mime_type == 'text/plain', content_types=['document']) - def handle_text_doc(message): - pass - - #Which could also be defined as: - def test_message(message): - return message.document.mime_type == 'text/plan' - - @bot.message_handler(func=test_message, content_types=['document']) - def handle_text_doc(message) - pass - - # Handlers can be stacked to create a function which will be called if either message_handler is eligible - # This handler will be called if the message starts with '/hello' OR is some emoji - @bot.message_handler(commands=['hello']) - @bot.message_handler(func=lambda msg: msg.text.encode("utf-8") == SOME_FANCY_EMOJI) - def send_something(message): - pass - -**Important: all handlers are tested in the order in which they were -declared** - -Callback Query Handler -^^^^^^^^^^^^^^^^^^^^^^ - -In bot2.0 update. You can get ``callback_query`` in update object. In -telebot use ``callback_query_handler`` to process callback\_querys. - -.. code:: python - - @bot.callback_query_handler(func=lambda call: True) - def test_callback(call): - logger.info(call) - -TeleBot -^^^^^^^ - -.. code:: python - - import telebot - - TOKEN = '' - tb = telebot.TeleBot(TOKEN) #create a new Telegram Bot object - - # Upon calling this function, TeleBot starts polling the Telegram servers for new messages. - # - none_stop: True/False (default False) - Don't stop polling when receiving an error from the Telegram servers - # - interval: True/False (default False) - The interval between polling requests - # Note: Editing this parameter harms the bot's response time - # - timeout: integer (default 20) - Timeout in seconds for long polling. - tb.polling(none_stop=False, interval=0, timeout=20) - - # getMe - user = tb.get_me() - - # setWebhook - tb.set_webhook(url="http://example.com", certificate=open('mycert.pem')) - # unset webhook - tb.remove_webhook() - - # getUpdates - updates = tb.get_updates() - updates = tb.get_updates(1234,100,20) #get_Updates(offset, limit, timeout): - - # sendMessage - tb.send_message(chatid, text) - - # forwardMessage - tb.forward_message(to_chat_id, from_chat_id, message_id) - - # All send_xyz functions which can take a file as an argument, can also take a file_id instead of a file. - # sendPhoto - photo = open('/tmp/photo.png', 'rb') - tb.send_photo(chat_id, photo) - tb.send_photo(chat_id, "FILEID") - - # sendAudio - audio = open('/tmp/audio.mp3', 'rb') - tb.send_audio(chat_id, audio) - tb.send_audio(chat_id, "FILEID") - - ## sendAudio with duration, performer and title. - tb.send_audio(CHAT_ID, file_data, 1, 'eternnoir', 'pyTelegram') - - # sendVoice - voice = open('/tmp/voice.ogg', 'rb') - tb.send_voice(chat_id, voice) - tb.send_voice(chat_id, "FILEID") - - # sendDocument - doc = open('/tmp/file.txt', 'rb') - tb.send_document(chat_id, doc) - tb.send_document(chat_id, "FILEID") - - # sendSticker - sti = open('/tmp/sti.webp', 'rb') - tb.send_sticker(chat_id, sti) - tb.send_sticker(chat_id, "FILEID") - - # sendVideo - video = open('/tmp/video.mp4', 'rb') - tb.send_video(chat_id, video) - tb.send_video(chat_id, "FILEID") - - # sendLocation - tb.send_location(chat_id, lat, lon) - - # sendChatAction - # action_string can be one of the following strings: 'typing', 'upload_photo', 'record_video', 'upload_video', - # 'record_audio', 'upload_audio', 'upload_document' or 'find_location'. - tb.send_chat_action(chat_id, action_string) - - # getFile - # Downloading a file is straightforward - # Returns a File object - import requests - file_info = tb.get_file(file_id) - - file = requests.get('https://api.telegram.org/file/bot{0}/{1}'.format(API_TOKEN, file_info.file_path)) - -Reply markup -^^^^^^^^^^^^ - -All ``send_xyz`` functions of TeleBot take an optional ``reply_markup`` -argument. This argument must be an instance of ``ReplyKeyboardMarkup``, -``ReplyKeyboardRemove`` or ``ForceReply``, which are defined in types.py. - -.. code:: python - - from telebot import types - - # Using the ReplyKeyboardMarkup class - # It's constructor can take the following optional arguments: - # - resize_keyboard: True/False (default False) - # - one_time_keyboard: True/False (default False) - # - selective: True/False (default False) - # - row_width: integer (default 3) - # row_width is used in combination with the add() function. - # It defines how many buttons are fit on each row before continuing on the next row. - markup = types.ReplyKeyboardMarkup(row_width=2) - itembtn1 = types.KeyboardButton('a') - itembtn2 = types.KeyboardButton('v') - itembtn3 = types.KeyboardButton('d') - markup.add(itembtn1, itembtn2, itembtn3) - tb.send_message(chat_id, "Choose one letter:", reply_markup=markup) - - # or add strings one row at a time: - markup = types.ReplyKeyboardMarkup() - itembtna = types.KeyboardButton('a') - itembtnv = types.KeyboardButton('v') - itembtnc = types.KeyboardButton('c') - itembtnd = types.KeyboardButton('d') - itembtne = types.KeyboardButton('e') - markup.row(itembtna, itembtnv) - markup.row(itembtnc, itembtnd, itembtne) - tb.send_message(chat_id, "Choose one letter:", reply_markup=markup) - -The last example yields this result: - -.. figure:: https://pp.vk.me/c624430/v624430512/473e5/_mxxW7FPe4U.jpg - :alt: ReplyKeyboardMarkup - - ReplyKeyboardMarkup - -.. code:: python - - # ReplyKeyboardRemove: hides a previously sent ReplyKeyboardMarkup - # Takes an optional selective argument (True/False, default False) - markup = types.ReplyKeyboardRemove(selective=False) - tb.send_message(chat_id, message, reply_markup=markup) - -.. code:: python - - # ForceReply: forces a user to reply to a message - # Takes an optional selective argument (True/False, default False) - markup = types.ForceReply(selective=False) - tb.send_message(chat_id, "Send me another word:", reply_markup=markup) - -ForceReply: - -.. figure:: https://pp.vk.me/c624430/v624430512/473ec/602byyWUHcs.jpg - :alt: ForceReply - - ForceReply - -Inline Mode -~~~~~~~~~~~ - -More information about `Inline -mode `__. - -inline\_handler -^^^^^^^^^^^^^^^ - -Now, you can use inline\_handler to get inline\_query in telebot. - -.. code:: python - - - @bot.inline_handler(lambda query: query.query == 'text') - def query_text(inline_query): - # Query message is text - -chosen\_inline\_handler -^^^^^^^^^^^^^^^^^^^^^^^ - -Use chosen\_inline\_handler to get chosen\_inline\_result in telebot. -Don't forgot add the /setinlinefeedback command for @Botfather. - -More information : -`collecting-feedback `__ - -.. code:: python - - @bot.chosen_inline_handler(func=lambda chosen_inline_result: True) - def test_chosen(chosen_inline_result): - # Process all chosen_inline_result. - -answer\_inline\_query -^^^^^^^^^^^^^^^^^^^^^ - -.. code:: python - - @bot.inline_handler(lambda query: query.query == 'text') - def query_text(inline_query): - try: - r = types.InlineQueryResultArticle('1', 'Result', 'Result message.') - r2 = types.InlineQueryResultArticle('2', 'Result2', 'Result message2.') - bot.answer_inline_query(inline_query.id, [r, r2]) - except Exception as e: - print(e) - -Advanced use of the API ------------------------ - -Asynchronous delivery of messages -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There exists an implementation of TeleBot which executes all -``send_xyz`` and the ``get_me`` functions asynchronously. This can speed -up you bot **significantly**, but it has unwanted side effects if used -without caution. To enable this behaviour, create an instance of -AsyncTeleBot instead of TeleBot. - -.. code:: python - - tb = telebot.AsyncTeleBot("TOKEN") - -Now, every function that calls the Telegram API is executed in a -separate Thread. The functions are modified to return an AsyncTask -instance (defined in util.py). Using AsyncTeleBot allows you to do the -following: - -.. code:: python - - import telebot - - tb = telebot.AsyncTeleBot("TOKEN") - task = tb.get_me() # Execute an API call - # Do some other operations... - a = 0 - for a in range(100): - a += 10 - - result = task.wait() # Get the result of the execution - -*Note: if you execute send\_xyz functions after eachother without -calling wait(), the order in which messages are delivered might be -wrong.* - -Sending large text messages -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sometimes you must send messages that exceed 5000 characters. The -Telegram API can not handle that many characters in one request, so we -need to split the message in multiples. Here is how to do that using the -API: - -.. code:: python - - from telebot import util - large_text = open("large_text.txt", "rb").read() - - # Split the text each 3000 characters. - # split_string returns a list with the splitted text. - splitted_text = util.split_string(large_text, 3000) - for text in splitted_text: - tb.send_message(chat_id, text) - -Controlling the amount of Threads used by TeleBot -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The TeleBot constructor takes the following optional arguments: - -- create\_threads: True/False (default True). A flag to indicate - whether TeleBot should execute message handlers on it's polling - Thread. -- num\_threads: integer (default 4). Controls the amount of - WorkerThreads created for the internal thread pool that TeleBot uses - to execute message handlers. Is not used when create\_threads is - False. - -The listener mechanism -~~~~~~~~~~~~~~~~~~~~~~ - -As an alternative to the message handlers, one can also register a -function as a listener to TeleBot. Example: - -.. code:: python - - def handle_messages(messages): - for message in messsages: - # Do something with the message - bot.reply_to(message, 'Hi') - - bot.set_update_listener(handle_messages) - bot.polling() - -Using webhooks -~~~~~~~~~~~~~~ - -When using webhooks telegram sends one Update per call, for processing -it you should call process\_new\_messages([update.message]) when you -recieve it. - -There are some examples using webhooks in the -*examples/webhook\_examples* directory. - -Logging -~~~~~~~ - -You can use the Telebot module logger to log debug info about Telebot. -Use ``telebot.logger`` to get the logger of the TeleBot module. It is -possible to add custom logging Handlers to the logger. Refer to the -`Python logging module -page `__ for more info. - -.. code:: python - - import logging - - logger = telebot.logger - telebot.logger.setLevel(logging.DEBUG) # Outputs debug messages to console. - -F.A.Q. ------- - -Bot 2.0 -~~~~~~~ - -April 9,2016 Telegram release new bot 2.0 API, which has a drastic -revision especially for the change of method's interface.If you want to -update to the latest version, please make sure you've switched bot's -code to bot 2.0 method interface. - -`More information about pyTelegramBotAPI support -bot2.0 `__ - -How can I distinguish a User and a GroupChat in message.chat? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Telegram Bot API support new type Chat for message.chat. - -- Check the ``type`` attribute in ``Chat`` object: -- \`\`\`python if message.chat.type == “private”: # private chat - message - -if message.chat.type == “group”: # group chat message - -if message.chat.type == “supergroup”: # supergroup chat message - -if message.chat.type == “channel”: # channel message - -\`\`\` - -The Telegram Chat Group ------------------------ - -Get help. Discuss. Chat. - -- Join the pyTelegramBotAPI Telegram Chat Group -- Messge to @eternnoir by telegram for Invitation. -- We now have a Telegram Channel as well! Keep yourself up to date with - API changes, and `join it `__. - -More examples -------------- - -- `Echo - Bot `__ -- `Deep - Linking `__ -- `next\_step\_handler - Example `__ - -Bots using this API -------------------- - -- `SiteAlert bot `__ - (`source `__) by - *ilteoood* - Monitors websites and sends a notification on changes -- `TelegramLoggingBot `__ - by *aRandomStranger* -- `Telegram - LMGTFY\_bot `__ by - *GabrielRF* -- `Telegram - UrlProBot `__ by - *GabrielRF* -- `Telegram Proxy - Bot `__ by - *Groosha* - A simple BITM (bot-in-the-middle) for Telegram acting as - some kind of "proxy". -- `Telegram Proxy Bot `__ by - *mrgigabyte* - - ``Credits for the original version of this bot goes to`` **Groosha** - ``, simply added certain features which I thought were needed``. -- `RadRetroRobot `__ by - *Tronikart* - Multifunctional Telegram Bot RadRetroRobot. -- `League of Legends bot `__ - (`source `__) by *i32ropie* -- `NeoBot `__ by *neoranger* -- `TagAlertBot `__ by *pitasi* - -Want to have your bot listed here? Send a Telegram message to @eternnoir -or @pevdh. - -.. |Download Month| image:: https://img.shields.io/pypi/v/pyTelegramBotAPI.svg - :target: https://pypi.python.org/pypi/pyTelegramBotAPI -.. |Build Status| image:: https://travis-ci.org/eternnoir/pyTelegramBotAPI.svg?branch=master - :target: https://travis-ci.org/eternnoir/pyTelegramBotAPI -.. |Download Month| image:: https://img.shields.io/pypi/dm/pyTelegramBotAPI.svg - :target: https://pypi.python.org/pypi/pyTelegramBotAPI From 494b535a9127c769401791c8acb7a1d1443560a0 Mon Sep 17 00:00:00 2001 From: Andru1999 Date: Wed, 8 Aug 2018 10:46:23 +1000 Subject: [PATCH 33/51] Fix issue When you use threading mode --- telebot/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index f520ead..320601c 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -6,7 +6,6 @@ import time import re import sys import six -import copy import os import pickle @@ -1288,8 +1287,7 @@ class TeleBot: chat_id = message.chat.id was_poped = False if chat_id in self.next_step_handlers.keys(): - handlers = copy.deepcopy(self.next_step_handlers[chat_id]) - self.next_step_handlers.pop(chat_id, None) + handlers = self.next_step_handlers.pop(chat_id, None) if (handlers): for handler in handlers: self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) From 12dbcb56d3d0892fe6d53d3d9079572635386ed9 Mon Sep 17 00:00:00 2001 From: FrankWang Date: Thu, 9 Aug 2018 08:45:07 +0800 Subject: [PATCH 34/51] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e1ec2a8..9222b7b 100644 --- a/README.md +++ b/README.md @@ -585,5 +585,7 @@ Get help. Discuss. Chat. * [yandex_music_bot](http://t.me/yandex_music_bot)- Downloads tracks/albums/public playlists from Yandex.Music streaming service for free. * [LearnIt](https://t.me/LearnItbot)([link](https://github.com/tiagonapoli/LearnIt)) - A Telegram Bot created to help people to memorize other languages’ vocabulary. * [MusicQuiz_bot](https://t.me/MusicQuiz_bot) by [Etoneja](https://github.com/Etoneja) - Listen to audiosamles and try to name the performer of the song. +* [Bot-Telegram-Shodan ](https://github.com/rubenleon/Bot-Telegram-Shodan) by [rubenleon](https://github.com/rubenleon) + Want to have your bot listed here? Send a Telegram message to @eternnoir or @pevdh. From 8ac6e664c5893e55d6a46adee0b61dbb55c1ffc9 Mon Sep 17 00:00:00 2001 From: uburuntu Date: Thu, 9 Aug 2018 19:10:01 +0300 Subject: [PATCH 35/51] new: InputMediaAnimation, InputMediaAudio, InputMediaDocument, editMessageMedia Added support for editing the media content of messages: added the method editMessageMedia and new types InputMediaAnimation, InputMediaAudio, and InputMediaDocument. --- telebot/__init__.py | 10 +++++ telebot/apihelper.py | 28 ++++++++++-- telebot/types.py | 102 +++++++++++++++++++++++++++++++++--------- tests/test_telebot.py | 11 +++++ 4 files changed, 128 insertions(+), 23 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index f520ead..ec19567 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -999,6 +999,12 @@ class TeleBot: return result return types.Message.de_json(result) + def edit_message_media(self, media, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None): + result = apihelper.edit_message_media(self.token, media, chat_id, message_id, inline_message_id, reply_markup) + if type(result) == bool: # if edit inline message return is bool not Message. + return result + return types.Message.de_json(result) + def edit_message_reply_markup(self, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None): result = apihelper.edit_message_reply_markup(self.token, chat_id, message_id, inline_message_id, reply_markup) if type(result) == bool: @@ -1677,6 +1683,10 @@ class AsyncTeleBot(TeleBot): def edit_message_text(self, *args, **kwargs): return TeleBot.edit_message_text(self, *args, **kwargs) + @util.async_dec() + def edit_message_media(self, *args, **kwargs): + return TeleBot.edit_message_media(self, *args, **kwargs) + @util.async_dec() def edit_message_reply_markup(self, *args, **kwargs): return TeleBot.edit_message_reply_markup(self, *args, **kwargs) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index ceda96c..f74f7c7 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from telebot.types import InputMedia try: import ujson as json @@ -264,7 +265,7 @@ def send_photo(token, chat_id, photo, caption=None, reply_to_message_id=None, re def send_media_group(token, chat_id, media, disable_notification=None, reply_to_message_id=None): method_url = r'sendMediaGroup' - media_json, files = _convert_input_media(media) + media_json, files = _convert_input_media_array(media) payload = {'chat_id': chat_id, 'media': media_json} if disable_notification: payload['disable_notification'] = disable_notification @@ -638,6 +639,21 @@ def edit_message_caption(token, caption, chat_id=None, message_id=None, inline_m return _make_request(token, method_url, params=payload) +def edit_message_media(token, media, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None): + method_url = r'editMessageMedia' + media_json, file = _convert_input_media(media) + payload = {'media': media_json} + if chat_id: + payload['chat_id'] = chat_id + if message_id: + payload['message_id'] = message_id + if inline_message_id: + payload['inline_message_id'] = inline_message_id + if reply_markup: + payload['reply_markup'] = _convert_markup(reply_markup) + return _make_request(token, method_url, params=payload, files=file) + + def edit_message_reply_markup(token, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None): method_url = r'editMessageReplyMarkup' payload = {} @@ -937,11 +953,17 @@ def _convert_markup(markup): return markup -def _convert_input_media(array): +def _convert_input_media(media): + if isinstance(media, InputMedia): + return media._convert_input_media() + return None, None + + +def _convert_input_media_array(array): media = [] files = {} for input_media in array: - if isinstance(input_media, types.JsonSerializable): + if isinstance(input_media, InputMedia): media_dict = input_media.to_dic() if media_dict['media'].startswith('attach://'): key = media_dict['media'].replace('attach://', '') diff --git a/telebot/types.py b/telebot/types.py index 27866d9..f3e0300 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -2073,48 +2073,56 @@ class MaskPosition(JsonDeserializable, JsonSerializable): # InputMedia -class InputMediaPhoto(JsonSerializable): - def __init__(self, media, caption=None, parse_mode=None): - self.type = "photo" +class InputMedia(JsonSerializable): + def __init__(self, type, media, caption=None, parse_mode=None): + self.type = type self.media = media self.caption = caption self.parse_mode = parse_mode + self._media_dic = 'attach://' + util.generate_random_token() if not util.is_string(self.media) else self.media + def to_json(self): return json.dumps(self.to_dic()) def to_dic(self): - ret = {'type': self.type, 'media': 'attach://' + util.generate_random_token() - if not util.is_string(self.media) else self.media} + ret = {'type': self.type, 'media': self._media_dic} if self.caption: ret['caption'] = self.caption if self.parse_mode: ret['parse_mode'] = self.parse_mode return ret + def _convert_input_media(self): + if util.is_string(self.media): + return self.to_json(), None -class InputMediaVideo(JsonSerializable): - def __init__(self, media, caption=None, parse_mode=None, width=None, height=None, duration=None, + return self.to_json(), {self._media_dic: self.media} + + +class InputMediaPhoto(InputMedia): + def __init__(self, media, caption=None, parse_mode=None): + super(InputMedia).__init__(type="photo", media=media, caption=caption, parse_mode=parse_mode) + + def to_dic(self): + ret = super(InputMedia).to_dic() + return ret + + +class InputMediaVideo(InputMedia): + def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None, supports_streaming=None): - self.type = "video" - self.media = media - self.caption = caption - self.parse_mode = parse_mode + super(InputMedia).__init__(type="video", media=media, caption=caption, parse_mode=parse_mode) + self.thumb = thumb self.width = width self.height = height self.duration = duration self.supports_streaming = supports_streaming - def to_json(self): - return json.dumps(self.to_dic()) - def to_dic(self): - ret = {'type': self.type, 'media': 'attach://' + util.generate_random_token() - if not util.is_string(self.media) else self.media} - if self.caption: - ret['caption'] = self.caption - if self.parse_mode: - ret['parse_mode'] = self.parse_mode + ret = super(InputMedia).to_dic() + if self.thumb: + ret['thumb'] = self.thumb if self.width: ret['width'] = self.width if self.height: @@ -2124,3 +2132,57 @@ class InputMediaVideo(JsonSerializable): if self.supports_streaming: ret['supports_streaming'] = self.supports_streaming return ret + + +class InputMediaAnimation(InputMedia): + def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None): + super(InputMedia).__init__(type="animation", media=media, caption=caption, parse_mode=parse_mode) + self.thumb = thumb + self.width = width + self.height = height + self.duration = duration + + def to_dic(self): + ret = super(InputMedia).to_dic() + if self.thumb: + ret['thumb'] = self.thumb + if self.width: + ret['width'] = self.width + if self.height: + ret['height'] = self.height + if self.duration: + ret['duration'] = self.duration + return ret + + +class InputMediaAudio(InputMedia): + def __init__(self, media, thumb=None, caption=None, parse_mode=None, duration=None, performer=None, title=None): + super(InputMedia).__init__(type="audio", media=media, caption=caption, parse_mode=parse_mode) + self.thumb = thumb + self.duration = duration + self.performer = performer + self.title = title + + def to_dic(self): + ret = super(InputMedia).to_dic() + if self.thumb: + ret['thumb'] = self.thumb + if self.duration: + ret['duration'] = self.duration + if self.performer: + ret['performer'] = self.performer + if self.title: + ret['title'] = self.title + return ret + + +class InputMediaDocument(InputMedia): + def __init__(self, media, thumb=None, caption=None, parse_mode=None): + super(InputMedia).__init__(type="document", media=media, caption=caption, parse_mode=parse_mode) + self.thumb = thumb + + def to_dic(self): + ret = super(InputMedia).to_dic() + if self.thumb: + ret['thumb'] = self.thumb + return ret diff --git a/tests/test_telebot.py b/tests/test_telebot.py index 6818b5b..3279b30 100644 --- a/tests/test_telebot.py +++ b/tests/test_telebot.py @@ -361,6 +361,17 @@ class TestTeleBot: new_msg = tb.edit_message_caption(caption='Edit test', chat_id=CHAT_ID, message_id=msg.message_id) assert new_msg.caption == 'Edit test' + def test_edit_message_media(self): + file_data = open('../examples/detailed_example/kitten.jpg', 'rb') + file_data_2 = open('../examples/detailed_example/rooster.jpg', 'rb') + tb = telebot.TeleBot(TOKEN) + msg = tb.send_photo(CHAT_ID, file_data) + new_msg = tb.edit_message_media(chat_id=CHAT_ID, message_id=msg.message_id, media=file_data_2) + assert type(new_msg) != bool + + new_msg = tb.edit_message_media(chat_id=CHAT_ID, message_id=msg.message_id, media=msg.photo[0].file_id) + assert type(new_msg) != bool + def test_get_chat(self): tb = telebot.TeleBot(TOKEN) ch = tb.get_chat(GROUP_ID) From cf69a06ab858f75f8ba7fca6b1570ce512f0ba4a Mon Sep 17 00:00:00 2001 From: uburuntu Date: Fri, 10 Aug 2018 16:47:59 +0300 Subject: [PATCH 36/51] enh: make code better and enhance test case --- telebot/apihelper.py | 7 +++---- telebot/types.py | 29 +++++++++++++++++------------ tests/test_telebot.py | 7 +++++-- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index f74f7c7..ae72e37 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from telebot.types import InputMedia try: import ujson as json @@ -651,7 +650,7 @@ def edit_message_media(token, media, chat_id=None, message_id=None, inline_messa payload['inline_message_id'] = inline_message_id if reply_markup: payload['reply_markup'] = _convert_markup(reply_markup) - return _make_request(token, method_url, params=payload, files=file) + return _make_request(token, method_url, params=payload, files=file, method='post' if file else 'get') def edit_message_reply_markup(token, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None): @@ -954,7 +953,7 @@ def _convert_markup(markup): def _convert_input_media(media): - if isinstance(media, InputMedia): + if isinstance(media, types.InputMedia): return media._convert_input_media() return None, None @@ -963,7 +962,7 @@ def _convert_input_media_array(array): media = [] files = {} for input_media in array: - if isinstance(input_media, InputMedia): + if isinstance(input_media, types.InputMedia): media_dict = input_media.to_dic() if media_dict['media'].startswith('attach://'): key = media_dict['media'].replace('attach://', '') diff --git a/telebot/types.py b/telebot/types.py index f3e0300..2e48b5d 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -2080,7 +2080,12 @@ class InputMedia(JsonSerializable): self.caption = caption self.parse_mode = parse_mode - self._media_dic = 'attach://' + util.generate_random_token() if not util.is_string(self.media) else self.media + if util.is_string(self.media): + self._media_name = '' + self._media_dic = self.media + else: + self._media_name = util.generate_random_token() + self._media_dic = 'attach://{}'.format(self._media_name) def to_json(self): return json.dumps(self.to_dic()) @@ -2097,22 +2102,22 @@ class InputMedia(JsonSerializable): if util.is_string(self.media): return self.to_json(), None - return self.to_json(), {self._media_dic: self.media} + return self.to_json(), {self._media_name: self.media} class InputMediaPhoto(InputMedia): def __init__(self, media, caption=None, parse_mode=None): - super(InputMedia).__init__(type="photo", media=media, caption=caption, parse_mode=parse_mode) + super(InputMediaPhoto, self).__init__(type="photo", media=media, caption=caption, parse_mode=parse_mode) def to_dic(self): - ret = super(InputMedia).to_dic() + ret = super(InputMediaPhoto, self).to_dic() return ret class InputMediaVideo(InputMedia): def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None, supports_streaming=None): - super(InputMedia).__init__(type="video", media=media, caption=caption, parse_mode=parse_mode) + super(InputMediaVideo, self).__init__(type="video", media=media, caption=caption, parse_mode=parse_mode) self.thumb = thumb self.width = width self.height = height @@ -2120,7 +2125,7 @@ class InputMediaVideo(InputMedia): self.supports_streaming = supports_streaming def to_dic(self): - ret = super(InputMedia).to_dic() + ret = super(InputMediaVideo, self).to_dic() if self.thumb: ret['thumb'] = self.thumb if self.width: @@ -2136,14 +2141,14 @@ class InputMediaVideo(InputMedia): class InputMediaAnimation(InputMedia): def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None): - super(InputMedia).__init__(type="animation", media=media, caption=caption, parse_mode=parse_mode) + super(InputMediaAnimation, self).__init__(type="animation", media=media, caption=caption, parse_mode=parse_mode) self.thumb = thumb self.width = width self.height = height self.duration = duration def to_dic(self): - ret = super(InputMedia).to_dic() + ret = super(InputMediaAnimation, self).to_dic() if self.thumb: ret['thumb'] = self.thumb if self.width: @@ -2157,14 +2162,14 @@ class InputMediaAnimation(InputMedia): class InputMediaAudio(InputMedia): def __init__(self, media, thumb=None, caption=None, parse_mode=None, duration=None, performer=None, title=None): - super(InputMedia).__init__(type="audio", media=media, caption=caption, parse_mode=parse_mode) + super(InputMediaAudio, self).__init__(type="audio", media=media, caption=caption, parse_mode=parse_mode) self.thumb = thumb self.duration = duration self.performer = performer self.title = title def to_dic(self): - ret = super(InputMedia).to_dic() + ret = super(InputMediaAudio, self).to_dic() if self.thumb: ret['thumb'] = self.thumb if self.duration: @@ -2178,11 +2183,11 @@ class InputMediaAudio(InputMedia): class InputMediaDocument(InputMedia): def __init__(self, media, thumb=None, caption=None, parse_mode=None): - super(InputMedia).__init__(type="document", media=media, caption=caption, parse_mode=parse_mode) + super(InputMediaDocument, self).__init__(type="document", media=media, caption=caption, parse_mode=parse_mode) self.thumb = thumb def to_dic(self): - ret = super(InputMedia).to_dic() + ret = super(InputMediaDocument, self).to_dic() if self.thumb: ret['thumb'] = self.thumb return ret diff --git a/tests/test_telebot.py b/tests/test_telebot.py index 3279b30..b81485e 100644 --- a/tests/test_telebot.py +++ b/tests/test_telebot.py @@ -366,11 +366,14 @@ class TestTeleBot: file_data_2 = open('../examples/detailed_example/rooster.jpg', 'rb') tb = telebot.TeleBot(TOKEN) msg = tb.send_photo(CHAT_ID, file_data) - new_msg = tb.edit_message_media(chat_id=CHAT_ID, message_id=msg.message_id, media=file_data_2) + new_msg = tb.edit_message_media(chat_id=CHAT_ID, message_id=msg.message_id, + media=types.InputMediaPhoto(file_data_2, caption='Test editMessageMedia 0')) assert type(new_msg) != bool - new_msg = tb.edit_message_media(chat_id=CHAT_ID, message_id=msg.message_id, media=msg.photo[0].file_id) + new_msg = tb.edit_message_media(chat_id=CHAT_ID, message_id=msg.message_id, + media=types.InputMediaPhoto(msg.photo[0].file_id, caption='Test editMessageMedia')) assert type(new_msg) != bool + assert new_msg.caption == 'Test editMessageMedia' def test_get_chat(self): tb = telebot.TeleBot(TOKEN) From 7dd53b1396b28b132eca5cbe9978a37af0a13b06 Mon Sep 17 00:00:00 2001 From: uburuntu Date: Tue, 14 Aug 2018 12:23:15 +0300 Subject: [PATCH 37/51] fix: support python2 super() --- telebot/types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 2e48b5d..c59a4d6 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -10,7 +10,7 @@ import six from telebot import util -class JsonSerializable: +class JsonSerializable(object): """ Subclasses of this class are guaranteed to be able to be converted to JSON format. All subclasses of this class must override to_json. @@ -26,7 +26,7 @@ class JsonSerializable: raise NotImplementedError -class Dictionaryable: +class Dictionaryable(object): """ Subclasses of this class are guaranteed to be able to be converted to dictionary. All subclasses of this class must override to_dic. @@ -42,7 +42,7 @@ class Dictionaryable: raise NotImplementedError -class JsonDeserializable: +class JsonDeserializable(object): """ Subclasses of this class are guaranteed to be able to be created from a json-style dict or json formatted string. All subclasses of this class must override de_json. From e419214b4998524ac1bd6db03713fb747d13560e Mon Sep 17 00:00:00 2001 From: uburuntu Date: Tue, 14 Aug 2018 17:29:35 +0300 Subject: [PATCH 38/51] fix: python2 positional argument specifiers --- telebot/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telebot/types.py b/telebot/types.py index c59a4d6..7340c10 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -2085,7 +2085,7 @@ class InputMedia(JsonSerializable): self._media_dic = self.media else: self._media_name = util.generate_random_token() - self._media_dic = 'attach://{}'.format(self._media_name) + self._media_dic = 'attach://{0}'.format(self._media_name) def to_json(self): return json.dumps(self.to_dic()) From 6a4c7e731b80e1187a4f9423ae6e7594e2e6be4f Mon Sep 17 00:00:00 2001 From: uburuntu Date: Fri, 17 Aug 2018 12:46:40 +0300 Subject: [PATCH 39/51] fix: delete doubled Sticker class (left a new one) --- telebot/types.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 7340c10..884e3d4 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -598,29 +598,6 @@ class Document(JsonDeserializable): self.file_size = file_size -class Sticker(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - obj = cls.check_json(json_string) - file_id = obj['file_id'] - width = obj['width'] - height = obj['height'] - thumb = None - if 'thumb' in obj: - thumb = PhotoSize.de_json(obj['thumb']) - emoji = obj.get('emoji') - file_size = obj.get('file_size') - return cls(file_id, width, height, thumb, emoji, file_size) - - def __init__(self, file_id, width, height, thumb, emoji=None, file_size=None): - self.file_id = file_id - self.width = width - self.height = height - self.thumb = thumb - self.emoji = emoji - self.file_size = file_size - - class Video(JsonDeserializable): @classmethod def de_json(cls, json_string): From 65a272b901b848bf5791c09ccbf03657aad47d71 Mon Sep 17 00:00:00 2001 From: uburuntu Date: Fri, 17 Aug 2018 12:47:44 +0300 Subject: [PATCH 40/51] fix: python 2/3 compatibility in examples --- examples/detailed_example/detailed_example.py | 4 ++-- examples/inline_example.py | 2 +- examples/telebot_bot/telebot_bot.py | 2 +- .../webhook_examples/webhook_cpython_echo_bot.py | 12 +++++++++--- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/detailed_example/detailed_example.py b/examples/detailed_example/detailed_example.py index 8fad9af..4bd428b 100644 --- a/examples/detailed_example/detailed_example.py +++ b/examples/detailed_example/detailed_example.py @@ -33,7 +33,7 @@ def get_user_step(uid): else: knownUsers.append(uid) userStep[uid] = 0 - print "New user detected, who hasn't used \"/start\" yet" + print("New user detected, who hasn't used \"/start\" yet") return 0 @@ -45,7 +45,7 @@ def listener(messages): for m in messages: if m.content_type == 'text': # print the sent message to the console - print str(m.chat.first_name) + " [" + str(m.chat.id) + "]: " + m.text + print(str(m.chat.first_name) + " [" + str(m.chat.id) + "]: " + m.text) bot = telebot.TeleBot(TOKEN) diff --git a/examples/inline_example.py b/examples/inline_example.py index c97dea2..725708e 100644 --- a/examples/inline_example.py +++ b/examples/inline_example.py @@ -69,5 +69,5 @@ if __name__ == '__main__': try: main_loop() except KeyboardInterrupt: - print >> sys.stderr, '\nExiting by user request.\n' + print('\nExiting by user request.\n') sys.exit(0) diff --git a/examples/telebot_bot/telebot_bot.py b/examples/telebot_bot/telebot_bot.py index cd29276..46319ce 100644 --- a/examples/telebot_bot/telebot_bot.py +++ b/examples/telebot_bot/telebot_bot.py @@ -70,7 +70,7 @@ def on_start(message): def listener(messages): for m in messages: - print str(m) + print(str(m)) bot.set_update_listener(listener) bot.polling() diff --git a/examples/webhook_examples/webhook_cpython_echo_bot.py b/examples/webhook_examples/webhook_cpython_echo_bot.py index 5c1dfc9..807b5a7 100644 --- a/examples/webhook_examples/webhook_cpython_echo_bot.py +++ b/examples/webhook_examples/webhook_cpython_echo_bot.py @@ -4,7 +4,13 @@ # This is a simple echo bot using decorators and webhook with BaseHTTPServer # It echoes any incoming text messages and does not use the polling method. -import BaseHTTPServer +try: + # Python 2 + from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +except ImportError: + # Python 3 + from http.server import BaseHTTPRequestHandler, HTTPServer + import ssl import telebot import logging @@ -38,7 +44,7 @@ bot = telebot.TeleBot(API_TOKEN) # WebhookHandler, process webhook calls -class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler): +class WebhookHandler(BaseHTTPRequestHandler): server_version = "WebhookHandler/1.0" def do_HEAD(self): @@ -88,7 +94,7 @@ bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH, certificate=open(WEBHOOK_SSL_CERT, 'r')) # Start server -httpd = BaseHTTPServer.HTTPServer((WEBHOOK_LISTEN, WEBHOOK_PORT), +httpd = HTTPServer((WEBHOOK_LISTEN, WEBHOOK_PORT), WebhookHandler) httpd.socket = ssl.wrap_socket(httpd.socket, From 54eba946be7426641cc5e58d888cb1626f1438e2 Mon Sep 17 00:00:00 2001 From: uburuntu Date: Fri, 17 Aug 2018 12:48:59 +0300 Subject: [PATCH 41/51] fix: wrong arguments usage (fix fa038c2) --- telebot/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 41b65a4..7865d94 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1496,12 +1496,12 @@ class AsyncTeleBot(TeleBot): TeleBot.__init__(self, *args, **kwargs) @util.async_dec() - def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save", del_file_after_loading=True): - return TeleBot.enable_save_next_step_handlers(self, delay, filename, del_file_after_loading) + def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save"): + return TeleBot.enable_save_next_step_handlers(self, delay, filename) @util.async_dec() - def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save", del_file_after_loading=True): - return TeleBot.enable_save_reply_handlers(self, delay, filename, del_file_after_loading) + def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"): + return TeleBot.enable_save_reply_handlers(self, delay, filename) @util.async_dec() def disable_save_next_step_handlers(self): @@ -1512,12 +1512,12 @@ class AsyncTeleBot(TeleBot): return TeleBot.enable_save_reply_handlers(self) @util.async_dec() - def load_next_step_handlers(self, filename="./.handler-saves/step.save"): - return TeleBot.load_next_step_handlers(self, filename) + def load_next_step_handlers(self, filename="./.handler-saves/step.save", del_file_after_loading=True): + return TeleBot.load_next_step_handlers(self, filename, del_file_after_loading) @util.async_dec() - def load_reply_handlers(self, filename="./.handler-saves/reply.save"): - return TeleBot.load_reply_handlers(self, filename) + def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True): + return TeleBot.load_reply_handlers(self, filename, del_file_after_loading) @util.async_dec() From feec1dde566ef0de96cf698b58c94f9899649fa5 Mon Sep 17 00:00:00 2001 From: uburuntu Date: Fri, 17 Aug 2018 12:49:37 +0300 Subject: [PATCH 42/51] fix: little style fixes --- examples/deep_linking.py | 1 - examples/step_example.py | 1 - telebot/__init__.py | 4 ++-- telebot/apihelper.py | 6 ++++-- telebot/util.py | 2 +- tests/test_telebot.py | 3 ++- tests/test_types.py | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/deep_linking.py b/examples/deep_linking.py index f92680f..91fcb15 100644 --- a/examples/deep_linking.py +++ b/examples/deep_linking.py @@ -31,7 +31,6 @@ # steps are not shown here. Only steps 5 to 7 are illustrated, some in pseudo-code, with this example. import telebot -import time bot = telebot.TeleBot('TOKEN') diff --git a/examples/step_example.py b/examples/step_example.py index 0fc0ef1..fea047e 100644 --- a/examples/step_example.py +++ b/examples/step_example.py @@ -2,7 +2,6 @@ """ This Example will show you how to use register_next_step handler. """ -import time import telebot from telebot import types diff --git a/telebot/__init__.py b/telebot/__init__.py index 7865d94..5a4ec35 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1294,14 +1294,14 @@ class TeleBot: was_poped = False if chat_id in self.next_step_handlers.keys(): handlers = self.next_step_handlers.pop(chat_id, None) - if (handlers): + if handlers: for handler in handlers: self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) new_messages.pop(i) # removing message that detects with next_step_handler was_poped = True if self.next_step_saver is not None: self.next_step_saver.start_save_timer() - if (not was_poped): + if not was_poped: i += 1 diff --git a/telebot/apihelper.py b/telebot/apihelper.py index ae72e37..e36bcd7 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -99,7 +99,6 @@ def get_file(token, file_id): def get_file_url(token, file_id): - method_url = r'getFile' return FILE_URL.format(token, get_file(token, file_id).file_path) @@ -123,6 +122,8 @@ def send_message(token, chat_id, text, disable_web_page_preview=None, reply_to_m :param disable_web_page_preview: :param reply_to_message_id: :param reply_markup: + :param parse_mode: + :param disable_notification: :return: """ method_url = r'sendMessage' @@ -769,7 +770,8 @@ def send_invoice(token, chat_id, title, description, invoice_payload, provider_t :param disable_notification: Sends the message silently. Users will receive a notification with no sound. :param reply_to_message_id: If the message is a reply, ID of the original message :param reply_markup: 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 - :return: + :param provider_data: + :return: """ method_url = r'sendInvoice' payload = {'chat_id': chat_id, 'title': title, 'description': description, 'payload': invoice_payload, diff --git a/telebot/util.py b/telebot/util.py index f448d78..e7af2c7 100644 --- a/telebot/util.py +++ b/telebot/util.py @@ -243,7 +243,7 @@ def extract_arguments(text): :param text: String to extract the arguments from a command :return: the arguments if `text` is a command (according to is_command), else None. """ - regexp = re.compile("\/\w*(@\w*)*\s*([\s\S]*)",re.IGNORECASE) + regexp = re.compile("/\w*(@\w*)*\s*([\s\S]*)",re.IGNORECASE) result = regexp.match(text) return result.group(2) if is_command(text) else None diff --git a/tests/test_telebot.py b/tests/test_telebot.py index b81485e..908b141 100644 --- a/tests/test_telebot.py +++ b/tests/test_telebot.py @@ -402,7 +402,8 @@ class TestTeleBot: 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 create_text_message(self, text): + @staticmethod + def create_text_message(text): params = {'text': text} chat = types.User(11, False, 'test') return types.Message(1, None, None, chat, 'text', params, "") diff --git a/tests/test_types.py b/tests/test_types.py index c174d42..42e285b 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -59,7 +59,7 @@ def test_json_Message_Sticker_without_thumb(): json_string = r'{"message_id":98,"from":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd","is_bot":true },"chat":{"id":10734,"first_name":"Fd","type":"private","last_name":"Wd","username":"dd"},"date":1435479551,"sticker":{"width":550,"height":368,"file_id":"BQADBQADNAIAAsYifgYdGJOa6bGAsQI","file_size":30320}}' msg = types.Message.de_json(json_string) assert msg.sticker.height == 368 - assert msg.sticker.thumb == None + assert msg.sticker.thumb is None assert msg.content_type == 'sticker' From 99466017c5ff09aa72b295f338e11a3e118420f8 Mon Sep 17 00:00:00 2001 From: uburuntu Date: Fri, 17 Aug 2018 12:54:26 +0300 Subject: [PATCH 43/51] enh: optimize imports --- examples/detailed_example/detailed_example.py | 3 ++- examples/inline_example.py | 7 ++++--- examples/payments_example.py | 3 +-- examples/telebot_bot/telebot_bot.py | 3 ++- .../webhook_examples/webhook_aiohttp_echo_bot.py | 1 - .../webhook_examples/webhook_cherrypy_echo_bot.py | 5 +++-- .../webhook_examples/webhook_cpython_echo_bot.py | 4 ++-- examples/webhook_examples/webhook_flask_echo_bot.py | 5 +++-- .../webhook_examples/webhook_flask_heroku_echo.py | 3 ++- .../webhook_examples/webhook_tornado_echo_bot.py | 12 +++++++----- telebot/__init__.py | 13 ++++++------- telebot/util.py | 5 +++-- 12 files changed, 35 insertions(+), 29 deletions(-) diff --git a/examples/detailed_example/detailed_example.py b/examples/detailed_example/detailed_example.py index 4bd428b..83fcb67 100644 --- a/examples/detailed_example/detailed_example.py +++ b/examples/detailed_example/detailed_example.py @@ -2,9 +2,10 @@ This is a detailed example using almost every command of the API """ +import time + import telebot from telebot import types -import time TOKEN = '' diff --git a/examples/inline_example.py b/examples/inline_example.py index 725708e..cc7ba77 100644 --- a/examples/inline_example.py +++ b/examples/inline_example.py @@ -1,8 +1,9 @@ # This example show how to write an inline mode telegramt bot use pyTelegramBotAPI. -import telebot -import time -import sys import logging +import sys +import time + +import telebot from telebot import types API_TOKEN = '' diff --git a/examples/payments_example.py b/examples/payments_example.py index 3e82955..d0f52d4 100644 --- a/examples/payments_example.py +++ b/examples/payments_example.py @@ -1,6 +1,5 @@ import telebot -from telebot.types import LabeledPrice -from telebot.types import ShippingOption +from telebot.types import LabeledPrice, ShippingOption token = '1234567890:AAAABBBBCCCCDDDDeeeeFFFFgggGHHHH' provider_token = '1234567890:TEST:AAAABBBBCCCCDDDD' # @BotFather -> Bot Settings -> Payments diff --git a/examples/telebot_bot/telebot_bot.py b/examples/telebot_bot/telebot_bot.py index 46319ce..b599efe 100644 --- a/examples/telebot_bot/telebot_bot.py +++ b/examples/telebot_bot/telebot_bot.py @@ -3,9 +3,10 @@ # and goes by the name 'TeleBot (@pyTeleBot)'. Join our group to talk to him! # WARNING: Tested with Python 2.7 -import telebot import os +import telebot + text_messages = { 'welcome': u'Please welcome {name}!\n\n' diff --git a/examples/webhook_examples/webhook_aiohttp_echo_bot.py b/examples/webhook_examples/webhook_aiohttp_echo_bot.py index d92cff9..c949b84 100644 --- a/examples/webhook_examples/webhook_aiohttp_echo_bot.py +++ b/examples/webhook_examples/webhook_aiohttp_echo_bot.py @@ -11,7 +11,6 @@ from aiohttp import web import telebot - API_TOKEN = '' WEBHOOK_HOST = '' diff --git a/examples/webhook_examples/webhook_cherrypy_echo_bot.py b/examples/webhook_examples/webhook_cherrypy_echo_bot.py index d0f3da0..c679a01 100644 --- a/examples/webhook_examples/webhook_cherrypy_echo_bot.py +++ b/examples/webhook_examples/webhook_cherrypy_echo_bot.py @@ -4,10 +4,11 @@ # This is a simple echo bot using decorators and webhook with CherryPy # It echoes any incoming text messages and does not use the polling method. -import cherrypy -import telebot import logging +import cherrypy + +import telebot API_TOKEN = '' diff --git a/examples/webhook_examples/webhook_cpython_echo_bot.py b/examples/webhook_examples/webhook_cpython_echo_bot.py index 807b5a7..639dcae 100644 --- a/examples/webhook_examples/webhook_cpython_echo_bot.py +++ b/examples/webhook_examples/webhook_cpython_echo_bot.py @@ -11,10 +11,10 @@ except ImportError: # Python 3 from http.server import BaseHTTPRequestHandler, HTTPServer -import ssl -import telebot import logging +import ssl +import telebot API_TOKEN = '' diff --git a/examples/webhook_examples/webhook_flask_echo_bot.py b/examples/webhook_examples/webhook_flask_echo_bot.py index d0327d7..2567cab 100644 --- a/examples/webhook_examples/webhook_flask_echo_bot.py +++ b/examples/webhook_examples/webhook_flask_echo_bot.py @@ -4,11 +4,12 @@ # This is a simple echo bot using decorators and webhook with flask # It echoes any incoming text messages and does not use the polling method. -import flask -import telebot import logging import time +import flask + +import telebot API_TOKEN = '' diff --git a/examples/webhook_examples/webhook_flask_heroku_echo.py b/examples/webhook_examples/webhook_flask_heroku_echo.py index 62d0a90..7bbf2bf 100644 --- a/examples/webhook_examples/webhook_flask_heroku_echo.py +++ b/examples/webhook_examples/webhook_flask_heroku_echo.py @@ -1,8 +1,9 @@ import os -import telebot from flask import Flask, request +import telebot + TOKEN = '' bot = telebot.TeleBot(TOKEN) server = Flask(__name__) diff --git a/examples/webhook_examples/webhook_tornado_echo_bot.py b/examples/webhook_examples/webhook_tornado_echo_bot.py index 538b7b9..c4c64b0 100644 --- a/examples/webhook_examples/webhook_tornado_echo_bot.py +++ b/examples/webhook_examples/webhook_tornado_echo_bot.py @@ -4,13 +4,15 @@ # This example shows webhook echo bot with Tornado web framework # Documenation to Tornado: http://tornadoweb.org -import telebot -import tornado.web -import tornado.ioloop -import tornado.httpserver -import tornado.options import signal +import tornado.httpserver +import tornado.ioloop +import tornado.options +import tornado.web + +import telebot + API_TOKEN = '' WEBHOOK_CERT = "./cert.pem" WEBHOOK_PKEY = "./pkey.pem" diff --git a/telebot/__init__.py b/telebot/__init__.py index 5a4ec35..95fa03a 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1,16 +1,15 @@ # -*- coding: utf-8 -*- from __future__ import print_function -import threading -import time -import re -import sys -import six - +import logging import os import pickle +import re +import sys +import threading +import time -import logging +import six logger = logging.getLogger('TeleBot') formatter = logging.Formatter( diff --git a/telebot/util.py b/telebot/util.py index e7af2c7..034aa49 100644 --- a/telebot/util.py +++ b/telebot/util.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- import random +import re import string +import sys import threading import traceback -import re -import sys + import six from six import string_types From 36621bb22a23d10cbe7d298a5bba65c415bebb4a Mon Sep 17 00:00:00 2001 From: uburuntu Date: Fri, 17 Aug 2018 13:01:03 +0300 Subject: [PATCH 44/51] fix: some intendation --- examples/deep_linking.py | 10 ++++++++-- examples/detailed_example/detailed_example.py | 9 +++++---- examples/echo_bot.py | 2 ++ examples/step_example.py | 1 - examples/telebot_bot/telebot_bot.py | 11 ++++++++--- .../webhook_aiohttp_echo_bot.py | 4 ++-- .../webhook_cherrypy_echo_bot.py | 9 ++++----- .../webhook_cpython_echo_bot.py | 5 ++--- .../webhook_flask_echo_bot.py | 3 +-- .../webhook_tornado_echo_bot.py | 19 ++++++++++++++----- telebot/__init__.py | 7 +++---- telebot/types.py | 14 ++++++++------ tests/test_types.py | 5 ++++- 13 files changed, 61 insertions(+), 38 deletions(-) diff --git a/examples/deep_linking.py b/examples/deep_linking.py index 91fcb15..98df204 100644 --- a/examples/deep_linking.py +++ b/examples/deep_linking.py @@ -34,30 +34,35 @@ import telebot bot = telebot.TeleBot('TOKEN') + def extract_unique_code(text): # Extracts the unique_code from the sent /start command. return text.split()[1] if len(text.split()) > 1 else None + def in_storage(unique_code): # (pseudo-code) Should check if a unique code exists in storage return True + def get_username_from_storage(unique_code): # (pseudo-code) Does a query to the storage, retrieving the associated username # Should be replaced by a real database-lookup. return "ABC" if in_storage(unique_code) else None + def save_chat_id(chat_id, username): # (pseudo-code) Save the chat_id->username to storage # Should be replaced by a real database query. pass + @bot.message_handler(commands=['start']) def send_welcome(message): unique_code = extract_unique_code(message.text) - if unique_code: # if the '/start' command contains a unique_code + if unique_code: # if the '/start' command contains a unique_code username = get_username_from_storage(unique_code) - if username: # if the username exists in our database + if username: # if the username exists in our database save_chat_id(message.chat.id, username) reply = "Hello {0}, how are you?".format(username) else: @@ -66,4 +71,5 @@ def send_welcome(message): reply = "Please visit me via a provided URL from the website." bot.reply_to(message, reply) + bot.polling() diff --git a/examples/detailed_example/detailed_example.py b/examples/detailed_example/detailed_example.py index 83fcb67..f481832 100644 --- a/examples/detailed_example/detailed_example.py +++ b/examples/detailed_example/detailed_example.py @@ -13,10 +13,10 @@ knownUsers = [] # todo: save these in a file, userStep = {} # so they won't reset every time the bot restarts commands = { # command description used in the "help" command - 'start': 'Get used to the bot', - 'help': 'Gives you information about the available commands', - 'sendLongText': 'A test using the \'send_chat_action\' command', - 'getImage': 'A test using multi-stage messages, custom keyboard, and media sending' + 'start' : 'Get used to the bot', + 'help' : 'Gives you information about the available commands', + 'sendLongText': 'A test using the \'send_chat_action\' command', + 'getImage' : 'A test using multi-stage messages, custom keyboard, and media sending' } imageSelect = types.ReplyKeyboardMarkup(one_time_keyboard=True) # create the image selection keyboard @@ -129,4 +129,5 @@ def command_default(m): # this is the standard reply to a normal message bot.send_message(m.chat.id, "I don't understand \"" + m.text + "\"\nMaybe try the help page at /help") + bot.polling() diff --git a/examples/echo_bot.py b/examples/echo_bot.py index f88d3bf..b20f09d 100644 --- a/examples/echo_bot.py +++ b/examples/echo_bot.py @@ -7,6 +7,7 @@ API_TOKEN = '' bot = telebot.TeleBot(API_TOKEN) + # Handle '/start' and '/help' @bot.message_handler(commands=['help', 'start']) def send_welcome(message): @@ -21,4 +22,5 @@ I am here to echo your kind words back to you. Just say anything nice and I'll s def echo_message(message): bot.reply_to(message, message.text) + bot.polling() diff --git a/examples/step_example.py b/examples/step_example.py index fea047e..0fc17e5 100644 --- a/examples/step_example.py +++ b/examples/step_example.py @@ -83,5 +83,4 @@ bot.enable_save_next_step_handlers(delay=2) # WARNING It will work only if enable_save_next_step_handlers was called! bot.load_next_step_handlers() - bot.polling() diff --git a/examples/telebot_bot/telebot_bot.py b/examples/telebot_bot/telebot_bot.py index b599efe..ac6b63c 100644 --- a/examples/telebot_bot/telebot_bot.py +++ b/examples/telebot_bot/telebot_bot.py @@ -34,8 +34,10 @@ if "TELEBOT_BOT_TOKEN" not in os.environ or "GROUP_CHAT_ID" not in os.environ: bot = telebot.AsyncTeleBot(os.environ["TELEBOT_BOT_TOKEN"]) GROUP_CHAT_ID = int(os.environ["GROUP_CHAT_ID"]) + def is_api_group(chat_id): - return chat_id== GROUP_CHAT_ID + return chat_id == GROUP_CHAT_ID + @bot.message_handler(func=lambda m: True, content_types=['new_chat_participant']) def on_user_joins(message): @@ -51,6 +53,7 @@ def on_user_joins(message): bot.reply_to(message, text_messages['welcome'].format(name=name)) + @bot.message_handler(commands=['info', 'help']) def on_info(message): if not is_api_group(message.chat.id): @@ -59,21 +62,23 @@ def on_info(message): bot.reply_to(message, text_messages['info']) + @bot.message_handler(commands=["ping"]) def on_ping(message): bot.reply_to(message, "Still alive and kicking!") + @bot.message_handler(commands=['start']) def on_start(message): if not is_api_group(message.chat.id): bot.reply_to(message, text_messages['wrong_chat']) return + def listener(messages): for m in messages: print(str(m)) + bot.set_update_listener(listener) bot.polling() - - diff --git a/examples/webhook_examples/webhook_aiohttp_echo_bot.py b/examples/webhook_examples/webhook_aiohttp_echo_bot.py index c949b84..bbb7c6e 100644 --- a/examples/webhook_examples/webhook_aiohttp_echo_bot.py +++ b/examples/webhook_examples/webhook_aiohttp_echo_bot.py @@ -31,7 +31,6 @@ WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key WEBHOOK_URL_BASE = "https://{}:{}".format(WEBHOOK_HOST, WEBHOOK_PORT) WEBHOOK_URL_PATH = "/{}/".format(API_TOKEN) - logger = telebot.logger telebot.logger.setLevel(logging.INFO) @@ -50,6 +49,7 @@ async def handle(request): else: return web.Response(status=403) + app.router.add_post('/{token}/', handle) @@ -71,7 +71,7 @@ def echo_message(message): bot.remove_webhook() # Set webhook -bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH, +bot.set_webhook(url=WEBHOOK_URL_BASE + WEBHOOK_URL_PATH, certificate=open(WEBHOOK_SSL_CERT, 'r')) # Build ssl context diff --git a/examples/webhook_examples/webhook_cherrypy_echo_bot.py b/examples/webhook_examples/webhook_cherrypy_echo_bot.py index c679a01..7b46e78 100644 --- a/examples/webhook_examples/webhook_cherrypy_echo_bot.py +++ b/examples/webhook_examples/webhook_cherrypy_echo_bot.py @@ -30,7 +30,6 @@ WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT) WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN) - logger = telebot.logger telebot.logger.setLevel(logging.INFO) @@ -71,7 +70,7 @@ def echo_message(message): bot.remove_webhook() # Set webhook -bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH, +bot.set_webhook(url=WEBHOOK_URL_BASE + WEBHOOK_URL_PATH, certificate=open(WEBHOOK_SSL_CERT, 'r')) # Disable CherryPy requests log @@ -81,9 +80,9 @@ for handler in tuple(access_log.handlers): # Start cherrypy server cherrypy.config.update({ - 'server.socket_host': WEBHOOK_LISTEN, - 'server.socket_port': WEBHOOK_PORT, - 'server.ssl_module': 'builtin', + 'server.socket_host' : WEBHOOK_LISTEN, + 'server.socket_port' : WEBHOOK_PORT, + 'server.ssl_module' : 'builtin', 'server.ssl_certificate': WEBHOOK_SSL_CERT, 'server.ssl_private_key': WEBHOOK_SSL_PRIV }) diff --git a/examples/webhook_examples/webhook_cpython_echo_bot.py b/examples/webhook_examples/webhook_cpython_echo_bot.py index 639dcae..029f361 100644 --- a/examples/webhook_examples/webhook_cpython_echo_bot.py +++ b/examples/webhook_examples/webhook_cpython_echo_bot.py @@ -36,7 +36,6 @@ WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT) WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN) - logger = telebot.logger telebot.logger.setLevel(logging.INFO) @@ -90,12 +89,12 @@ def echo_message(message): bot.remove_webhook() # Set webhook -bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH, +bot.set_webhook(url=WEBHOOK_URL_BASE + WEBHOOK_URL_PATH, certificate=open(WEBHOOK_SSL_CERT, 'r')) # Start server httpd = HTTPServer((WEBHOOK_LISTEN, WEBHOOK_PORT), - WebhookHandler) + WebhookHandler) httpd.socket = ssl.wrap_socket(httpd.socket, certfile=WEBHOOK_SSL_CERT, diff --git a/examples/webhook_examples/webhook_flask_echo_bot.py b/examples/webhook_examples/webhook_flask_echo_bot.py index 2567cab..daa3995 100644 --- a/examples/webhook_examples/webhook_flask_echo_bot.py +++ b/examples/webhook_examples/webhook_flask_echo_bot.py @@ -31,7 +31,6 @@ WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT) WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN) - logger = telebot.logger telebot.logger.setLevel(logging.INFO) @@ -78,7 +77,7 @@ bot.remove_webhook() time.sleep(0.1) # Set webhook -bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH, +bot.set_webhook(url=WEBHOOK_URL_BASE + WEBHOOK_URL_PATH, certificate=open(WEBHOOK_SSL_CERT, 'r')) # Start flask server diff --git a/examples/webhook_examples/webhook_tornado_echo_bot.py b/examples/webhook_examples/webhook_tornado_echo_bot.py index c4c64b0..171d2d7 100644 --- a/examples/webhook_examples/webhook_tornado_echo_bot.py +++ b/examples/webhook_examples/webhook_tornado_echo_bot.py @@ -31,15 +31,18 @@ WEBHOOK_URL_BASE = "https://{0}:{1}/{2}".format(WEBHOOK_HOST, str(WEBHOOK_PORT), bot = telebot.TeleBot(API_TOKEN) + class Root(tornado.web.RequestHandler): def get(self): self.write("Hi! This is webhook example!") self.finish() + class webhook_serv(tornado.web.RequestHandler): def get(self): self.write("What are you doing here?") self.finish() + def post(self): if "Content-Length" in self.request.headers and \ "Content-Type" in self.request.headers and \ @@ -54,21 +57,26 @@ class webhook_serv(tornado.web.RequestHandler): else: self.write("What are you doing here?") self.finish() - + + tornado.options.define("port", default=WEBHOOK_PORT, help="run on the given port", type=int) is_closing = False + + def signal_handler(signum, frame): global is_closing print("Exiting...") is_closing = True + def try_exit(): global is_closing if is_closing: # clean up here tornado.ioloop.IOLoop.instance().stop() print("Exit success!") - + + # Handle '/start' and '/help' @bot.message_handler(commands=['help', 'start']) def send_welcome(message): @@ -76,6 +84,7 @@ def send_welcome(message): ("Hi there, I am EchoBot.\n" "I am here to echo your kind words back to you.")) + bot.remove_webhook() bot.set_webhook(url=WEBHOOK_URL_BASE, certificate=open(WEBHOOK_CERT, 'r')) @@ -88,9 +97,9 @@ application = tornado.web.Application([ ]) http_server = tornado.httpserver.HTTPServer(application, ssl_options={ - "certfile": WEBHOOK_CERT, - "keyfile": WEBHOOK_PKEY, - }) + "certfile": WEBHOOK_CERT, + "keyfile" : WEBHOOK_PKEY, +}) http_server.listen(tornado.options.options.port) tornado.ioloop.PeriodicCallback(try_exit, 100).start() tornado.ioloop.IOLoop.instance().start() diff --git a/telebot/__init__.py b/telebot/__init__.py index 95fa03a..52a4e5a 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -33,6 +33,7 @@ class Handler: """ Class for (next step|reply) handlers """ + def __init__(self, callback, *args, **kwargs): self.callback = callback self.args = args @@ -46,6 +47,7 @@ class Saver: """ Class for saving (next step|reply) handlers """ + def __init__(self, handlers, filename, delay): self.handlers = handlers self.filename = filename @@ -1303,12 +1305,11 @@ class TeleBot: if not was_poped: i += 1 - @staticmethod def _build_handler_dict(handler, **filters): return { 'function': handler, - 'filters': filters + 'filters' : filters } def message_handler(self, commands=None, regexp=None, func=None, content_types=['text'], **kwargs): @@ -1518,8 +1519,6 @@ class AsyncTeleBot(TeleBot): def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True): return TeleBot.load_reply_handlers(self, filename, del_file_after_loading) - @util.async_dec() - @util.async_dec() def get_me(self): return TeleBot.get_me(self) diff --git a/telebot/types.py b/telebot/types.py index 884e3d4..06a1c44 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -457,11 +457,11 @@ class Message(JsonDeserializable): if not entities: return text _subs = { - "bold": "{text}", - "italic": "{text}", - "pre": "

{text}
", - "code": "{text}", - "url": "{text}", + "bold" : "{text}", + "italic" : "{text}", + "pre" : "
{text}
", + "code" : "{text}", + "url" : "{text}", "text_link": "{text}" } if hasattr(self, "custom_subs"): @@ -469,6 +469,7 @@ class Message(JsonDeserializable): _subs[type] = self.custom_subs[type] utf16_text = text.encode("utf-16-le") html_text = "" + def func(text, type=None, url=None, user=None): text = text.decode("utf-16-le") if type == "text_mention": @@ -501,6 +502,7 @@ class Message(JsonDeserializable): def html_caption(self): return self.__html_text(self.caption, self.caption_entities) + class MessageEntity(JsonDeserializable): @classmethod def de_json(cls, json_string): @@ -1069,7 +1071,7 @@ class InputVenueMessageContent(Dictionaryable): def to_dic(self): json_dic = {'latitude': self.latitude, 'longitude': self.longitude, 'title': self.title, - 'address': self.address} + 'address' : self.address} if self.foursquare_id: json_dic['foursquare_id'] = self.foursquare_id return json_dic diff --git a/tests/test_types.py b/tests/test_types.py index 42e285b..c229bdf 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -113,6 +113,7 @@ def test_json_voice(): assert voice.duration == 0 assert voice.file_size == 10481 + def test_json_update(): json_string = r'{"update_id":938203,"message":{"message_id":241,"from":{"is_bot":true,"id":9734,"first_name":"Fk","last_name":"Wg","username":"nir"},"chat":{"id":1111,"first_name":"Fk","type":"private","last_name":"Wg","username":"oir"},"date":1441447009,"text":"HIHI"}}' update = types.Update.de_json(json_string) @@ -120,6 +121,7 @@ def test_json_update(): assert update.message.message_id == 241 assert update.message.from_user.id == 9734 + def test_json_chat(): json_string = r'{"id": -111111,"title": "Test Title","type": "group"}' chat = types.Chat.de_json(json_string) @@ -127,6 +129,7 @@ def test_json_chat(): assert chat.type == 'group' assert chat.title == 'Test Title' + def test_InlineQueryResultCachedPhoto(): iq = types.InlineQueryResultCachedPhoto('aaa', 'Fileid') json_str = iq.to_json() @@ -143,6 +146,7 @@ def test_InlineQueryResultCachedPhoto_with_title(): assert 'Title' in json_str assert 'caption' not in json_str + def test_InlineQueryResultCachedPhoto_with_markup(): markup = types.InlineKeyboardMarkup() markup.add(types.InlineKeyboardButton("Google", url="http://www.google.com")) @@ -154,4 +158,3 @@ def test_InlineQueryResultCachedPhoto_with_markup(): assert 'Title' in json_str assert 'caption' not in json_str assert 'reply_markup' in json_str - From d9ace2adc8f1646dab5e97ccb29bfacea7ebccfa Mon Sep 17 00:00:00 2001 From: SetazeR Date: Wed, 5 Sep 2018 12:32:19 +0700 Subject: [PATCH 45/51] fix typo + add inline keyboard example --- examples/inline_example.py | 2 +- examples/inline_keyboard_example.py | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 examples/inline_keyboard_example.py diff --git a/examples/inline_example.py b/examples/inline_example.py index c97dea2..5164eec 100644 --- a/examples/inline_example.py +++ b/examples/inline_example.py @@ -1,4 +1,4 @@ -# This example show how to write an inline mode telegramt bot use pyTelegramBotAPI. +# This example show how to write an inline mode telegram bot use pyTelegramBotAPI. import telebot import time import sys diff --git a/examples/inline_keyboard_example.py b/examples/inline_keyboard_example.py new file mode 100644 index 0000000..0618eee --- /dev/null +++ b/examples/inline_keyboard_example.py @@ -0,0 +1,27 @@ +# This example show how to use inline keyboards and process button presses +import telebot +from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton + +TELEGRAM_TOKEN = '' + +bot = telebot.TeleBot(TELEGRAM_TOKEN) + +def gen_markup(): + markup = InlineKeyboardMarkup() + markup.row_width = 2 + markup.add(InlineKeyboardButton("Yes", callback_data=f"cb_yes"), + InlineKeyboardButton("No", callback_data=f"cb_no")) + return markup + +@bot.callback_query_handler(func=lambda call: True) +def callback_query(call): + if call.data == "cb_yes": + bot.answer_callback_query(call.id, "Answer is Yes") + elif call.data == "cb_no": + bot.answer_callback_query(call.id, "Answer is No") + +@bot.message_handler(func=lambda message: True) +def message_handler(message): + bot.send_message(message.chat.id, "Yes/no?", reply_markup=gen_markup()) + +bot.polling(none_stop=True) From bab9f7bbb9664b99edfc36b1926e5403fbb1a6f9 Mon Sep 17 00:00:00 2001 From: uburuntu Date: Thu, 6 Sep 2018 12:42:44 +0300 Subject: [PATCH 46/51] enh: reset requests.Session feature Need for proxy changing and other reconnection stuff --- telebot/apihelper.py | 4 ++-- telebot/util.py | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index ae72e37..a5e1df3 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -27,8 +27,8 @@ CONNECT_TIMEOUT = 3.5 READ_TIMEOUT = 9999 -def _get_req_session(): - return util.per_thread('req_session', lambda: requests.session()) +def _get_req_session(reset=False): + return util.per_thread('req_session', lambda: requests.session(), reset) def _make_request(token, method_name, method='get', params=None, files=None, base_url=API_URL): diff --git a/telebot/util.py b/telebot/util.py index f448d78..e16ebb5 100644 --- a/telebot/util.py +++ b/telebot/util.py @@ -248,13 +248,12 @@ def extract_arguments(text): return result.group(2) if is_command(text) else None -def per_thread(key, construct_value): - try: - return getattr(thread_local, key) - except AttributeError: +def per_thread(key, construct_value, reset=False): + if reset or not hasattr(thread_local, key): value = construct_value() setattr(thread_local, key, value) - return value + + return getattr(thread_local, key) def generate_random_token(): From 2c57c5c01c6f5700b9072bb5f7aa9a177fae871d Mon Sep 17 00:00:00 2001 From: eternnoir Date: Fri, 7 Sep 2018 16:02:03 +0800 Subject: [PATCH 47/51] Update version. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9a401bc..a52df53 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def read(filename): return file.read() setup(name='pyTelegramBotAPI', - version='3.6.5', + version='3.6.6', description='Python Telegram bot api. ', long_description=read('README.md'), long_description_content_type="text/markdown", From 891988be932e43eebf2e856de35cfbd083d71198 Mon Sep 17 00:00:00 2001 From: Rafael Medina Date: Sat, 15 Sep 2018 20:25:06 +0200 Subject: [PATCH 48/51] Added check for parse_mode in BaseInlineQueryResultCached. Should fix #571 --- telebot/types.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 7340c10..a7a74eb 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -1605,6 +1605,7 @@ class BaseInlineQueryResultCached(JsonSerializable): self.caption = None self.reply_markup = None self.input_message_content = None + self.parse_mode = None self.payload_dic = {} def to_json(self): @@ -1621,6 +1622,8 @@ class BaseInlineQueryResultCached(JsonSerializable): json_dict['reply_markup'] = self.reply_markup.to_dic() if self.input_message_content: json_dict['input_message_content'] = self.input_message_content.to_dic() + if self.parse_mode: + json_dict['parse_mode'] = self.parse_mode return json.dumps(json_dict) @@ -1636,8 +1639,8 @@ class InlineQueryResultCachedPhoto(BaseInlineQueryResultCached): self.caption = caption self.reply_markup = reply_markup self.input_message_content = input_message_content + self.parse_mode = parse_mode self.payload_dic['photo_file_id'] = photo_file_id - self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedGif(BaseInlineQueryResultCached): @@ -1652,8 +1655,8 @@ class InlineQueryResultCachedGif(BaseInlineQueryResultCached): self.caption = caption self.reply_markup = reply_markup self.input_message_content = input_message_content + self.parse_mode = parse_mode self.payload_dic['gif_file_id'] = gif_file_id - self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedMpeg4Gif(BaseInlineQueryResultCached): @@ -1668,8 +1671,8 @@ class InlineQueryResultCachedMpeg4Gif(BaseInlineQueryResultCached): self.caption = caption self.reply_markup = reply_markup self.input_message_content = input_message_content + self.parse_mode = parse_mode self.payload_dic['mpeg4_file_id'] = mpeg4_file_id - self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedSticker(BaseInlineQueryResultCached): @@ -1695,8 +1698,8 @@ class InlineQueryResultCachedDocument(BaseInlineQueryResultCached): self.caption = caption self.reply_markup = reply_markup self.input_message_content = input_message_content + self.parse_mode = parse_mode self.payload_dic['document_file_id'] = document_file_id - self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedVideo(BaseInlineQueryResultCached): @@ -1711,8 +1714,8 @@ class InlineQueryResultCachedVideo(BaseInlineQueryResultCached): self.caption = caption self.reply_markup = reply_markup self.input_message_content = input_message_content + self.parse_mode = parse_mode self.payload_dic['video_file_id'] = video_file_id - self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedVoice(BaseInlineQueryResultCached): @@ -1726,8 +1729,8 @@ class InlineQueryResultCachedVoice(BaseInlineQueryResultCached): self.caption = caption self.reply_markup = reply_markup self.input_message_content = input_message_content + self.parse_mode = parse_mode self.payload_dic['voice_file_id'] = voice_file_id - self.payload_dic['parse_mode'] = parse_mode class InlineQueryResultCachedAudio(BaseInlineQueryResultCached): @@ -1739,8 +1742,8 @@ class InlineQueryResultCachedAudio(BaseInlineQueryResultCached): self.caption = caption self.reply_markup = reply_markup self.input_message_content = input_message_content + self.parse_mode = parse_mode self.payload_dic['audio_file_id'] = audio_file_id - self.payload_dic['parse_mode'] = parse_mode # Games From b8f442d06bfadf35e3a74ba5b3247f9f58cd2fc3 Mon Sep 17 00:00:00 2001 From: FrankWang Date: Tue, 9 Oct 2018 17:32:27 +0800 Subject: [PATCH 49/51] Update Bots using this API --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9222b7b..fbe40e8 100644 --- a/README.md +++ b/README.md @@ -586,6 +586,7 @@ Get help. Discuss. Chat. * [LearnIt](https://t.me/LearnItbot)([link](https://github.com/tiagonapoli/LearnIt)) - A Telegram Bot created to help people to memorize other languages’ vocabulary. * [MusicQuiz_bot](https://t.me/MusicQuiz_bot) by [Etoneja](https://github.com/Etoneja) - Listen to audiosamles and try to name the performer of the song. * [Bot-Telegram-Shodan ](https://github.com/rubenleon/Bot-Telegram-Shodan) by [rubenleon](https://github.com/rubenleon) +* [MandangoBot](https://t.me/MandangoBot) by @Alvaricias - Bot for managing Marvel Strike Force alliances (only in spanish, atm). Want to have your bot listed here? Send a Telegram message to @eternnoir or @pevdh. From 584955962e3e304a44a62d45e878e892569a8779 Mon Sep 17 00:00:00 2001 From: khode-mohsen <44228057+khode-mohsen@users.noreply.github.com> Date: Fri, 19 Oct 2018 03:38:03 +0330 Subject: [PATCH 50/51] Update echo_bot.py add '#!/usr/bin/env' for direct execute --- examples/echo_bot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/echo_bot.py b/examples/echo_bot.py index f88d3bf..a3e8cbb 100644 --- a/examples/echo_bot.py +++ b/examples/echo_bot.py @@ -1,3 +1,5 @@ +#!/usr/bin/python + # This is a simple echo bot using the decorator mechanism. # It echoes any incoming text messages. From 76fc8fbe5ee76e6b79c1ea72c41010826b8704c1 Mon Sep 17 00:00:00 2001 From: khode-mohsen <44228057+khode-mohsen@users.noreply.github.com> Date: Fri, 19 Oct 2018 03:47:19 +0330 Subject: [PATCH 51/51] Update deep_linking.py add '#!/usr/bin/python' for direct execute --- examples/deep_linking.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/deep_linking.py b/examples/deep_linking.py index f92680f..3d7ec77 100644 --- a/examples/deep_linking.py +++ b/examples/deep_linking.py @@ -1,3 +1,5 @@ +#!/usr/bin/python + # This example shows how to implement deep linking (https://core.telegram.org/bots#deep-linking) # with the pyTelegramBotAPI. # Note: This is not a working, production-ready sample.