Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Badiboy
2020-04-24 18:19:55 +03:00
10 changed files with 569 additions and 125 deletions

View File

@@ -15,6 +15,7 @@
* [General use of the API](#general-use-of-the-api) * [General use of the API](#general-use-of-the-api)
* [Message handlers](#message-handlers) * [Message handlers](#message-handlers)
* [Callback Query handlers](#callback-query-handler) * [Callback Query handlers](#callback-query-handler)
* [Middleware handlers](#middleware-handler)
* [TeleBot](#telebot) * [TeleBot](#telebot)
* [Reply markup](#reply-markup) * [Reply markup](#reply-markup)
* [Inline Mode](#inline-mode) * [Inline Mode](#inline-mode)
@@ -223,6 +224,24 @@ In bot2.0 update. You can get `callback_query` in update object. In telebot use
def test_callback(call): def test_callback(call):
logger.info(call) logger.info(call)
``` ```
#### Middleware Handler
A middleware handler is a function that allows you to modify requests or the bot context as they pass through the
Telegram to the bot. You can imagine middleware as a chain of logic connection handled before any other handlers are
executed.
```python
@bot.middleware_handler(update_types=['message'])
def modify_message(bot_instance, message):
# modifying the message before it reaches any other handler
message.another_text = message.text + ':changed'
@bot.message_handler(commands=['start'])
def start(message):
# the message is already modified when it reaches message handler
assert message.another_text == message.text + ':changed'
```
There are other examples using middleware handler in the [examples/middleware](examples/middleware) directory.
#### TeleBot #### TeleBot
```python ```python

View File

@@ -3,3 +3,4 @@ pytest==3.0.2
requests==2.20.0 requests==2.20.0
six==1.9.0 six==1.9.0
wheel==0.24.0 wheel==0.24.0
redis==3.4.1

View File

@@ -20,6 +20,7 @@ setup(name='pyTelegramBotAPI',
install_requires=['requests', 'six'], install_requires=['requests', 'six'],
extras_require={ extras_require={
'json': 'ujson', 'json': 'ujson',
'redis': 'redis>=3.4.1'
}, },
classifiers=[ classifiers=[
'Development Status :: 5 - Production/Stable', 'Development Status :: 5 - Production/Stable',

View File

@@ -2,8 +2,6 @@
from __future__ import print_function from __future__ import print_function
import logging import logging
import os
import pickle
import re import re
import sys import sys
import threading import threading
@@ -23,6 +21,7 @@ logger.addHandler(console_output_handler)
logger.setLevel(logging.ERROR) logger.setLevel(logging.ERROR)
from telebot import apihelper, types, util from telebot import apihelper, types, util
from telebot.handler_backends import MemoryHandlerBackend, FileHandlerBackend
""" """
Module : telebot Module : telebot
@@ -43,64 +42,6 @@ class Handler:
return getattr(self, item) return getattr(self, item)
class Saver:
"""
Class for saving (next step|reply) handlers
"""
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():
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)
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)
@staticmethod
def dump_handlers(handlers, filename, file_mode="wb"):
dirs = filename.rsplit('/', maxsplit=1)[0]
os.makedirs(dirs, exist_ok=True)
with open(filename + ".tmp", file_mode) as file:
if (apihelper.CUSTOM_SERIALIZER is None):
pickle.dump(handlers, file)
else:
apihelper.CUSTOM_SERIALIZER.dump(handlers, file)
if os.path.isfile(filename):
os.remove(filename)
os.rename(filename + ".tmp", filename)
@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, "rb") as file:
if (apihelper.CUSTOM_SERIALIZER is None):
handlers = pickle.load(file)
else:
handlers = apihelper.CUSTOM_SERIALIZER.load(file)
if del_file_after_loading:
os.remove(filename)
return handlers
class TeleBot: class TeleBot:
""" This is TeleBot Class """ This is TeleBot Class
Methods: Methods:
@@ -117,6 +58,7 @@ class TeleBot:
sendVideoNote sendVideoNote
sendLocation sendLocation
sendChatAction sendChatAction
sendDice
getUserProfilePhotos getUserProfilePhotos
getUpdates getUpdates
getFile getFile
@@ -141,7 +83,10 @@ class TeleBot:
answerInlineQuery answerInlineQuery
""" """
def __init__(self, token, threaded=True, skip_pending=False, num_threads=2): def __init__(
self, token, threaded=True, skip_pending=False, num_threads=2,
next_step_backend=None, reply_backend=None
):
""" """
:param token: bot API token :param token: bot API token
:return: Telebot object. :return: Telebot object.
@@ -155,14 +100,13 @@ class TeleBot:
self.last_update_id = 0 self.last_update_id = 0
self.exc_info = None self.exc_info = None
# key: message_id, value: handler list self.next_step_backend = next_step_backend
self.reply_handlers = {} if not self.next_step_backend:
self.next_step_backend = MemoryHandlerBackend()
# key: chat_id, value: handler list self.reply_backend = reply_backend
self.next_step_handlers = {} if not self.reply_backend:
self.reply_backend = MemoryHandlerBackend()
self.next_step_saver = None
self.reply_saver = None
self.message_handlers = [] self.message_handlers = []
self.edited_message_handlers = [] self.edited_message_handlers = []
@@ -196,51 +140,89 @@ class TeleBot:
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"):
""" """
Enable saving next step handlers (by default saving disable) Enable saving next step handlers (by default saving disabled)
This function explicitly assigns FileHandlerBackend (instead of Saver) just to keep backward
compatibility whose purpose was to enable file saving capability for handlers. And the same
implementation is now available with FileHandlerBackend
Most probably this function should be deprecated in future major releases
:param delay: Delay between changes in handlers and saving :param delay: Delay between changes in handlers and saving
:param filename: Filename of save file :param filename: Filename of save file
""" """
self.next_step_saver = Saver(self.next_step_handlers, filename, delay) self.next_step_backend = FileHandlerBackend(self.next_step_backend.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"):
""" """
Enable saving reply handlers (by default saving disable) Enable saving reply handlers (by default saving disable)
This function explicitly assigns FileHandlerBackend (instead of Saver) just to keep backward
compatibility whose purpose was to enable file saving capability for handlers. And the same
implementation is now available with FileHandlerBackend
Most probably this function should be deprecated in future major releases
:param delay: Delay between changes in handlers and saving :param delay: Delay between changes in handlers and saving
:param filename: Filename of save file :param filename: Filename of save file
""" """
self.reply_saver = Saver(self.reply_handlers, filename, delay) self.reply_backend = FileHandlerBackend(self.reply_backend.handlers, filename, delay)
def disable_save_next_step_handlers(self): def disable_save_next_step_handlers(self):
""" """
Disable saving next step handlers (by default saving disable) Disable saving next step handlers (by default saving disable)
This function is left to keep backward compatibility whose purpose was to disable file saving capability
for handlers. For the same purpose, MemoryHandlerBackend is reassigned as a new next_step_backend backend
instead of FileHandlerBackend.
Most probably this function should be deprecated in future major releases
""" """
self.next_step_saver = None self.next_step_backend = MemoryHandlerBackend(self.next_step_backend.handlers)
def disable_save_reply_handlers(self): def disable_save_reply_handlers(self):
""" """
Disable saving next step handlers (by default saving disable) Disable saving next step handlers (by default saving disable)
This function is left to keep backward compatibility whose purpose was to disable file saving capability
for handlers. For the same purpose, MemoryHandlerBackend is reassigned as a new reply_backend backend
instead of FileHandlerBackend.
Most probably this function should be deprecated in future major releases
""" """
self.reply_saver = None self.reply_backend = MemoryHandlerBackend(self.reply_backend.handlers)
def load_next_step_handlers(self, filename="./.handler-saves/step.save", del_file_after_loading=True): def load_next_step_handlers(self, filename="./.handler-saves/step.save", del_file_after_loading=True):
""" """
Load next step handlers from save file Load next step handlers from save file
This function is left to keep backward compatibility whose purpose was to load handlers from file with the
help of FileHandlerBackend and is only recommended to use if next_step_backend was assigned as
FileHandlerBackend before entering this function
Most probably this function should be deprecated in future major releases
:param filename: Filename of the file where handlers was saved :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 :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) self.next_step_backend: FileHandlerBackend
self.next_step_backend.load_handlers(filename, del_file_after_loading)
def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True): def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True):
""" """
Load reply handlers from save file Load reply handlers from save file
This function is left to keep backward compatibility whose purpose was to load handlers from file with the
help of FileHandlerBackend and is only recommended to use if reply_backend was assigned as
FileHandlerBackend before entering this function
Most probably this function should be deprecated in future major releases
:param filename: Filename of the file where handlers was saved :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 :param del_file_after_loading: Is passed True, after loading save file will be deleted
""" """
self.reply_saver.load_handlers(filename) self.reply_backend: FileHandlerBackend
self.reply_backend.load_handlers(filename, del_file_after_loading)
def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None): 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) return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates)
@@ -397,7 +379,7 @@ class TeleBot:
def process_middlewares(self, update): def process_middlewares(self, update):
for update_type, middlewares in self.typed_middleware_handlers.items(): for update_type, middlewares in self.typed_middleware_handlers.items():
if hasattr(update, update_type): if getattr(update, update_type) is not None:
for typed_middleware_handler in middlewares: for typed_middleware_handler in middlewares:
typed_middleware_handler(self, getattr(update, update_type)) typed_middleware_handler(self, getattr(update, update_type))
@@ -665,6 +647,19 @@ class TeleBot:
""" """
return apihelper.delete_message(self.token, chat_id, message_id) return apihelper.delete_message(self.token, chat_id, message_id)
def send_dice(self, chat_id, disable_notification=None, reply_to_message_id=None, reply_markup=None):
"""
Use this method to send dices.
:param chat_id:
:param disable_notification:
:param reply_to_message_id:
:param reply_markup:
:return: Message
"""
return types.Message.de_json(
apihelper.send_dice(self.token, chat_id, disable_notification, reply_to_message_id, reply_markup)
)
def send_photo(self, chat_id, photo, caption=None, reply_to_message_id=None, reply_markup=None, def send_photo(self, chat_id, photo, caption=None, reply_to_message_id=None, reply_markup=None,
parse_mode=None, disable_notification=None): parse_mode=None, disable_notification=None):
""" """
@@ -937,7 +932,7 @@ class TeleBot:
def restrict_chat_member(self, chat_id, user_id, until_date=None, can_send_messages=None, def restrict_chat_member(self, chat_id, user_id, until_date=None, can_send_messages=None,
can_send_media_messages=None, can_send_other_messages=None, can_send_media_messages=None, can_send_other_messages=None,
can_add_web_page_previews=None): can_add_web_page_previews=None, can_invite_users=None):
""" """
Use this method to restrict a user in a supergroup. Use this method to restrict a user in a supergroup.
The bot must be an administrator in the supergroup for this to work and must have The bot must be an administrator in the supergroup for this to work and must have
@@ -956,11 +951,13 @@ class TeleBot:
use inline bots, implies can_send_media_messages use inline bots, implies can_send_media_messages
:param can_add_web_page_previews: Pass True, if the user may add web page previews to their messages, :param can_add_web_page_previews: Pass True, if the user may add web page previews to their messages,
implies can_send_media_messages implies can_send_media_messages
:param can_invite_users: Pass True, if the user is allowed to invite new users to the chat,
implies can_invite_users
:return: types.Message :return: types.Message
""" """
return apihelper.restrict_chat_member(self.token, chat_id, user_id, until_date, can_send_messages, return apihelper.restrict_chat_member(self.token, chat_id, user_id, until_date, can_send_messages,
can_send_media_messages, can_send_other_messages, can_send_media_messages, can_send_other_messages,
can_add_web_page_previews) can_add_web_page_previews, can_invite_users)
def promote_chat_member(self, chat_id, user_id, can_change_info=None, can_post_messages=None, def promote_chat_member(self, chat_id, user_id, can_change_info=None, can_post_messages=None,
can_edit_messages=None, can_delete_messages=None, can_invite_users=None, can_edit_messages=None, can_delete_messages=None, can_invite_users=None,
@@ -1401,12 +1398,7 @@ class TeleBot:
:param callback: The callback function to be called when a reply arrives. Must accept one `message` :param callback: The callback function to be called when a reply arrives. Must accept one `message`
parameter, which will contain the replied message. parameter, which will contain the replied message.
""" """
if message_id in self.reply_handlers.keys(): self.reply_backend.register_handler(message_id, Handler(callback, *args, **kwargs))
self.reply_handlers[message_id].append(Handler(callback, *args, **kwargs))
else:
self.reply_handlers[message_id] = [Handler(callback, *args, **kwargs)]
if self.reply_saver is not None:
self.reply_saver.start_save_timer()
def _notify_reply_handlers(self, new_messages): def _notify_reply_handlers(self, new_messages):
""" """
@@ -1416,14 +1408,9 @@ class TeleBot:
""" """
for message in new_messages: for message in new_messages:
if hasattr(message, "reply_to_message") and message.reply_to_message is not None: if hasattr(message, "reply_to_message") and message.reply_to_message is not None:
reply_msg_id = message.reply_to_message.message_id handlers = self.reply_backend.get_handlers(message.reply_to_message.message_id)
if reply_msg_id in self.reply_handlers.keys():
handlers = self.reply_handlers[reply_msg_id]
for handler in handlers: for handler in handlers:
self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"])
self.reply_handlers.pop(reply_msg_id)
if self.reply_saver is not None:
self.reply_saver.start_save_timer()
def register_next_step_handler(self, message, callback, *args, **kwargs): def register_next_step_handler(self, message, callback, *args, **kwargs):
""" """
@@ -1450,13 +1437,7 @@ class TeleBot:
:param args: Args to pass in callback func :param args: Args to pass in callback func
:param kwargs: Args to pass in callback func :param kwargs: Args to pass in callback func
""" """
if chat_id in self.next_step_handlers.keys(): self.next_step_backend.register_handler(chat_id, Handler(callback, *args, **kwargs))
self.next_step_handlers[chat_id].append(Handler(callback, *args, **kwargs))
else:
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()
def clear_step_handler(self, message): def clear_step_handler(self, message):
""" """
@@ -1473,10 +1454,7 @@ class TeleBot:
:param chat_id: The chat for which we want to clear next step handlers :param chat_id: The chat for which we want to clear next step handlers
""" """
self.next_step_handlers[chat_id] = [] self.next_step_backend.clear_handlers(chat_id)
if self.next_step_saver is not None:
self.next_step_saver.start_save_timer()
def clear_reply_handlers(self, message): def clear_reply_handlers(self, message):
""" """
@@ -1493,10 +1471,7 @@ class TeleBot:
:param message_id: The message id for which we want to clear reply handlers :param message_id: The message id for which we want to clear reply handlers
""" """
self.reply_handlers[message_id] = [] self.reply_backend.clear_handlers(message_id)
if self.reply_saver is not None:
self.reply_saver.start_save_timer()
def _notify_next_handlers(self, new_messages): def _notify_next_handlers(self, new_messages):
""" """
@@ -1504,22 +1479,14 @@ class TeleBot:
:param new_messages: :param new_messages:
:return: :return:
""" """
i = 0 for i, message in enumerate(new_messages):
while i < len(new_messages): need_pop = False
message = new_messages[i] handlers = self.next_step_backend.get_handlers(message.chat.id)
chat_id = message.chat.id
was_poped = False
if chat_id in self.next_step_handlers.keys():
handlers = self.next_step_handlers.pop(chat_id, None)
if handlers:
for handler in handlers: for handler in handlers:
need_pop = True
self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"])
if need_pop:
new_messages.pop(i) # removing message that detects with next_step_handler 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:
i += 1
@staticmethod @staticmethod
def _build_handler_dict(handler, **filters): def _build_handler_dict(handler, **filters):
@@ -1994,6 +1961,10 @@ class AsyncTeleBot(TeleBot):
def send_message(self, *args, **kwargs): def send_message(self, *args, **kwargs):
return TeleBot.send_message(self, *args, **kwargs) return TeleBot.send_message(self, *args, **kwargs)
@util.async_dec()
def send_dice(self, *args, **kwargs):
return TeleBot.send_dice(self, *args, **kwargs)
@util.async_dec() @util.async_dec()
def forward_message(self, *args, **kwargs): def forward_message(self, *args, **kwargs):
return TeleBot.forward_message(self, *args, **kwargs) return TeleBot.forward_message(self, *args, **kwargs)

View File

@@ -259,6 +259,18 @@ def forward_message(token, chat_id, from_chat_id, message_id, disable_notificati
return _make_request(token, method_url, params=payload) return _make_request(token, method_url, params=payload)
def send_dice(token, chat_id, disable_notification=None, reply_to_message_id=None, reply_markup=None):
method_url = r'sendDice'
payload = {'chat_id': chat_id}
if disable_notification:
payload['disable_notification'] = disable_notification
if reply_to_message_id:
payload['reply_to_message_id'] = reply_to_message_id
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
return _make_request(token, method_url, params=payload)
def send_photo(token, chat_id, photo, caption=None, reply_to_message_id=None, reply_markup=None, def send_photo(token, chat_id, photo, caption=None, reply_to_message_id=None, reply_markup=None,
parse_mode=None, disable_notification=None): parse_mode=None, disable_notification=None):
method_url = r'sendPhoto' method_url = r'sendPhoto'
@@ -557,7 +569,7 @@ def unban_chat_member(token, chat_id, user_id):
def restrict_chat_member(token, chat_id, user_id, until_date=None, can_send_messages=None, def restrict_chat_member(token, chat_id, user_id, until_date=None, can_send_messages=None,
can_send_media_messages=None, can_send_other_messages=None, can_send_media_messages=None, can_send_other_messages=None,
can_add_web_page_previews=None): can_add_web_page_previews=None, can_invite_users=None):
method_url = 'restrictChatMember' method_url = 'restrictChatMember'
payload = {'chat_id': chat_id, 'user_id': user_id} payload = {'chat_id': chat_id, 'user_id': user_id}
if until_date: if until_date:
@@ -570,6 +582,8 @@ def restrict_chat_member(token, chat_id, user_id, until_date=None, can_send_mess
payload['can_send_other_messages'] = can_send_other_messages payload['can_send_other_messages'] = can_send_other_messages
if can_add_web_page_previews: if can_add_web_page_previews:
payload['can_add_web_page_previews'] = can_add_web_page_previews payload['can_add_web_page_previews'] = can_add_web_page_previews
if can_invite_users:
payload['can_invite_users'] = can_invite_users
return _make_request(token, method_url, params=payload, method='post') return _make_request(token, method_url, params=payload, method='post')

145
telebot/handler_backends.py Normal file
View File

@@ -0,0 +1,145 @@
import os
import pickle
import threading
from telebot import apihelper
class HandlerBackend:
"""
Class for saving (next step|reply) handlers
"""
def __init__(self, handlers=None):
if handlers is None:
handlers = {}
self.handlers = handlers
def register_handler(self, handler_group_id, handler):
raise NotImplementedError()
def clear_handlers(self, handler_group_id):
raise NotImplementedError()
def get_handlers(self, handler_group_id):
raise NotImplementedError()
class MemoryHandlerBackend(HandlerBackend):
def register_handler(self, handler_group_id, handler):
if handler_group_id in self.handlers:
self.handlers[handler_group_id].append(handler)
else:
self.handlers[handler_group_id] = [handler]
def clear_handlers(self, handler_group_id):
self.handlers.pop(handler_group_id, [])
def get_handlers(self, handler_group_id):
return self.handlers.pop(handler_group_id, [])
class FileHandlerBackend(HandlerBackend):
def __init__(self, handlers=None, filename='./.handler-saves/handlers.save', delay=120):
super().__init__(handlers)
self.filename = filename
self.delay = delay
self.timer = threading.Timer(delay, self.save_handlers)
def register_handler(self, handler_group_id, handler):
if handler_group_id in self.handlers:
self.handlers[handler_group_id].append(handler)
else:
self.handlers[handler_group_id] = [handler]
self.start_save_timer()
def clear_handlers(self, handler_group_id):
self.handlers.pop(handler_group_id, [])
self.start_save_timer()
def get_handlers(self, handler_group_id):
handlers = self.handlers.pop(handler_group_id, [])
self.start_save_timer()
return handlers
def start_save_timer(self):
if not self.timer.is_alive():
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)
def load_handlers(self, filename=None, del_file_after_loading=True):
if not filename:
filename = self.filename
tmp = self.return_load_handlers(filename, del_file_after_loading=del_file_after_loading)
if tmp is not None:
self.handlers.update(tmp)
@staticmethod
def dump_handlers(handlers, filename, file_mode="wb"):
dirs = filename.rsplit('/', maxsplit=1)[0]
os.makedirs(dirs, exist_ok=True)
with open(filename + ".tmp", file_mode) as file:
if (apihelper.CUSTOM_SERIALIZER is None):
pickle.dump(handlers, file)
else:
apihelper.CUSTOM_SERIALIZER.dump(handlers, file)
if os.path.isfile(filename):
os.remove(filename)
os.rename(filename + ".tmp", filename)
@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, "rb") as file:
if (apihelper.CUSTOM_SERIALIZER is None):
handlers = pickle.load(file)
else:
handlers = apihelper.CUSTOM_SERIALIZER.load(file)
if del_file_after_loading:
os.remove(filename)
return handlers
class RedisHandlerBackend(HandlerBackend):
def __init__(self, handlers=None, host='localhost', port=6379, db=0, prefix='telebot'):
super().__init__(handlers)
from redis import Redis
self.prefix = prefix
self.redis = Redis(host, port, db)
def _key(self, handle_group_id):
return ':'.join((self.prefix, str(handle_group_id)))
def register_handler(self, handler_group_id, handler):
handlers = []
value = self.redis.get(self._key(handler_group_id))
if value:
handlers = pickle.loads(value)
handlers.append(handler)
self.redis.set(self._key(handler_group_id), pickle.dumps(handlers))
def clear_handlers(self, handler_group_id):
self.redis.delete(self._key(handler_group_id))
def get_handlers(self, handler_group_id):
handlers = []
value = self.redis.get(self._key(handler_group_id))
if value:
handlers = pickle.loads(value)
self.clear_handlers(handler_group_id)
return handlers

View File

@@ -293,6 +293,9 @@ class Message(JsonDeserializable):
if 'venue' in obj: if 'venue' in obj:
opts['venue'] = Venue.de_json(obj['venue']) opts['venue'] = Venue.de_json(obj['venue'])
content_type = 'venue' content_type = 'venue'
if 'dice' in obj:
opts['dice'] = Dice.de_json(obj['dice'])
content_type = 'dice'
if 'new_chat_members' in obj: if 'new_chat_members' in obj:
new_chat_members = [] new_chat_members = []
for member in obj['new_chat_members']: for member in obj['new_chat_members']:
@@ -397,6 +400,7 @@ class Message(JsonDeserializable):
self.location = None self.location = None
self.venue = None self.venue = None
self.animation = None self.animation = None
self.dice = None
self.new_chat_member = None # Deprecated since Bot API 3.0. Not processed anymore self.new_chat_member = None # Deprecated since Bot API 3.0. Not processed anymore
self.new_chat_members = None self.new_chat_members = None
self.left_chat_member = None self.left_chat_member = None
@@ -511,6 +515,24 @@ class MessageEntity(JsonDeserializable):
self.user = user self.user = user
class Dice(JsonSerializable, Dictionaryable, JsonDeserializable):
@classmethod
def de_json(cls, json_string):
if (json_string is None): return None
obj = cls.check_json(json_string)
value = obj['value']
return cls(value)
def __init__(self, value):
self.value = value
def to_json(self):
return json.dumps({'value': self.value})
def to_dic(self):
return {'value': self.value}
class PhotoSize(JsonDeserializable): class PhotoSize(JsonDeserializable):
@classmethod @classmethod
def de_json(cls, json_string): def de_json(cls, json_string):

View File

@@ -0,0 +1,257 @@
import sys
sys.path.append('../')
import os
import time
import pytest
import telebot
from telebot import types, MemoryHandlerBackend, FileHandlerBackend
from telebot.handler_backends import RedisHandlerBackend
@pytest.fixture()
def telegram_bot():
return telebot.TeleBot('', threaded=False)
@pytest.fixture
def private_chat():
return types.Chat(id=11, type='private')
@pytest.fixture
def user():
return types.User(id=10, is_bot=False, first_name='Some User')
@pytest.fixture()
def message(user, private_chat):
params = {'text': '/start'}
return types.Message(
message_id=1, from_user=user, date=None, chat=private_chat, content_type='text', options=params, json_string=""
)
@pytest.fixture()
def reply_to_message(user, private_chat, message):
params = {'text': '/start'}
reply_message = types.Message(
message_id=2, from_user=user, date=None, chat=private_chat, content_type='text', options=params, json_string=""
)
reply_message.reply_to_message = message
return reply_message
@pytest.fixture()
def update_type(message):
edited_message = None
channel_post = None
edited_channel_post = None
inline_query = None
chosen_inline_result = None
callback_query = None
shipping_query = None
pre_checkout_query = None
poll = None
return types.Update(1001234038283, message, edited_message, channel_post, edited_channel_post, inline_query,
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll)
@pytest.fixture()
def reply_to_message_update_type(reply_to_message):
edited_message = None
channel_post = None
edited_channel_post = None
inline_query = None
chosen_inline_result = None
callback_query = None
shipping_query = None
pre_checkout_query = None
poll = None
return types.Update(1001234038284, reply_to_message, edited_message, channel_post, edited_channel_post,
inline_query,
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll)
def next_handler(message):
message.text = 'entered next_handler'
def test_memory_handler_backend_default_backend(telegram_bot):
assert telegram_bot.reply_backend.__class__ == MemoryHandlerBackend
assert telegram_bot.next_step_backend.__class__ == MemoryHandlerBackend
def test_memory_handler_backend_register_next_step_handler(telegram_bot, private_chat, update_type):
@telegram_bot.message_handler(commands=['start'])
def start(message):
message.text = 'entered start'
telegram_bot.register_next_step_handler_by_chat_id(message.chat.id, next_handler)
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'
assert len(telegram_bot.next_step_backend.handlers[private_chat.id]) == 1
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered next_handler'
assert private_chat.id not in telegram_bot.next_step_backend.handlers
def test_memory_handler_backend_clear_next_step_handler(telegram_bot, private_chat, update_type):
@telegram_bot.message_handler(commands=['start'])
def start(message):
message.text = 'entered start'
telegram_bot.register_next_step_handler_by_chat_id(message.chat.id, next_handler)
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'
assert len(telegram_bot.next_step_backend.handlers[private_chat.id]) == 1
telegram_bot.clear_step_handler_by_chat_id(private_chat.id)
assert private_chat.id not in telegram_bot.next_step_backend.handlers
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'
def test_memory_handler_backend_register_reply_handler(telegram_bot, private_chat, update_type,
reply_to_message_update_type):
@telegram_bot.message_handler(commands=['start'])
def start(message):
message.text = 'entered start'
telegram_bot.register_for_reply_by_message_id(message.message_id, next_handler)
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'
assert len(telegram_bot.reply_backend.handlers[update_type.message.message_id]) == 1
telegram_bot.process_new_updates([reply_to_message_update_type])
assert reply_to_message_update_type.message.text == 'entered next_handler'
assert private_chat.id not in telegram_bot.reply_backend.handlers
def test_memory_handler_backend_clear_reply_handler(telegram_bot, private_chat, update_type,
reply_to_message_update_type):
@telegram_bot.message_handler(commands=['start'])
def start(message):
message.text = 'entered start'
telegram_bot.register_for_reply_by_message_id(message.message_id, next_handler)
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'
assert len(telegram_bot.reply_backend.handlers[update_type.message.message_id]) == 1
telegram_bot.clear_reply_handlers_by_message_id(update_type.message.message_id)
assert update_type.message.message_id not in telegram_bot.reply_backend.handlers
telegram_bot.process_new_updates([reply_to_message_update_type])
assert reply_to_message_update_type.message.text == 'entered start'
def test_file_handler_backend_register_next_step_handler(telegram_bot, private_chat, update_type):
telegram_bot.next_step_backend=FileHandlerBackend(filename='./.handler-saves/step1.save', delay=0.1)
@telegram_bot.message_handler(commands=['start'])
def start(message):
message.text = 'entered start'
telegram_bot.register_next_step_handler_by_chat_id(message.chat.id, next_handler)
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'
time.sleep(0.2)
assert os.path.exists(telegram_bot.next_step_backend.filename)
assert len(telegram_bot.next_step_backend.handlers[private_chat.id]) == 1
telegram_bot.next_step_backend.handlers = {}
telegram_bot.next_step_backend.load_handlers()
assert len(telegram_bot.next_step_backend.handlers[private_chat.id]) == 1
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered next_handler'
assert private_chat.id not in telegram_bot.next_step_backend.handlers
time.sleep(0.2)
if os.path.exists(telegram_bot.next_step_backend.filename):
os.remove(telegram_bot.next_step_backend.filename)
def test_file_handler_backend_clear_next_step_handler(telegram_bot, private_chat, update_type):
telegram_bot.next_step_backend=FileHandlerBackend(filename='./.handler-saves/step2.save', delay=0.1)
@telegram_bot.message_handler(commands=['start'])
def start(message):
message.text = 'entered start'
telegram_bot.register_next_step_handler_by_chat_id(message.chat.id, next_handler)
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'
assert len(telegram_bot.next_step_backend.handlers[private_chat.id]) == 1
time.sleep(0.2)
assert os.path.exists(telegram_bot.next_step_backend.filename)
telegram_bot.clear_step_handler_by_chat_id(private_chat.id)
time.sleep(0.2)
telegram_bot.next_step_backend.load_handlers()
assert private_chat.id not in telegram_bot.next_step_backend.handlers
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'
time.sleep(0.2)
if os.path.exists(telegram_bot.next_step_backend.filename):
os.remove(telegram_bot.next_step_backend.filename)
def test_redis_handler_backend_register_next_step_handler(telegram_bot, private_chat, update_type):
telegram_bot.next_step_backend = RedisHandlerBackend(prefix='pyTelegramBotApi:step_backend1')
@telegram_bot.message_handler(commands=['start'])
def start(message):
message.text = 'entered start'
telegram_bot.register_next_step_handler_by_chat_id(message.chat.id, next_handler)
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered next_handler'
def test_redis_handler_backend_clear_next_step_handler(telegram_bot, private_chat, update_type):
telegram_bot.next_step_backend = RedisHandlerBackend(prefix='pyTelegramBotApi:step_backend2')
@telegram_bot.message_handler(commands=['start'])
def start(message):
message.text = 'entered start'
telegram_bot.register_next_step_handler_by_chat_id(message.chat.id, next_handler)
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'
telegram_bot.clear_step_handler_by_chat_id(private_chat.id)
telegram_bot.process_new_updates([update_type])
assert update_type.message.text == 'entered start'

View File

@@ -241,6 +241,12 @@ class TestTeleBot:
ret_msg = tb.send_message(CHAT_ID, text) ret_msg = tb.send_message(CHAT_ID, text)
assert ret_msg.message_id assert ret_msg.message_id
def test_send_dice(self):
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_dice(CHAT_ID)
assert ret_msg.message_id
assert ret_msg.content_type == 'dice'
def test_send_message_dis_noti(self): def test_send_message_dis_noti(self):
text = 'CI Test Message' text = 'CI Test Message'
tb = telebot.TeleBot(TOKEN) tb = telebot.TeleBot(TOKEN)

View File

@@ -17,6 +17,14 @@ def test_json_message():
assert msg.text == 'HIHI' assert msg.text == 'HIHI'
def test_json_message_with_dice():
jsonstring = r'{"message_id":5560,"from":{"id":879343317,"is_bot":false,"first_name":"George","last_name":"Forse","username":"dr_fxrse","language_code":"ru"},"chat":{"id":879343317,"first_name":"George","last_name":"Forse","username":"dr_fxrse","type":"private"},"date":1586926330,"dice":{"value":4}}'
msg = types.Message.de_json(jsonstring)
assert msg.content_type == 'dice'
assert isinstance(msg.dice, types.Dice)
assert msg.dice.value == 4
def test_json_message_group(): def test_json_message_group():
json_string = r'{"message_id":10,"from":{"id":12345,"first_name":"g","last_name":"G","username":"GG","is_bot":true},"chat":{"id":-866,"type":"private","title":"\u4ea4"},"date":1435303157,"text":"HIHI"}' json_string = r'{"message_id":10,"from":{"id":12345,"first_name":"g","last_name":"G","username":"GG","is_bot":true},"chat":{"id":-866,"type":"private","title":"\u4ea4"},"date":1435303157,"text":"HIHI"}'
msg = types.Message.de_json(json_string) msg = types.Message.de_json(json_string)