Compare commits

...

10 Commits

Author SHA1 Message Date
Badiboy 7c9b01b10a
Merge pull request #1722 from Badiboy/master
Handlers and Middlewares processing union
2022-09-24 22:16:33 +03:00
Badiboy b3993bb019 Merge remote-tracking branch 'upstream/master' 2022-09-24 22:14:45 +03:00
Badiboy 36b889feab
Merge pull request #1736 from AntonGlyzin/antonglyzin-shopbotlist
Added a new bot to the list
2022-09-24 16:06:33 +03:00
Anton d943f40643 Added a new bot to the list 2022-09-24 15:33:11 +03:00
_run dafafd2ad2
Merge pull request #1735 from coder2020official/class_params
⚙️ Added some frequent parameters to classes(see full list)
2022-09-23 22:06:09 +04:00
coder2020official e002484a9b ⚙️ Added some frequent parameters to classes(see full list)
Added:
- disable_web_page_preview
- disable_notification
- protect_content
- allow_sending_without_reply
2022-09-23 21:52:40 +04:00
Badiboy 52e09637c2 Fix: do not call handler in one more task 2022-09-17 23:17:07 +03:00
Badiboy e7a96ec2ed Rename also in Async 2022-09-17 14:09:05 +03:00
Badiboy 598de25b6d Rename _check_middlewares to _get_middlewares 2022-09-17 12:55:55 +03:00
Badiboy da639dd1f6 Handlers and Middlewares processing union
Call for handlers now union in a single function for future extension.

Plus minor fixes in storages.
2022-09-17 11:57:12 +03:00
8 changed files with 353 additions and 128 deletions

View File

@ -885,5 +885,6 @@ Here are some examples of template:
* [Gugumoe-bot](http://t.me/gugumoe_bot) ([source](https://github.com/GooGuJiang/Gugumoe-bot)) by [咕谷酱](https://gmoe.cc) GuXiaoJiang is a multi-functional robot, such as OSU game information query, IP test, animation screenshot search and other functions.
* [Feedback-bot](https://github.com/coder2020official/feedbackbot) A feedback bot for user-admin communication. Made on AsyncTeleBot, using [template](https://github.com/coder2020official/asynctelebot_template).
* [TeleServ](https://github.com/ablakely/TeleServ) by [ablakely](https://github.com/ablakely) This is a Telegram to IRC bridge which links as an IRC server and makes Telegram users appear as native IRC users.
* [Simple Store Bot](https://github.com/AntonGlyzin/myshopbot) by [Anton Glyzin](https://github.com/AntonGlyzin) This is a simple telegram-store with an admin panel. Designed according to a template.
**Want to have your bot listed here? Just make a pull request. Only bots with public source code are accepted.**

View File

@ -131,6 +131,18 @@ class TeleBot:
:param use_class_middlewares: Use class middlewares, defaults to False
:type use_class_middlewares: :obj:`bool`, optional
:param disable_web_page_preview: Default value for disable_web_page_preview, defaults to None
:type disable_web_page_preview: :obj:`bool`, optional
:param disable_notification: Default value for disable_notification, defaults to None
:type disable_notification: :obj:`bool`, optional
:param protect_content: Default value for protect_content, defaults to None
:type protect_content: :obj:`bool`, optional
:param allow_sending_without_reply: Default value for allow_sending_without_reply, defaults to None
:type allow_sending_without_reply: :obj:`bool`, optional
"""
def __init__(
@ -139,18 +151,32 @@ class TeleBot:
next_step_backend: Optional[HandlerBackend]=None, reply_backend: Optional[HandlerBackend]=None,
exception_handler: Optional[ExceptionHandler]=None, last_update_id: Optional[int]=0,
suppress_middleware_excepions: Optional[bool]=False, state_storage: Optional[StateStorageBase]=StateMemoryStorage(),
use_class_middlewares: Optional[bool]=False
use_class_middlewares: Optional[bool]=False,
disable_web_page_preview: Optional[bool]=None,
disable_notification: Optional[bool]=None,
protect_content: Optional[bool]=None,
allow_sending_without_reply: Optional[bool]=None
):
self.token = token
self.parse_mode = parse_mode
self.update_listener = []
self.skip_pending = skip_pending
self.suppress_middleware_excepions = suppress_middleware_excepions
self.__stop_polling = threading.Event()
# update-related
self.token = token
self.skip_pending = skip_pending # backward compatibility
self.last_update_id = last_update_id
# propertys
self.suppress_middleware_excepions = suppress_middleware_excepions
self.parse_mode = parse_mode
self.disable_web_page_preview = disable_web_page_preview
self.disable_notification = disable_notification
self.protect_content = protect_content
self.allow_sending_without_reply = allow_sending_without_reply
# threading-related
self.__stop_polling = threading.Event()
self.exc_info = None
# states & register_next_step_handler
self.current_states = state_storage
self.next_step_backend = next_step_backend
if not self.next_step_backend:
self.next_step_backend = MemoryHandlerBackend()
@ -159,8 +185,9 @@ class TeleBot:
if not self.reply_backend:
self.reply_backend = MemoryHandlerBackend()
# handlers
self.exception_handler = exception_handler
self.update_listener = []
self.message_handlers = []
self.edited_message_handlers = []
self.channel_post_handlers = []
@ -178,8 +205,7 @@ class TeleBot:
self.custom_filters = {}
self.state_handlers = []
self.current_states = state_storage
# middlewares
self.use_class_middlewares = use_class_middlewares
if apihelper.ENABLE_MIDDLEWARE and not use_class_middlewares:
self.typed_middleware_handlers = {
@ -205,6 +231,8 @@ class TeleBot:
'You are using class based middlewares while having ENABLE_MIDDLEWARE set to True. This is not recommended.'
)
self.middlewares = [] if use_class_middlewares else None
# threads
self.threaded = threaded
if self.threaded:
self.worker_pool = util.ThreadPool(self, num_threads=num_threads)
@ -469,6 +497,7 @@ class TeleBot:
webhook_url = "{}://{}:{}/{}".format(protocol, listen, port, url_path)
if certificate and certificate_key:
# noinspection PyTypeChecker
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_ctx.load_cert_chain(certificate, certificate_key)
@ -1087,11 +1116,6 @@ class TeleBot:
def _exec_task(self, task, *args, **kwargs):
if kwargs:
if kwargs.pop('task_type', "") == 'handler':
if kwargs.pop('pass_bot', False):
kwargs['bot'] = self
if self.threaded:
self.worker_pool.put(task, *args, **kwargs)
else:
@ -1444,6 +1468,10 @@ class TeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_web_page_preview = self.disable_web_page_preview if (disable_web_page_preview is None) else disable_web_page_preview
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
apihelper.send_message(
@ -1482,6 +1510,9 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
return types.Message.de_json(
apihelper.forward_message(self.token, chat_id, from_chat_id, message_id, disable_notification, timeout, protect_content))
@ -1544,6 +1575,11 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.MessageID.de_json(
apihelper.copy_message(self.token, chat_id, from_chat_id, message_id, caption, parse_mode, caption_entities,
disable_notification, reply_to_message_id, allow_sending_without_reply, reply_markup,
@ -1621,6 +1657,10 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
apihelper.send_dice(
self.token, chat_id, emoji, disable_notification, reply_to_message_id,
@ -1684,6 +1724,9 @@ class TeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
apihelper.send_photo(
@ -1769,6 +1812,9 @@ class TeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
apihelper.send_audio(
@ -1837,6 +1883,9 @@ class TeleBot:
:return: On success, the sent Message is returned.
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
apihelper.send_voice(
@ -1917,6 +1966,10 @@ class TeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
if data and not(document):
# function typo miss compatibility
document = data
@ -1981,9 +2034,14 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
if data and not(sticker):
# function typo miss compatibility
sticker = data
return types.Message.de_json(
apihelper.send_data(
self.token, chat_id, sticker, 'sticker',
@ -2071,6 +2129,10 @@ class TeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
if data and not(video):
# function typo miss compatibility
video = data
@ -2157,6 +2219,9 @@ class TeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
apihelper.send_animation(
@ -2224,6 +2289,10 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
apihelper.send_video_note(
self.token, chat_id, data, duration, length, reply_to_message_id, reply_markup,
@ -2270,6 +2339,10 @@ class TeleBot:
:return: On success, an array of Messages that were sent is returned.
:rtype: List[types.Message]
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
result = apihelper.send_media_group(
self.token, chat_id, media, disable_notification, reply_to_message_id, timeout,
allow_sending_without_reply, protect_content)
@ -2338,6 +2411,10 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
apihelper.send_location(
self.token, chat_id, latitude, longitude, live_period,
@ -2509,6 +2586,10 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
apihelper.send_venue(
self.token, chat_id, latitude, longitude, title, address, foursquare_id, foursquare_type,
@ -2571,6 +2652,10 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
apihelper.send_contact(
self.token, chat_id, phone_number, first_name, last_name, vcard,
@ -3322,6 +3407,8 @@ class TeleBot:
:return: True on success.
:rtype: :obj:`bool`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
return apihelper.pin_chat_message(self.token, chat_id, message_id, disable_notification)
def unpin_chat_message(self, chat_id: Union[int, str], message_id: Optional[int]=None) -> bool:
@ -3403,6 +3490,7 @@ class TeleBot:
:rtype: :obj:`types.Message` or :obj:`bool`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_web_page_preview = self.disable_web_page_preview if (disable_web_page_preview is None) else disable_web_page_preview
result = apihelper.edit_message_text(self.token, text, chat_id, message_id, inline_message_id, parse_mode,
entities, disable_web_page_preview, reply_markup)
@ -3515,6 +3603,10 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
result = apihelper.send_game(
self.token, chat_id, game_short_name, disable_notification,
reply_to_message_id, reply_markup, timeout,
@ -3717,6 +3809,10 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
result = apihelper.send_invoice(
self.token, chat_id, title, description, invoice_payload, provider_token,
currency, prices, start_parameter, photo_url, photo_size, photo_width,
@ -3918,6 +4014,10 @@ class TeleBot:
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
if isinstance(question, types.Poll):
raise RuntimeError("The send_poll signature was changed, please see send_poll function details.")
@ -4790,8 +4890,14 @@ class TeleBot:
if not isinstance(regexp, str):
logger.error(f"{method_name}: Regexp filter should be string. Not able to use the supplied type.")
def message_handler(self, commands: Optional[List[str]]=None, regexp: Optional[str]=None, func: Optional[Callable]=None,
content_types: Optional[List[str]]=None, chat_types: Optional[List[str]]=None, **kwargs):
def message_handler(
self,
commands: Optional[List[str]]=None,
regexp: Optional[str]=None,
func: Optional[Callable]=None,
content_types: Optional[List[str]]=None,
chat_types: Optional[List[str]]=None,
**kwargs):
"""
Handles New incoming message of any kind - text, photo, sticker, etc.
As a parameter to the decorator function, it passes :class:`telebot.types.Message` object.
@ -5875,7 +5981,7 @@ class TeleBot:
return False
# middleware check-up method
def _check_middleware(self, update_type):
def _get_middlewares(self, update_type):
"""
Check middleware
@ -5889,100 +5995,115 @@ class TeleBot:
def _run_middlewares_and_handler(self, message, handlers, middlewares, update_type):
"""
This class is made to run handler and middleware in queue.
This method is made to run handlers and middlewares in queue.
:param handler: handler that should be executed.
:param middleware: middleware that should be executed.
:param message: received message (update part) to process with handlers and/or middlewares
:param handlers: all created handlers (not filtered)
:param middlewares: middlewares that should be executed (already filtered)
:param update_type: handler/update type (Update field name)
:return:
"""
data = {}
params =[]
handler_error = None
skip_handlers = False
if middlewares:
for middleware in middlewares:
if middleware.update_sensitive:
if hasattr(middleware, f'pre_process_{update_type}'):
result = getattr(middleware, f'pre_process_{update_type}')(message, data)
else:
logger.error('Middleware {} does not have pre_process_{} method. pre_process function execution was skipped.'.format(middleware.__class__.__name__, update_type))
result = None
else:
result = middleware.pre_process(message, data)
# We will break this loop if CancelUpdate is returned
# Also, we will not run other middlewares
if isinstance(result, CancelUpdate):
return
elif isinstance(result, SkipHandler):
skip_handlers = True
if handlers and not(skip_handlers):
try:
if not self.use_class_middlewares:
if handlers:
for handler in handlers:
process_handler = self._test_message_handler(handler, message)
if not process_handler: continue
for i in inspect.signature(handler['function']).parameters:
params.append(i)
if len(params) == 1:
handler['function'](message)
elif "data" in params:
if len(params) == 2:
handler['function'](message, data)
elif len(params) == 3:
handler['function'](message, data=data, bot=self)
if self._test_message_handler(handler, message):
if handler.get('pass_bot', False):
handler['function'](message, bot = self)
else:
logger.error("It is not allowed to pass data and values inside data to the handler. Check your handler: {}".format(handler['function']))
return
else:
data_copy = data.copy()
for key in list(data_copy):
# remove data from data_copy if handler does not accept it
if key not in params:
del data_copy[key]
if handler.get('pass_bot'):
data_copy["bot"] = self
if len(data_copy) > len(params) - 1: # remove the message parameter
logger.error("You are passing more parameters than the handler needs. Check your handler: {}".format(handler['function']))
return
handler["function"](message, **data_copy)
break
except Exception as e:
handler_error = e
if self.exception_handler:
self.exception_handler.handle(e)
else:
logging.error(str(e))
logger.debug("Exception traceback:\n%s", traceback.format_exc())
handler['function'](message)
break
else:
data = {}
params =[]
handler_error = None
skip_handlers = False
if middlewares:
for middleware in middlewares:
if middleware.update_sensitive:
if hasattr(middleware, f'post_process_{update_type}'):
getattr(middleware, f'post_process_{update_type}')(message, data, handler_error)
if middlewares:
for middleware in middlewares:
if middleware.update_sensitive:
if hasattr(middleware, f'pre_process_{update_type}'):
result = getattr(middleware, f'pre_process_{update_type}')(message, data)
else:
logger.error('Middleware {} does not have pre_process_{} method. pre_process function execution was skipped.'.format(middleware.__class__.__name__, update_type))
result = None
else:
logger.error("Middleware: {} does not have post_process_{} method. Post process function was not executed.".format(middleware.__class__.__name__, update_type))
else:
middleware.post_process(message, data, handler_error)
result = middleware.pre_process(message, data)
# We will break this loop if CancelUpdate is returned
# Also, we will not run other middlewares
if isinstance(result, CancelUpdate):
return
elif isinstance(result, SkipHandler):
skip_handlers = True
if handlers and not(skip_handlers):
try:
for handler in handlers:
process_handler = self._test_message_handler(handler, message)
if not process_handler: continue
for i in inspect.signature(handler['function']).parameters:
params.append(i)
if len(params) == 1:
handler['function'](message)
elif "data" in params:
if len(params) == 2:
handler['function'](message, data)
elif len(params) == 3:
handler['function'](message, data=data, bot=self)
else:
logger.error("It is not allowed to pass data and values inside data to the handler. Check your handler: {}".format(handler['function']))
return
else:
data_copy = data.copy()
for key in list(data_copy):
# remove data from data_copy if handler does not accept it
if key not in params:
del data_copy[key]
if handler.get('pass_bot'):
data_copy["bot"] = self
if len(data_copy) > len(params) - 1: # remove the message parameter
logger.error("You are passing more parameters than the handler needs. Check your handler: {}".format(handler['function']))
return
handler["function"](message, **data_copy)
break
except Exception as e:
handler_error = e
if self.exception_handler:
self.exception_handler.handle(e)
else:
logger.error(str(e))
logger.debug("Exception traceback:\n%s", traceback.format_exc())
if middlewares:
for middleware in middlewares:
if middleware.update_sensitive:
if hasattr(middleware, f'post_process_{update_type}'):
getattr(middleware, f'post_process_{update_type}')(message, data, handler_error)
else:
logger.error("Middleware: {} does not have post_process_{} method. Post process function was not executed.".format(middleware.__class__.__name__, update_type))
else:
middleware.post_process(message, data, handler_error)
def _notify_command_handlers(self, handlers, new_messages, update_type):
"""
Notifies command handlers.
:param handlers:
:param new_messages:
:param handlers: all created handlers
:param new_messages: received messages to proceed
:param update_type: handler/update type (Update fields)
:return:
"""
if not(handlers) and not(self.use_class_middlewares):
return
if self.use_class_middlewares:
middlewares = self._get_middlewares(update_type)
else:
middlewares = None
for message in new_messages:
if not self.use_class_middlewares:
for message_handler in handlers:
if self._test_message_handler(message_handler, message):
self._exec_task(message_handler['function'], message, pass_bot=message_handler['pass_bot'], task_type='handler')
break
else:
middleware = self._check_middleware(update_type)
self._exec_task(self._run_middlewares_and_handler, message, handlers=handlers, middlewares=middleware, update_type=update_type)
return
self._exec_task(
self._run_middlewares_and_handler,
message,
handlers=handlers,
middlewares=middlewares,
update_type=update_type)

View File

@ -3,7 +3,6 @@ from datetime import datetime
import logging
import re
import time
import traceback
from typing import Any, Awaitable, Callable, List, Optional, Union
@ -99,21 +98,45 @@ class AsyncTeleBot:
:param state_storage: Storage for states, defaults to StateMemoryStorage()
:type state_storage: :class:`telebot.asyncio_storage.StateMemoryStorage`, optional
:param disable_web_page_preview: Default value for disable_web_page_preview, defaults to None
:type disable_web_page_preview: :obj:`bool`, optional
:param disable_notification: Default value for disable_notification, defaults to None
:type disable_notification: :obj:`bool`, optional
:param protect_content: Default value for protect_content, defaults to None
:type protect_content: :obj:`bool`, optional
:param allow_sending_without_reply: Default value for allow_sending_without_reply, defaults to None
:type allow_sending_without_reply: :obj:`bool`, optional
"""
def __init__(self, token: str, parse_mode: Optional[str]=None, offset: Optional[int]=None,
exception_handler: Optional[ExceptionHandler]=None, state_storage: Optional[StateStorageBase]=StateMemoryStorage()) -> None:
exception_handler: Optional[ExceptionHandler]=None,
state_storage: Optional[StateStorageBase]=StateMemoryStorage(),
disable_web_page_preview: Optional[bool]=None,
disable_notification: Optional[bool]=None,
protect_content: Optional[bool]=None,
allow_sending_without_reply: Optional[bool]=None) -> None:
# update-related
self.token = token
self.offset = offset
self.token = token
# properties
self.parse_mode = parse_mode
self.disable_web_page_preview = disable_web_page_preview
self.disable_notification = disable_notification
self.protect_content = protect_content
self.allow_sending_without_reply = allow_sending_without_reply
# states
self.current_states = state_storage
# handlers
self.update_listener = []
self.exception_handler = exception_handler
self.message_handlers = []
self.edited_message_handlers = []
self.channel_post_handlers = []
@ -130,9 +153,6 @@ class AsyncTeleBot:
self.chat_join_request_handlers = []
self.custom_filters = {}
self.state_handlers = []
self.current_states = state_storage
self.middlewares = []
async def close_session(self):
@ -339,7 +359,8 @@ class AsyncTeleBot:
await self.close_session()
logger.warning('Polling is stopped.')
def _loop_create_task(self, coro):
@staticmethod
def _loop_create_task(coro):
return asyncio.create_task(coro)
async def _process_updates(self, handlers, messages, update_type):
@ -351,12 +372,22 @@ class AsyncTeleBot:
:return:
"""
tasks = []
middlewares = await self._get_middlewares(update_type)
for message in messages:
middleware = await self.process_middlewares(update_type)
tasks.append(self._run_middlewares_and_handlers(handlers, message, middleware, update_type))
tasks.append(self._run_middlewares_and_handlers(message, handlers, middlewares, update_type))
await asyncio.gather(*tasks)
async def _run_middlewares_and_handlers(self, handlers, message, middlewares, update_type):
async def _run_middlewares_and_handlers(self, message, handlers, middlewares, update_type):
"""
This method is made to run handlers and middlewares in queue.
:param message: received message (update part) to process with handlers and/or middlewares
:param handlers: all created handlers (not filtered)
:param middlewares: middlewares that should be executed (already filtered)
:param update_type: handler/update type (Update field name)
:return:
"""
handler_error = None
data = {}
skip_handlers = False
@ -426,7 +457,7 @@ class AsyncTeleBot:
else:
logger.error('Middleware {} does not have post_process_{} method. post_process function execution was skipped.'.format(middleware.__class__.__name__, update_type))
else: await middleware.post_process(message, data, handler_error)
# update handling
async def process_new_updates(self, updates: List[types.Update]):
"""
Process new updates.
@ -615,7 +646,7 @@ class AsyncTeleBot:
"""
await self._process_updates(self.chat_join_request_handlers, chat_join_request, 'chat_join_request')
async def process_middlewares(self, update_type):
async def _get_middlewares(self, update_type):
"""
:meta private:
"""
@ -2277,6 +2308,10 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_web_page_preview = self.disable_web_page_preview if (disable_web_page_preview is None) else disable_web_page_preview
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
await asyncio_helper.send_message(
@ -2315,6 +2350,9 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
return types.Message.de_json(
await asyncio_helper.forward_message(self.token, chat_id, from_chat_id, message_id, disable_notification, timeout, protect_content))
@ -2377,6 +2415,9 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.MessageID.de_json(
await asyncio_helper.copy_message(self.token, chat_id, from_chat_id, message_id, caption, parse_mode, caption_entities,
@ -2455,6 +2496,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
await asyncio_helper.send_dice(
self.token, chat_id, emoji, disable_notification, reply_to_message_id,
@ -2517,6 +2562,9 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
await asyncio_helper.send_photo(
@ -2601,6 +2649,9 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
await asyncio_helper.send_audio(
@ -2668,6 +2719,9 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
await asyncio_helper.send_voice(
@ -2747,6 +2801,10 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
if data and not(document):
# function typo miss compatibility
document = data
@ -2807,6 +2865,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
if data and not(sticker):
# function typo miss compatibility
logger.warning("send_sticker: data parameter is deprecated. Use sticker instead.")
@ -2898,6 +2960,10 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
if data and not(video):
# function typo miss compatibility
logger.warning("send_sticker: data parameter is deprecated. Use video instead.")
@ -2985,6 +3051,9 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
await asyncio_helper.send_animation(
@ -3051,6 +3120,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
await asyncio_helper.send_video_note(
self.token, chat_id, data, duration, length, reply_to_message_id, reply_markup,
@ -3096,6 +3169,10 @@ class AsyncTeleBot:
:return: On success, an array of Messages that were sent is returned.
:rtype: List[types.Message]
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
result = await asyncio_helper.send_media_group(
self.token, chat_id, media, disable_notification, reply_to_message_id, timeout,
allow_sending_without_reply, protect_content)
@ -3163,6 +3240,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
await asyncio_helper.send_location(
self.token, chat_id, latitude, longitude, live_period,
@ -3333,6 +3414,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
await asyncio_helper.send_venue(
self.token, chat_id, latitude, longitude, title, address, foursquare_id, foursquare_type,
@ -3394,6 +3479,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
return types.Message.de_json(
await asyncio_helper.send_contact(
self.token, chat_id, phone_number, first_name, last_name, vcard,
@ -4154,6 +4243,8 @@ class AsyncTeleBot:
:return: True on success.
:rtype: :obj:`bool`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
return await asyncio_helper.pin_chat_message(self.token, chat_id, message_id, disable_notification)
async def unpin_chat_message(self, chat_id: Union[int, str], message_id: Optional[int]=None) -> bool:
@ -4235,6 +4326,7 @@ class AsyncTeleBot:
:rtype: :obj:`types.Message` or :obj:`bool`
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
disable_web_page_preview = self.disable_web_page_preview if (disable_web_page_preview is None) else disable_web_page_preview
result = await asyncio_helper.edit_message_text(self.token, text, chat_id, message_id, inline_message_id, parse_mode,
entities, disable_web_page_preview, reply_markup)
@ -4347,6 +4439,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
result = await asyncio_helper.send_game(
self.token, chat_id, game_short_name, disable_notification,
reply_to_message_id, reply_markup, timeout,
@ -4548,6 +4644,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
result = await asyncio_helper.send_invoice(
self.token, chat_id, title, description, invoice_payload, provider_token,
currency, prices, start_parameter, photo_url, photo_size, photo_width,
@ -4749,12 +4849,14 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
protect_content = self.protect_content if (protect_content is None) else protect_content
allow_sending_without_reply = self.allow_sending_without_reply if (allow_sending_without_reply is None) else allow_sending_without_reply
explanation_parse_mode = self.parse_mode if (explanation_parse_mode is None) else explanation_parse_mode
if isinstance(question, types.Poll):
raise RuntimeError("The send_poll signature was changed, please see send_poll function details.")
explanation_parse_mode = self.parse_mode if (explanation_parse_mode is None) else explanation_parse_mode
return types.Message.de_json(
await asyncio_helper.send_poll(
self.token, chat_id,

View File

@ -1,6 +1,5 @@
"""
This file is used by TeleBot.run_webhooks() function.
Fastapi is required to run this script.
"""
@ -15,15 +14,11 @@ try:
except ImportError:
fastapi_installed = False
from telebot.types import Update
from typing import Optional
class SyncWebhookListener:
def __init__(self, bot,
secret_token: str, host: Optional[str]="127.0.0.1",
@ -33,13 +28,13 @@ class SyncWebhookListener:
debug: Optional[bool]=False
) -> None:
"""
Aynchronous implementation of webhook listener
for asynchronous version of telebot.
Synchronous implementation of webhook listener
for synchronous version of telebot.
Not supposed to be used manually by user.
Use AsyncTeleBot.run_webhooks() instead.
Use TeleBot.run_webhooks() instead.
:param bot: AsyncTeleBot instance.
:type bot: telebot.async_telebot.AsyncTeleBot
:param bot: TeleBot instance.
:type bot: telebot.TeleBot
:param secret_token: Telegram secret token
:type secret_token: str
@ -77,7 +72,8 @@ class SyncWebhookListener:
self._prepare_endpoint_urls()
def _check_dependencies(self):
@staticmethod
def _check_dependencies():
if not fastapi_installed:
raise ImportError('Fastapi or uvicorn is not installed. Please install it via pip.')

View File

@ -41,7 +41,10 @@ class StateStorageBase:
def get_state(self, chat_id, user_id):
raise NotImplementedError
def get_interactive_data(self, chat_id, user_id):
raise NotImplementedError
def save(self, chat_id, user_id, data):
raise NotImplementedError

View File

@ -3,6 +3,7 @@ from telebot.storage.base_storage import StateStorageBase, StateContext
class StateMemoryStorage(StateStorageBase):
def __init__(self) -> None:
super().__init__()
self.data = {}
#
# {chat_id: {user_id: {'state': None, 'data': {}}, ...}, ...}

View File

@ -5,8 +5,8 @@ import pickle
class StatePickleStorage(StateStorageBase):
# noinspection PyMissingConstructor
def __init__(self, file_path="./.state-save/states.pkl") -> None:
super().__init__()
self.file_path = file_path
self.create_dir()
self.data = self.read()

View File

@ -16,6 +16,7 @@ class StateRedisStorage(StateStorageBase):
TeleBot(storage=StateRedisStorage())
"""
def __init__(self, host='localhost', port=6379, db=0, password=None, prefix='telebot_'):
super().__init__()
self.redis = ConnectionPool(host=host, port=port, db=db, password=password)
#self.con = Redis(connection_pool=self.redis) -> use this when necessary
#