1
0
mirror of https://github.com/eternnoir/pyTelegramBotAPI.git synced 2023-08-10 21:12:57 +03:00

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. * [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). * [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. * [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.** **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 :param use_class_middlewares: Use class middlewares, defaults to False
:type use_class_middlewares: :obj:`bool`, optional :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 :param colorful_logs: Outputs colorful logs
:type colorful_logs: :obj:`bool`, optional :type colorful_logs: :obj:`bool`, optional
@ -142,15 +155,28 @@ class TeleBot:
next_step_backend: Optional[HandlerBackend]=None, reply_backend: Optional[HandlerBackend]=None, next_step_backend: Optional[HandlerBackend]=None, reply_backend: Optional[HandlerBackend]=None,
exception_handler: Optional[ExceptionHandler]=None, last_update_id: Optional[int]=0, exception_handler: Optional[ExceptionHandler]=None, last_update_id: Optional[int]=0,
suppress_middleware_excepions: Optional[bool]=False, state_storage: Optional[StateStorageBase]=StateMemoryStorage(), 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 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: if colorful_logs:
try: try:
import coloredlogs import coloredlogs
@ -159,12 +185,13 @@ class TeleBot:
raise ImportError( raise ImportError(
'Install colorredlogs module to use colorful_logs option.' 'Install colorredlogs module to use colorful_logs option.'
) )
# threading-related
self.__stop_polling = threading.Event() self.__stop_polling = threading.Event()
self.last_update_id = last_update_id
self.exc_info = None self.exc_info = None
# states & register_next_step_handler
self.current_states = state_storage
self.next_step_backend = next_step_backend self.next_step_backend = next_step_backend
if not self.next_step_backend: if not self.next_step_backend:
self.next_step_backend = MemoryHandlerBackend() self.next_step_backend = MemoryHandlerBackend()
@ -173,8 +200,9 @@ class TeleBot:
if not self.reply_backend: if not self.reply_backend:
self.reply_backend = MemoryHandlerBackend() self.reply_backend = MemoryHandlerBackend()
# handlers
self.exception_handler = exception_handler self.exception_handler = exception_handler
self.update_listener = []
self.message_handlers = [] self.message_handlers = []
self.edited_message_handlers = [] self.edited_message_handlers = []
self.channel_post_handlers = [] self.channel_post_handlers = []
@ -192,8 +220,7 @@ class TeleBot:
self.custom_filters = {} self.custom_filters = {}
self.state_handlers = [] self.state_handlers = []
self.current_states = state_storage # middlewares
self.use_class_middlewares = use_class_middlewares self.use_class_middlewares = use_class_middlewares
if apihelper.ENABLE_MIDDLEWARE and not use_class_middlewares: if apihelper.ENABLE_MIDDLEWARE and not use_class_middlewares:
self.typed_middleware_handlers = { 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.' '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 self.middlewares = [] if use_class_middlewares else None
# threads
self.threaded = threaded self.threaded = threaded
if self.threaded: if self.threaded:
self.worker_pool = util.ThreadPool(self, num_threads=num_threads) self.worker_pool = util.ThreadPool(self, num_threads=num_threads)
@ -483,6 +512,7 @@ class TeleBot:
webhook_url = "{}://{}:{}/{}".format(protocol, listen, port, url_path) webhook_url = "{}://{}:{}/{}".format(protocol, listen, port, url_path)
if certificate and certificate_key: if certificate and certificate_key:
# noinspection PyTypeChecker
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_ctx.load_cert_chain(certificate, certificate_key) ssl_ctx.load_cert_chain(certificate, certificate_key)
@ -1126,11 +1156,6 @@ class TeleBot:
def _exec_task(self, task, *args, **kwargs): 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: if self.threaded:
self.worker_pool.put(task, *args, **kwargs) self.worker_pool.put(task, *args, **kwargs)
else: else:
@ -1483,6 +1508,10 @@ class TeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.Message.de_json(
apihelper.send_message( apihelper.send_message(
@ -1521,6 +1550,9 @@ class TeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
apihelper.forward_message(self.token, chat_id, from_chat_id, message_id, disable_notification, timeout, protect_content)) 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. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.MessageID.de_json(
apihelper.copy_message(self.token, chat_id, from_chat_id, message_id, caption, parse_mode, caption_entities, 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, 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. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
apihelper.send_dice( apihelper.send_dice(
self.token, chat_id, emoji, disable_notification, reply_to_message_id, self.token, chat_id, emoji, disable_notification, reply_to_message_id,
@ -1723,6 +1764,9 @@ class TeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.Message.de_json(
apihelper.send_photo( apihelper.send_photo(
@ -1808,6 +1852,9 @@ class TeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.Message.de_json(
apihelper.send_audio( apihelper.send_audio(
@ -1876,6 +1923,9 @@ class TeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.Message.de_json(
apihelper.send_voice( apihelper.send_voice(
@ -1956,6 +2006,10 @@ class TeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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): if data and not(document):
# function typo miss compatibility # function typo miss compatibility
document = data document = data
@ -2020,9 +2074,14 @@ class TeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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): if data and not(sticker):
# function typo miss compatibility # function typo miss compatibility
sticker = data sticker = data
return types.Message.de_json( return types.Message.de_json(
apihelper.send_data( apihelper.send_data(
self.token, chat_id, sticker, 'sticker', self.token, chat_id, sticker, 'sticker',
@ -2110,6 +2169,10 @@ class TeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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): if data and not(video):
# function typo miss compatibility # function typo miss compatibility
video = data video = data
@ -2196,6 +2259,9 @@ class TeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.Message.de_json(
apihelper.send_animation( apihelper.send_animation(
@ -2263,6 +2329,10 @@ class TeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
apihelper.send_video_note( apihelper.send_video_note(
self.token, chat_id, data, duration, length, reply_to_message_id, reply_markup, 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. :return: On success, an array of Messages that were sent is returned.
:rtype: List[types.Message] :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( result = apihelper.send_media_group(
self.token, chat_id, media, disable_notification, reply_to_message_id, timeout, self.token, chat_id, media, disable_notification, reply_to_message_id, timeout,
allow_sending_without_reply, protect_content) allow_sending_without_reply, protect_content)
@ -2377,6 +2451,10 @@ class TeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
apihelper.send_location( apihelper.send_location(
self.token, chat_id, latitude, longitude, live_period, self.token, chat_id, latitude, longitude, live_period,
@ -2548,6 +2626,10 @@ class TeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
apihelper.send_venue( apihelper.send_venue(
self.token, chat_id, latitude, longitude, title, address, foursquare_id, foursquare_type, 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. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
apihelper.send_contact( apihelper.send_contact(
self.token, chat_id, phone_number, first_name, last_name, vcard, self.token, chat_id, phone_number, first_name, last_name, vcard,
@ -3361,6 +3447,8 @@ class TeleBot:
:return: True on success. :return: True on success.
:rtype: :obj:`bool` :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) 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: 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` :rtype: :obj:`types.Message` or :obj:`bool`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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, result = apihelper.edit_message_text(self.token, text, chat_id, message_id, inline_message_id, parse_mode,
entities, disable_web_page_preview, reply_markup) entities, disable_web_page_preview, reply_markup)
@ -3554,6 +3643,10 @@ class TeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :obj:`types.Message` :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( result = apihelper.send_game(
self.token, chat_id, game_short_name, disable_notification, self.token, chat_id, game_short_name, disable_notification,
reply_to_message_id, reply_markup, timeout, reply_to_message_id, reply_markup, timeout,
@ -3756,6 +3849,10 @@ class TeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :obj:`types.Message` :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( result = apihelper.send_invoice(
self.token, chat_id, title, description, invoice_payload, provider_token, self.token, chat_id, title, description, invoice_payload, provider_token,
currency, prices, start_parameter, photo_url, photo_size, photo_width, currency, prices, start_parameter, photo_url, photo_size, photo_width,
@ -3957,6 +4054,10 @@ class TeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :obj:`types.Message` :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): if isinstance(question, types.Poll):
raise RuntimeError("The send_poll signature was changed, please see send_poll function details.") 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): if not isinstance(regexp, str):
logger.error(f"{method_name}: Regexp filter should be string. Not able to use the supplied type.") 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, def message_handler(
content_types: Optional[List[str]]=None, chat_types: Optional[List[str]]=None, **kwargs): 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. 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. As a parameter to the decorator function, it passes :class:`telebot.types.Message` object.
@ -5914,7 +6021,7 @@ class TeleBot:
return False return False
# middleware check-up method # middleware check-up method
def _check_middleware(self, update_type): def _get_middlewares(self, update_type):
""" """
Check middleware Check middleware
@ -5928,100 +6035,115 @@ class TeleBot:
def _run_middlewares_and_handler(self, message, handlers, middlewares, update_type): 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 message: received message (update part) to process with handlers and/or middlewares
:param middleware: middleware that should be executed. :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: :return:
""" """
data = {}
params =[]
handler_error = None
skip_handlers = False
if middlewares: if not self.use_class_middlewares:
for middleware in middlewares: if handlers:
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:
for handler in handlers: for handler in handlers:
process_handler = self._test_message_handler(handler, message) if self._test_message_handler(handler, message):
if not process_handler: continue if handler.get('pass_bot', False):
for i in inspect.signature(handler['function']).parameters: handler['function'](message, bot = self)
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: else:
logger.error("It is not allowed to pass data and values inside data to the handler. Check your handler: {}".format(handler['function'])) handler['function'](message)
return break
else: else:
data_copy = data.copy() data = {}
for key in list(data_copy): params =[]
# remove data from data_copy if handler does not accept it handler_error = None
if key not in params: skip_handlers = False
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())
if middlewares: if middlewares:
for middleware in middlewares: for middleware in middlewares:
if middleware.update_sensitive: if middleware.update_sensitive:
if hasattr(middleware, f'post_process_{update_type}'): if hasattr(middleware, f'pre_process_{update_type}'):
getattr(middleware, f'post_process_{update_type}')(message, data, handler_error) 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: else:
logger.error("Middleware: {} does not have post_process_{} method. Post process function was not executed.".format(middleware.__class__.__name__, update_type)) result = middleware.pre_process(message, data)
else: # We will break this loop if CancelUpdate is returned
middleware.post_process(message, data, handler_error) # 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): def _notify_command_handlers(self, handlers, new_messages, update_type):
""" """
Notifies command handlers. Notifies command handlers.
:param handlers: :param handlers: all created handlers
:param new_messages: :param new_messages: received messages to proceed
:param update_type: handler/update type (Update fields)
:return: :return:
""" """
if not(handlers) and not(self.use_class_middlewares): if not(handlers) and not(self.use_class_middlewares):
return return
if self.use_class_middlewares:
middlewares = self._get_middlewares(update_type)
else:
middlewares = None
for message in new_messages: for message in new_messages:
if not self.use_class_middlewares: self._exec_task(
for message_handler in handlers: self._run_middlewares_and_handler,
if self._test_message_handler(message_handler, message): message,
self._exec_task(message_handler['function'], message, pass_bot=message_handler['pass_bot'], task_type='handler') handlers=handlers,
break middlewares=middlewares,
else: update_type=update_type)
middleware = self._check_middleware(update_type)
self._exec_task(self._run_middlewares_and_handler, message, handlers=handlers, middlewares=middleware, update_type=update_type)
return

View File

@ -3,8 +3,6 @@ from datetime import datetime
import logging import logging
import re import re
import sys
import time
import traceback import traceback
from typing import Any, Awaitable, Callable, List, Optional, Union from typing import Any, Awaitable, Callable, List, Optional, Union
@ -99,21 +97,37 @@ class AsyncTeleBot:
:param state_storage: Storage for states, defaults to StateMemoryStorage() :param state_storage: Storage for states, defaults to StateMemoryStorage()
:type state_storage: :class:`telebot.asyncio_storage.StateMemoryStorage`, optional :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 :param colorful_logs: Outputs colorful logs
:type colorful_logs: :obj:`bool`, optional :type colorful_logs: :obj:`bool`, optional
""" """
def __init__(self, token: str, parse_mode: Optional[str]=None, offset: Optional[int]=None, 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: colorful_logs: Optional[bool]=False) -> None:
# update-related
self.token = token self.token = token
self.offset = offset self.offset = offset
self.token = token
self.parse_mode = parse_mode
self.update_listener = []
# logs-related
if colorful_logs: if colorful_logs:
try: try:
import coloredlogs import coloredlogs
@ -122,11 +136,20 @@ class AsyncTeleBot:
raise ImportError( raise ImportError(
'Install colorredlogs module to use colorful_logs option.' '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.exception_handler = exception_handler
self.message_handlers = [] self.message_handlers = []
self.edited_message_handlers = [] self.edited_message_handlers = []
self.channel_post_handlers = [] self.channel_post_handlers = []
@ -143,9 +166,6 @@ class AsyncTeleBot:
self.chat_join_request_handlers = [] self.chat_join_request_handlers = []
self.custom_filters = {} self.custom_filters = {}
self.state_handlers = [] self.state_handlers = []
self.current_states = state_storage
self.middlewares = [] self.middlewares = []
self._user = None # set during polling self._user = None # set during polling
@ -296,7 +316,7 @@ class AsyncTeleBot:
logger.error("Infinity polling exception: %s", str(e)) logger.error("Infinity polling exception: %s", str(e))
if logger_level and logger_level >= logging.DEBUG: if logger_level and logger_level >= logging.DEBUG:
logger.error("Exception traceback:\n%s", traceback.format_exc()) logger.error("Exception traceback:\n%s", traceback.format_exc())
time.sleep(3) await asyncio.sleep(3)
continue continue
if logger_level and logger_level >= logging.INFO: if logger_level and logger_level >= logging.INFO:
logger.error("Infinity polling: polling exited") logger.error("Infinity polling: polling exited")
@ -390,7 +410,8 @@ class AsyncTeleBot:
await self.close_session() await self.close_session()
logger.warning('Polling is stopped.') logger.warning('Polling is stopped.')
def _loop_create_task(self, coro): @staticmethod
def _loop_create_task(coro):
return asyncio.create_task(coro) return asyncio.create_task(coro)
async def _process_updates(self, handlers, messages, update_type): async def _process_updates(self, handlers, messages, update_type):
@ -402,12 +423,22 @@ class AsyncTeleBot:
:return: :return:
""" """
tasks = [] tasks = []
middlewares = await self._get_middlewares(update_type)
for message in messages: for message in messages:
middleware = await self.process_middlewares(update_type) tasks.append(self._run_middlewares_and_handlers(message, handlers, middlewares, update_type))
tasks.append(self._run_middlewares_and_handlers(handlers, message, middleware, update_type))
await asyncio.gather(*tasks) 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 handler_error = None
data = {} data = {}
skip_handlers = False skip_handlers = False
@ -477,7 +508,7 @@ class AsyncTeleBot:
else: else:
logger.error('Middleware {} does not have post_process_{} method. post_process function execution was skipped.'.format(middleware.__class__.__name__, update_type)) 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) else: await middleware.post_process(message, data, handler_error)
# update handling
async def process_new_updates(self, updates: List[types.Update]): async def process_new_updates(self, updates: List[types.Update]):
""" """
Process new updates. Process new updates.
@ -666,7 +697,7 @@ class AsyncTeleBot:
""" """
await self._process_updates(self.chat_join_request_handlers, chat_join_request, 'chat_join_request') 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: :meta private:
""" """
@ -2328,6 +2359,10 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.Message.de_json(
await asyncio_helper.send_message( await asyncio_helper.send_message(
@ -2366,6 +2401,9 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
await asyncio_helper.forward_message(self.token, chat_id, from_chat_id, message_id, disable_notification, timeout, protect_content)) 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` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.MessageID.de_json(
await asyncio_helper.copy_message(self.token, chat_id, from_chat_id, message_id, caption, parse_mode, caption_entities, 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. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
await asyncio_helper.send_dice( await asyncio_helper.send_dice(
self.token, chat_id, emoji, disable_notification, reply_to_message_id, self.token, chat_id, emoji, disable_notification, reply_to_message_id,
@ -2568,6 +2613,9 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.Message.de_json(
await asyncio_helper.send_photo( await asyncio_helper.send_photo(
@ -2652,6 +2700,9 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.Message.de_json(
await asyncio_helper.send_audio( await asyncio_helper.send_audio(
@ -2719,6 +2770,9 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.Message.de_json(
await asyncio_helper.send_voice( await asyncio_helper.send_voice(
@ -2798,6 +2852,10 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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): if data and not(document):
# function typo miss compatibility # function typo miss compatibility
document = data document = data
@ -2858,6 +2916,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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): if data and not(sticker):
# function typo miss compatibility # function typo miss compatibility
logger.warning("send_sticker: data parameter is deprecated. Use sticker instead.") logger.warning("send_sticker: data parameter is deprecated. Use sticker instead.")
@ -2949,6 +3011,10 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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): if data and not(video):
# function typo miss compatibility # function typo miss compatibility
logger.warning("send_sticker: data parameter is deprecated. Use video instead.") logger.warning("send_sticker: data parameter is deprecated. Use video instead.")
@ -3036,6 +3102,9 @@ class AsyncTeleBot:
:rtype: :class:`telebot.types.Message` :rtype: :class:`telebot.types.Message`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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( return types.Message.de_json(
await asyncio_helper.send_animation( await asyncio_helper.send_animation(
@ -3102,6 +3171,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
await asyncio_helper.send_video_note( await asyncio_helper.send_video_note(
self.token, chat_id, data, duration, length, reply_to_message_id, reply_markup, 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. :return: On success, an array of Messages that were sent is returned.
:rtype: List[types.Message] :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( result = await asyncio_helper.send_media_group(
self.token, chat_id, media, disable_notification, reply_to_message_id, timeout, self.token, chat_id, media, disable_notification, reply_to_message_id, timeout,
allow_sending_without_reply, protect_content) allow_sending_without_reply, protect_content)
@ -3214,6 +3291,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
await asyncio_helper.send_location( await asyncio_helper.send_location(
self.token, chat_id, latitude, longitude, live_period, self.token, chat_id, latitude, longitude, live_period,
@ -3384,6 +3465,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
await asyncio_helper.send_venue( await asyncio_helper.send_venue(
self.token, chat_id, latitude, longitude, title, address, foursquare_id, foursquare_type, 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. :return: On success, the sent Message is returned.
:rtype: :class:`telebot.types.Message` :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( return types.Message.de_json(
await asyncio_helper.send_contact( await asyncio_helper.send_contact(
self.token, chat_id, phone_number, first_name, last_name, vcard, self.token, chat_id, phone_number, first_name, last_name, vcard,
@ -4205,6 +4294,8 @@ class AsyncTeleBot:
:return: True on success. :return: True on success.
:rtype: :obj:`bool` :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) 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: 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` :rtype: :obj:`types.Message` or :obj:`bool`
""" """
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode 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, 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) entities, disable_web_page_preview, reply_markup)
@ -4398,6 +4490,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :obj:`types.Message` :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( result = await asyncio_helper.send_game(
self.token, chat_id, game_short_name, disable_notification, self.token, chat_id, game_short_name, disable_notification,
reply_to_message_id, reply_markup, timeout, reply_to_message_id, reply_markup, timeout,
@ -4599,6 +4695,10 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :obj:`types.Message` :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( result = await asyncio_helper.send_invoice(
self.token, chat_id, title, description, invoice_payload, provider_token, self.token, chat_id, title, description, invoice_payload, provider_token,
currency, prices, start_parameter, photo_url, photo_size, photo_width, currency, prices, start_parameter, photo_url, photo_size, photo_width,
@ -4800,12 +4900,14 @@ class AsyncTeleBot:
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :obj:`types.Message` :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): if isinstance(question, types.Poll):
raise RuntimeError("The send_poll signature was changed, please see send_poll function details.") 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( return types.Message.de_json(
await asyncio_helper.send_poll( await asyncio_helper.send_poll(
self.token, chat_id, self.token, chat_id,

View File

@ -420,7 +420,7 @@ class StateFilter(AdvancedCustomFilter):
text = text.name text = text.name
if message.chat.type in ['group', 'supergroup']: 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: if group_state == text:
return True return True
elif type(text) is list and group_state in text: elif type(text) is list and group_state in text:
@ -428,7 +428,7 @@ class StateFilter(AdvancedCustomFilter):
else: 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: if user_state == text:
return True return True
elif type(text) is list and user_state in text: elif type(text) is list and user_state in text:

View File

@ -23,7 +23,7 @@ session = None
FILE_URL = None FILE_URL = None
REQUEST_TIMEOUT = None REQUEST_TIMEOUT = 300
MAX_RETRIES = 3 MAX_RETRIES = 3
REQUEST_LIMIT = 50 REQUEST_LIMIT = 50
@ -58,10 +58,29 @@ class SessionManager:
session_manager = 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) params = _prepare_data(params, files)
if request_timeout is None:
request_timeout = REQUEST_TIMEOUT
timeout = aiohttp.ClientTimeout(total=request_timeout) timeout = aiohttp.ClientTimeout(total=request_timeout)
got_result = False got_result = False
current_try=0 current_try=0
@ -161,15 +180,13 @@ async def get_file_url(token, file_id):
async def download_file(token, file_path): async def download_file(token, file_path):
if FILE_URL is None: if FILE_URL is None:
url = "https://api.telegram.org/file/bot{0}/{1}".format(token, file_path) url = "https://api.telegram.org/file/bot{0}/{1}".format(token, file_path)
else: else: url = FILE_URL.format(token, file_path)
# noinspection PyUnresolvedReferences session = await session_manager.get_session()
url = FILE_URL.format(token, file_path) async with session.get(url, proxy=proxy) as response:
async with await session_manager.get_session() as session: if response.status != 200:
async with session.get(url, proxy=proxy) as response: raise ApiHTTPException('Download file', result)
result = await response.read() result = await response.read()
if response.status != 200:
raise ApiHTTPException('Download file', result)
return result return result

View File

@ -428,7 +428,7 @@ class StateFilter(AdvancedCustomFilter):
text = text.name text = text.name
if message.chat.type in ['group', 'supergroup']: 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: if group_state == text:
return True return True
elif type(text) is list and group_state in text: elif type(text) is list and group_state in text:
@ -436,7 +436,7 @@ class StateFilter(AdvancedCustomFilter):
else: 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: if user_state == text:
return True return True
elif type(text) is list and user_state in text: 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. This file is used by TeleBot.run_webhooks() function.
Fastapi is required to run this script. Fastapi is required to run this script.
""" """
@ -15,15 +14,11 @@ try:
except ImportError: except ImportError:
fastapi_installed = False fastapi_installed = False
from telebot.types import Update from telebot.types import Update
from typing import Optional from typing import Optional
class SyncWebhookListener: class SyncWebhookListener:
def __init__(self, bot, def __init__(self, bot,
secret_token: str, host: Optional[str]="127.0.0.1", secret_token: str, host: Optional[str]="127.0.0.1",
@ -33,13 +28,13 @@ class SyncWebhookListener:
debug: Optional[bool]=False debug: Optional[bool]=False
) -> None: ) -> None:
""" """
Aynchronous implementation of webhook listener Synchronous implementation of webhook listener
for asynchronous version of telebot. for synchronous version of telebot.
Not supposed to be used manually by user. Not supposed to be used manually by user.
Use AsyncTeleBot.run_webhooks() instead. Use TeleBot.run_webhooks() instead.
:param bot: AsyncTeleBot instance. :param bot: TeleBot instance.
:type bot: telebot.async_telebot.AsyncTeleBot :type bot: telebot.TeleBot
:param secret_token: Telegram secret token :param secret_token: Telegram secret token
:type secret_token: str :type secret_token: str
@ -77,7 +72,8 @@ class SyncWebhookListener:
self._prepare_endpoint_urls() self._prepare_endpoint_urls()
def _check_dependencies(self): @staticmethod
def _check_dependencies():
if not fastapi_installed: if not fastapi_installed:
raise ImportError('Fastapi or uvicorn is not installed. Please install it via pip.') 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): def get_state(self, chat_id, user_id):
raise NotImplementedError raise NotImplementedError
def get_interactive_data(self, chat_id, user_id):
raise NotImplementedError
def save(self, chat_id, user_id, data): def save(self, chat_id, user_id, data):
raise NotImplementedError raise NotImplementedError

View File

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

View File

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

View File

@ -16,6 +16,7 @@ class StateRedisStorage(StateStorageBase):
TeleBot(storage=StateRedisStorage()) TeleBot(storage=StateRedisStorage())
""" """
def __init__(self, host='localhost', port=6379, db=0, password=None, prefix='telebot_'): 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.redis = ConnectionPool(host=host, port=port, db=db, password=password)
#self.con = Redis(connection_pool=self.redis) -> use this when necessary #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 telebot.apihelper import ApiTelegramException
from time import sleep from time import sleep
msg = None
try: try:
msg = function(*args, **kwargs) return function(*args, **kwargs)
except ApiTelegramException as ex: except ApiTelegramException as ex:
if ex.error_code == 429: if ex.error_code == 429:
sleep(ex.result_json['parameters']['retry_after']) sleep(ex.result_json['parameters']['retry_after'])
msg = function(*args, **kwargs) return function(*args, **kwargs)
finally: else:
return msg raise
def parse_web_app_data(token: str, raw_init_data: str): def parse_web_app_data(token: str, raw_init_data: str):