From a2822c74ed04375518eb15ec0ae40952d2f7e533 Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 4 Dec 2021 21:34:15 +0500 Subject: [PATCH 1/4] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d1b13d0..ce33584 100644 --- a/README.md +++ b/README.md @@ -558,7 +558,7 @@ apihelper.API_URL = "http://localhost:4200/bot{0}/{1}" *Note: 4200 is an example port* ### Asynchronous TeleBot -New: There is an asynchronous implementation of telebot. It is more flexible. +New: There is an asynchronous implementation of telebot. To enable this behaviour, create an instance of AsyncTeleBot instead of TeleBot. ```python tb = telebot.AsyncTeleBot("TOKEN") From fb52137bff4e27bd398f05296a411020855c00be Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 4 Dec 2021 21:54:26 +0500 Subject: [PATCH 2/4] 2 new examples --- .../CallbackData_example.py | 87 +++++++++++++++++++ .../asynchronous_telebot/update_listener.py | 14 +++ 2 files changed, 101 insertions(+) create mode 100644 examples/asynchronous_telebot/CallbackData_example.py create mode 100644 examples/asynchronous_telebot/update_listener.py diff --git a/examples/asynchronous_telebot/CallbackData_example.py b/examples/asynchronous_telebot/CallbackData_example.py new file mode 100644 index 0000000..0386bec --- /dev/null +++ b/examples/asynchronous_telebot/CallbackData_example.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +""" +This Example will show you how to use CallbackData +""" + +from telebot.callback_data import CallbackData, CallbackDataFilter +from telebot import types +from telebot.async_telebot import AsyncTeleBot +from telebot.asyncio_filters import AdvancedCustomFilter + +API_TOKEN = 'TOKEN' +PRODUCTS = [ + {'id': '0', 'name': 'xiaomi mi 10', 'price': 400}, + {'id': '1', 'name': 'samsung s20', 'price': 800}, + {'id': '2', 'name': 'iphone 13', 'price': 1300} +] + +bot = AsyncTeleBot(API_TOKEN) +products_factory = CallbackData('product_id', prefix='products') + + +def products_keyboard(): + return types.InlineKeyboardMarkup( + keyboard=[ + [ + types.InlineKeyboardButton( + text=product['name'], + callback_data=products_factory.new(product_id=product["id"]) + ) + ] + for product in PRODUCTS + ] + ) + + +def back_keyboard(): + return types.InlineKeyboardMarkup( + keyboard=[ + [ + types.InlineKeyboardButton( + text='⬅', + callback_data='back' + ) + ] + ] + ) + + +class ProductsCallbackFilter(AdvancedCustomFilter): + key = 'config' + + async def check(self, call: types.CallbackQuery, config: CallbackDataFilter): + return config.check(query=call) + + +@bot.message_handler(commands=['products']) +async def products_command_handler(message: types.Message): + await bot.send_message(message.chat.id, 'Products:', reply_markup=products_keyboard()) + + +# Only product with field - product_id = 2 +@bot.callback_query_handler(func=None, config=products_factory.filter(product_id='2')) +async def product_one_callback(call: types.CallbackQuery): + await bot.answer_callback_query(callback_query_id=call.id, text='Not available :(', show_alert=True) + + +# Any other products +@bot.callback_query_handler(func=None, config=products_factory.filter()) +async def products_callback(call: types.CallbackQuery): + callback_data: dict = products_factory.parse(callback_data=call.data) + product_id = int(callback_data['product_id']) + product = PRODUCTS[product_id] + + text = f"Product name: {product['name']}\n" \ + f"Product price: {product['price']}" + await bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, + text=text, reply_markup=back_keyboard()) + + +@bot.callback_query_handler(func=lambda c: c.data == 'back') +async def back_callback(call: types.CallbackQuery): + await bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, + text='Products:', reply_markup=products_keyboard()) + + +bot.add_custom_filter(ProductsCallbackFilter()) +bot.polling() diff --git a/examples/asynchronous_telebot/update_listener.py b/examples/asynchronous_telebot/update_listener.py new file mode 100644 index 0000000..75488cf --- /dev/null +++ b/examples/asynchronous_telebot/update_listener.py @@ -0,0 +1,14 @@ +from telebot.async_telebot import AsyncTeleBot + +# Update listeners are functions that are called when any update is received. + +bot = AsyncTeleBot(token='TOKEN') + +async def update_listener(messages): + for message in messages: + if message.text == '/start': + await bot.send_message(message.chat.id, 'Hello!') + +bot.set_update_listener(update_listener) + +bot.polling() \ No newline at end of file From a5ee5f816c7ac32872fc98b5db1af06f55c901aa Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 4 Dec 2021 21:57:16 +0500 Subject: [PATCH 3/4] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ce33584..5d62908 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ #

pyTelegramBotAPI -

A simple, but extensible Python implementation for the Telegram Bot API. -

Supports both sync and async ways. +

A simple, but extensible Python implementation for the Telegram Bot API.

+

Supports both sync and async ways.

-##

Supported Bot API version: 5.4! +##

Supporting Bot API version: 5.4! ## Contents @@ -53,7 +53,7 @@ * [Proxy](#proxy) * [Testing](#testing) * [API conformance](#api-conformance) - * [Asynchronous TeleBot](#asynctelebot) + * [AsyncTeleBot](#asynctelebot) * [F.A.Q.](#faq) * [How can I distinguish a User and a GroupChat in message.chat?](#how-can-i-distinguish-a-user-and-a-groupchat-in-messagechat) * [How can I handle reocurring ConnectionResetErrors?](#how-can-i-handle-reocurring-connectionreseterrors) @@ -184,8 +184,8 @@ TeleBot supports the following filters: |content_types|list of strings (default `['text']`)|`True` if message.content_type is in the list of strings.| |regexp|a regular expression as a string|`True` if `re.search(regexp_arg)` returns `True` and `message.content_type == 'text'` (See [Python Regular Expressions](https://docs.python.org/2/library/re.html))| |commands|list of strings|`True` if `message.content_type == 'text'` and `message.text` starts with a command that is in the list of strings.| -|chat_types|list of chat types|`True` if `message.chat.type` in your filter -|func|a function (lambda or function reference)|`True` if the lambda or function reference returns `True` +|chat_types|list of chat types|`True` if `message.chat.type` in your filter| +|func|a function (lambda or function reference)|`True` if the lambda or function reference returns `True`| Here are some examples of using the filters and message handlers: @@ -376,8 +376,8 @@ bot.add_custom_filter(IsAdmin()) # Now, you can use it in handler. @bot.message_handler(is_admin=True) def admin_of_group(message): - bot.send_message(message.chat.id, 'You are admin of this group'!) - + bot.send_message(message.chat.id, 'You are admin of this group!') + ``` @@ -572,8 +572,8 @@ tb = telebot.AsyncTeleBot("TOKEN") @tb.message_handler(commands=['start']) async def start_message(message): - await bot.send_message(message.chat.id, 'Hello'!) - + await bot.send_message(message.chat.id, 'Hello!') + ``` See more in [examples](https://github.com/eternnoir/pyTelegramBotAPI/tree/master/examples/asynchronous_telebot) @@ -769,7 +769,7 @@ Telegram Bot API support new type Chat for message.chat. - ```python if message.chat.type == "private": - # private chat message + # private chat message if message.chat.type == "group": # group chat message From 4f198bc6f59c5545a1515e470eb343b004c26322 Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 4 Dec 2021 22:03:14 +0500 Subject: [PATCH 4/4] Forgot to update file --- telebot/async_telebot.py | 33 ++++++++++++++++++--------- telebot/asyncio_helper.py | 47 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index 9f24d90..e7221bc 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -97,16 +97,15 @@ class AsyncTeleBot: def __init__(self, token: str, parse_mode: Optional[str]=None, offset=None, - exception_handler=None,suppress_middleware_excepions=False) -> None: # TODO: ADD TYPEHINTS + exception_handler=None) -> None: # TODO: ADD TYPEHINTS self.token = token self.offset = offset self.token = token self.parse_mode = parse_mode self.update_listener = [] - self.suppress_middleware_excepions = suppress_middleware_excepions - self.exc_info = None + self.exception_handler = exception_handler @@ -234,13 +233,23 @@ class AsyncTeleBot: try: updates = await self.get_updates(offset=self.offset, allowed_updates=allowed_updates, timeout=timeout, request_timeout=request_timeout) + if updates: + self.offset = updates[-1].update_id + 1 + self._loop_create_task(self.process_new_updates(updates)) # Seperate task for processing updates + if interval: await asyncio.sleep(interval) + + except KeyboardInterrupt: + return except asyncio.CancelledError: return except asyncio_helper.ApiTelegramException as e: logger.error(str(e)) - continue + if non_stop: + continue + else: + break except Exception as e: logger.error('Cause exception while getting updates.') if non_stop: @@ -249,10 +258,6 @@ class AsyncTeleBot: continue else: raise e - if updates: - self.offset = updates[-1].update_id + 1 - self._loop_create_task(self.process_new_updates(updates)) # Seperate task for processing updates - if interval: await asyncio.sleep(interval) finally: self._polling = False @@ -297,7 +302,12 @@ class AsyncTeleBot: break except Exception as e: handler_error = e - logger.info(str(e)) + + if not middleware: + if self.exception_handler: + return self.exception_handler.handle(e) + logging.error(str(e)) + return if middleware: await middleware.post_process(message, data, handler_error) @@ -448,7 +458,7 @@ class AsyncTeleBot: if len(self.update_listener) == 0: return for listener in self.update_listener: - self._loop_create_task(listener, new_messages) + self._loop_create_task(listener(new_messages)) async def _test_message_handler(self, message_handler, message): """ @@ -466,6 +476,9 @@ class AsyncTeleBot: return True + def set_update_listener(self, func): + self.update_listener.append(func) + def add_custom_filter(self, custom_filter): """ Create custom filter. diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 3d1189d..3a765de 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -8,7 +8,7 @@ try: import ujson as json except ImportError: import json - +import os API_URL = 'https://api.telegram.org/bot{0}/{1}' from datetime import datetime @@ -42,14 +42,55 @@ RETRY_TIMEOUT = 2 MAX_RETRIES = 15 async def _process_request(token, url, method='get', params=None, files=None, request_timeout=None): + params = compose_data(params, files) async with await session_manager._get_new_session() as session: - async with session.get(API_URL.format(token, url), params=params, data=files, timeout=request_timeout) as response: + async with session.request(method=method, url=API_URL.format(token, url), data=params, timeout=request_timeout) as response: logger.debug("Request: method={0} url={1} params={2} files={3} request_timeout={4}".format(method, url, params, files, request_timeout).replace(token, token.split(':')[0] + ":{TOKEN}")) json_result = await _check_result(url, response) if json_result: return json_result['result'] +def guess_filename(obj): + """ + Get file name from object + + :param obj: + :return: + """ + name = getattr(obj, 'name', None) + if name and isinstance(name, str) and name[0] != '<' and name[-1] != '>': + return os.path.basename(name) + + +def compose_data(params=None, files=None): + """ + Prepare request data + + :param params: + :param files: + :return: + """ + data = aiohttp.formdata.FormData(quote_fields=False) + + if params: + for key, value in params.items(): + data.add_field(key, str(value)) + + if files: + for key, f in files.items(): + if isinstance(f, tuple): + if len(f) == 2: + filename, fileobj = f + else: + raise ValueError('Tuple must have exactly 2 elements: filename, fileobj') + else: + filename, fileobj = guess_filename(f) or key, f + + data.add_field(key, fileobj, filename=filename) + + return data + async def _convert_markup(markup): if isinstance(markup, types.JsonSerializable): return markup.to_json() @@ -731,7 +772,7 @@ async def send_audio(token, chat_id, audio, caption=None, duration=None, perform async def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, caption=None, thumb=None, caption_entities=None, allow_sending_without_reply=None, disable_content_type_detection=None, visible_file_name=None): - method_url = get_method_by_type(data_type) + method_url = await get_method_by_type(data_type) payload = {'chat_id': chat_id} files = None if not util.is_string(data):