Merge branch 'master' into conflicts

This commit is contained in:
_run 2022-09-30 22:53:19 +04:00 committed by GitHub
commit d3080b6d4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 485 additions and 153 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

@ -0,0 +1,89 @@
#!/usr/bin/env python
"""
Asynchronous Telegram Echo Bot example.
This is a simple bot that echoes each message that is received onto the chat.
It uses the Starlette ASGI framework to receive updates via webhook requests.
"""
import uvicorn
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import PlainTextResponse, Response
from starlette.routing import Route
from telebot.async_telebot import AsyncTeleBot
from telebot.types import Message, Update
API_TOKEN = "TOKEN"
WEBHOOK_HOST = "<ip/domain>"
WEBHOOK_PORT = 8443 # 443, 80, 88 or 8443 (port need to be 'open')
WEBHOOK_LISTEN = "0.0.0.0"
WEBHOOK_SSL_CERT = "./webhook_cert.pem" # Path to the ssl certificate
WEBHOOK_SSL_PRIV = "./webhook_pkey.pem" # Path to the ssl private key
WEBHOOK_URL = f"https://{WEBHOOK_HOST}:{WEBHOOK_PORT}/telegram"
WEBHOOK_SECRET_TOKEN = "SECRET_TOKEN"
logger = telebot.logger
telebot.logger.setLevel(logging.INFO)
bot = AsyncTeleBot(token=API_TOKEN)
# BOT HANDLERS
@bot.message_handler(commands=["help", "start"])
async def send_welcome(message: Message):
"""
Handle '/start' and '/help'
"""
await bot.reply_to(
message,
("Hi there, I am EchoBot.\n" "I am here to echo your kind words back to you."),
)
@bot.message_handler(func=lambda _: True, content_types=["text"])
async def echo_message(message: Message):
"""
Handle all other messages
"""
await bot.reply_to(message, message.text)
# WEBSERVER HANDLERS
async def telegram(request: Request) -> Response:
"""Handle incoming Telegram updates."""
token_header_name = "X-Telegram-Bot-Api-Secret-Token"
if request.headers.get(token_header_name) != WEBHOOK_SECRET_TOKEN:
return PlainTextResponse("Forbidden", status_code=403)
await bot.process_new_updates([Update.de_json(await request.json())])
return Response()
async def startup() -> None:
"""Register webhook for telegram updates."""
webhook_info = await bot.get_webhook_info(30)
if WEBHOOK_URL != webhook_info.url:
logger.debug(
f"updating webhook url, old: {webhook_info.url}, new: {WEBHOOK_URL}"
)
if not await bot.set_webhook(
url=WEBHOOK_URL, secret_token=WEBHOOK_SECRET_TOKEN
):
raise RuntimeError("unable to set webhook")
app = Starlette(
routes=[
Route("/telegram", telegram, methods=["POST"]),
],
on_startup=[startup],
)
uvicorn.run(
app,
host=WEBHOOK_HOST,
port=WEBHOOK_LISTEN,
ssl_certfile=WEBHOOK_SSL_CERT,
ssl_keyfile=WEBHOOK_SSL_PRIV,
)

View File

@ -131,6 +131,19 @@ 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
:param colorful_logs: Outputs colorful logs
:type colorful_logs: :obj:`bool`, optional
@ -142,15 +155,28 @@ 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,
colorful_logs: Optional[bool]=False
):
self.token = token
self.parse_mode = parse_mode
self.update_listener = []
self.skip_pending = skip_pending
self.suppress_middleware_excepions = suppress_middleware_excepions
# 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
# logs-related
if colorful_logs:
try:
import coloredlogs
@ -159,12 +185,13 @@ class TeleBot:
raise ImportError(
'Install colorredlogs module to use colorful_logs option.'
)
# threading-related
self.__stop_polling = threading.Event()
self.last_update_id = last_update_id
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()
@ -173,8 +200,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 = []
@ -192,8 +220,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 = {
@ -219,6 +246,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)
@ -483,6 +512,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)
@ -1126,11 +1156,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:
@ -1483,6 +1508,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(
@ -1521,6 +1550,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))
@ -1583,6 +1615,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,
@ -1660,6 +1697,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,
@ -1723,6 +1764,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(
@ -1808,6 +1852,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(
@ -1876,6 +1923,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(
@ -1956,6 +2006,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
@ -2020,9 +2074,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',
@ -2110,6 +2169,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
@ -2196,6 +2259,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(
@ -2263,6 +2329,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,
@ -2309,6 +2379,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)
@ -2377,6 +2451,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,
@ -2548,6 +2626,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,
@ -2610,6 +2692,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,
@ -3361,6 +3447,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:
@ -3442,6 +3530,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)
@ -3554,6 +3643,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,
@ -3756,6 +3849,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,
@ -3957,6 +4054,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.")
@ -4829,8 +4930,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.
@ -5914,7 +6021,7 @@ class TeleBot:
return False
# middleware check-up method
def _check_middleware(self, update_type):
def _get_middlewares(self, update_type):
"""
Check middleware
@ -5928,100 +6035,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,8 +3,6 @@ from datetime import datetime
import logging
import re
import sys
import time
import traceback
from typing import Any, Awaitable, Callable, List, Optional, Union
@ -99,21 +97,37 @@ 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
:param colorful_logs: Outputs colorful logs
:type colorful_logs: :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(),
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,
colorful_logs: Optional[bool]=False) -> None:
# update-related
self.token = token
self.offset = offset
self.token = token
self.parse_mode = parse_mode
self.update_listener = []
# logs-related
if colorful_logs:
try:
import coloredlogs
@ -122,11 +136,20 @@ class AsyncTeleBot:
raise ImportError(
'Install colorredlogs module to use colorful_logs option.'
)
# 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 = []
@ -143,9 +166,6 @@ class AsyncTeleBot:
self.chat_join_request_handlers = []
self.custom_filters = {}
self.state_handlers = []
self.current_states = state_storage
self.middlewares = []
self._user = None # set during polling
@ -296,7 +316,7 @@ class AsyncTeleBot:
logger.error("Infinity polling exception: %s", str(e))
if logger_level and logger_level >= logging.DEBUG:
logger.error("Exception traceback:\n%s", traceback.format_exc())
time.sleep(3)
await asyncio.sleep(3)
continue
if logger_level and logger_level >= logging.INFO:
logger.error("Infinity polling: polling exited")
@ -390,7 +410,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):
@ -402,12 +423,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
@ -477,7 +508,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.
@ -666,7 +697,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:
"""
@ -2328,6 +2359,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(
@ -2366,6 +2401,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))
@ -2428,6 +2466,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,
@ -2506,6 +2547,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,
@ -2568,6 +2613,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(
@ -2652,6 +2700,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(
@ -2719,6 +2770,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(
@ -2798,6 +2852,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
@ -2858,6 +2916,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.")
@ -2949,6 +3011,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.")
@ -3036,6 +3102,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(
@ -3102,6 +3171,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,
@ -3147,6 +3220,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)
@ -3214,6 +3291,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,
@ -3384,6 +3465,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,
@ -3445,6 +3530,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,
@ -4205,6 +4294,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:
@ -4286,6 +4377,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)
@ -4398,6 +4490,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,
@ -4599,6 +4695,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,
@ -4800,12 +4900,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

@ -420,7 +420,7 @@ class StateFilter(AdvancedCustomFilter):
text = text.name
if message.chat.type in ['group', 'supergroup']:
group_state = await self.bot.current_states.get_state(user_id, chat_id)
group_state = await self.bot.current_states.get_state(chat_id, user_id)
if group_state == text:
return True
elif type(text) is list and group_state in text:
@ -428,7 +428,7 @@ class StateFilter(AdvancedCustomFilter):
else:
user_state = await self.bot.current_states.get_state(user_id, chat_id)
user_state = await self.bot.current_states.get_state(chat_id, user_id)
if user_state == text:
return True
elif type(text) is list and user_state in text:

View File

@ -23,7 +23,7 @@ session = None
FILE_URL = None
REQUEST_TIMEOUT = None
REQUEST_TIMEOUT = 300
MAX_RETRIES = 3
REQUEST_LIMIT = 50
@ -58,10 +58,29 @@ class SessionManager:
session_manager = SessionManager()
async def _process_request(token, url, method='get', params=None, files=None, request_timeout=None):
async def _process_request(token, url, method='get', params=None, files=None, **kwargs):
# Let's resolve all timeout parameters.
# getUpdates parameter may contain 2 parameters: request_timeout & timeout.
# other methods may contain timeout parameter that should be applied to
# ClientTimeout only.
# timeout should be added to params for getUpdates. All other timeout's should be used
# for request timeout.
# here we got request_timeout, so this is getUpdates method.
if 'request_timeout' in kwargs:
request_timeout = kwargs.pop('request_timeout')
else:
# let's check for timeout in params
request_timeout = params.pop('timeout', None)
# we will apply default request_timeout if there is no timeout in params
# otherwise, we will use timeout parameter applied for payload.
request_timeout = REQUEST_TIMEOUT if request_timeout is None else request_timeout
# Preparing data by adding all parameters and files to FormData
params = _prepare_data(params, files)
if request_timeout is None:
request_timeout = REQUEST_TIMEOUT
timeout = aiohttp.ClientTimeout(total=request_timeout)
got_result = False
current_try=0
@ -161,15 +180,13 @@ async def get_file_url(token, file_id):
async def download_file(token, file_path):
if FILE_URL is None:
url = "https://api.telegram.org/file/bot{0}/{1}".format(token, file_path)
else:
# noinspection PyUnresolvedReferences
url = FILE_URL.format(token, file_path)
async with await session_manager.get_session() as session:
async with session.get(url, proxy=proxy) as response:
result = await response.read()
if response.status != 200:
raise ApiHTTPException('Download file', result)
else: url = FILE_URL.format(token, file_path)
session = await session_manager.get_session()
async with session.get(url, proxy=proxy) as response:
if response.status != 200:
raise ApiHTTPException('Download file', result)
result = await response.read()
return result

View File

@ -428,7 +428,7 @@ class StateFilter(AdvancedCustomFilter):
text = text.name
if message.chat.type in ['group', 'supergroup']:
group_state = self.bot.current_states.get_state(user_id, chat_id)
group_state = self.bot.current_states.get_state(chat_id, user_id)
if group_state == text:
return True
elif type(text) is list and group_state in text:
@ -436,7 +436,7 @@ class StateFilter(AdvancedCustomFilter):
else:
user_state = self.bot.current_states.get_state(user_id, chat_id)
user_state = self.bot.current_states.get_state(chat_id, user_id)
if user_state == text:
return True
elif type(text) is list and user_state in text:

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
#

View File

@ -647,15 +647,15 @@ def antiflood(function: Callable, *args, **kwargs):
"""
from telebot.apihelper import ApiTelegramException
from time import sleep
msg = None
try:
msg = function(*args, **kwargs)
return function(*args, **kwargs)
except ApiTelegramException as ex:
if ex.error_code == 429:
sleep(ex.result_json['parameters']['retry_after'])
msg = function(*args, **kwargs)
finally:
return msg
return function(*args, **kwargs)
else:
raise
def parse_web_app_data(token: str, raw_init_data: str):