Compare commits

...

21 Commits

Author SHA1 Message Date
Badiboy fbf34f5953
Bump version to 4.2.1 - AsyncTeleBot alpha 2021-12-04 20:25:39 +03:00
Badiboy 4347dd3dd9
Merge pull request #1380 from coder2020official/master
2 new examples and behaviour change
2021-12-04 20:08:26 +03:00
_run d830ae0b15 Merge branch 'master' of https://github.com/coder2020official/pyTelegramBotAPI 2021-12-04 22:03:56 +05:00
_run 4f198bc6f5 Forgot to update file 2021-12-04 22:03:14 +05:00
_run 66615a41c4
Merge branch 'eternnoir:master' into master 2021-12-04 21:57:32 +05:00
_run a5ee5f816c Update README.md 2021-12-04 21:57:16 +05:00
_run fb52137bff 2 new examples 2021-12-04 21:54:26 +05:00
Badiboy 7ee07f4dc7
Merge pull request #1379 from Badiboy/master
Readme
2021-12-04 19:44:00 +03:00
Badiboy f224069a34 Update README.md 2021-12-04 19:43:33 +03:00
Badiboy 6cca77f755 Update README.md 2021-12-04 19:43:01 +03:00
Badiboy 084289baa4
Merge pull request #1378 from Badiboy/master
Readme minor fixed
2021-12-04 19:42:08 +03:00
Badiboy e2dbb88459 Readme minor fixed 2021-12-04 19:41:25 +03:00
_run a2822c74ed
Update README.md 2021-12-04 21:34:15 +05:00
Badiboy 4cd30c75ac
Merge pull request #1377 from coder2020official/master
Exception handler, Boolean Fix and more
2021-12-04 19:33:53 +03:00
_run f4b9480588
Update README.md 2021-12-04 21:25:47 +05:00
_run 482589af49
Update README.md 2021-12-04 21:25:14 +05:00
_run bbe4a96984
Update README.md 2021-12-04 21:23:23 +05:00
_run 60294d0c41
Update README.md 2021-12-04 21:22:44 +05:00
_run 3035763277 Update send_file_example.py 2021-12-04 21:22:00 +05:00
_run 51eabde320 Update 2021-12-04 21:11:51 +05:00
_run a5305f551c
Update README.md 2021-12-03 21:13:02 +05:00
11 changed files with 396 additions and 32 deletions

View File

@ -6,9 +6,10 @@
# <p align="center">pyTelegramBotAPI
<p align="center">A simple, but extensible Python implementation for the <a href="https://core.telegram.org/bots/api">Telegram Bot API</a>.
<p align="center">A simple, but extensible Python implementation for the <a href="https://core.telegram.org/bots/api">Telegram Bot API</a>.</p>
<p align="center">Supports both sync and async ways.</p>
## <p align="center">Supported Bot API version: <a href="https://core.telegram.org/bots/api#november-5-2021">5.4</a>!
## <p align="center">Supporting Bot API version: <a href="https://core.telegram.org/bots/api#november-5-2021">5.4</a>!
## Contents
@ -43,7 +44,7 @@
* [Reply markup](#reply-markup)
* [Advanced use of the API](#advanced-use-of-the-api)
* [Using local Bot API Server](#using-local-bot-api-sever)
* [Asynchronous delivery of messages](#asynchronous-delivery-of-messages)
* [Asynchronous TeleBot](#asynchronous-telebot)
* [Sending large text messages](#sending-large-text-messages)
* [Controlling the amount of Threads used by TeleBot](#controlling-the-amount-of-threads-used-by-telebot)
* [The listener mechanism](#the-listener-mechanism)
@ -52,6 +53,7 @@
* [Proxy](#proxy)
* [Testing](#testing)
* [API conformance](#api-conformance)
* [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)
@ -182,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:
@ -374,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!')
```
@ -555,26 +557,26 @@ apihelper.API_URL = "http://localhost:4200/bot{0}/{1}"
*Note: 4200 is an example port*
### Asynchronous delivery of messages
There exists an implementation of TeleBot which executes all `send_xyz` and the `get_me` functions asynchronously. This can speed up your bot __significantly__, but it has unwanted side effects if used without caution.
### Asynchronous TeleBot
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")
```
Now, every function that calls the Telegram API is executed in a separate Thread. The functions are modified to return an AsyncTask instance (defined in util.py). Using AsyncTeleBot allows you to do the following:
Now, every function that calls the Telegram API is executed in a separate asynchronous task.
Using AsyncTeleBot allows you to do the following:
```python
import telebot
tb = telebot.AsyncTeleBot("TOKEN")
task = tb.get_me() # Execute an API call
# Do some other operations...
a = 0
for a in range(100):
a += 10
result = task.wait() # Get the result of the execution
@tb.message_handler(commands=['start'])
async def start_message(message):
await bot.send_message(message.chat.id, 'Hello!')
```
*Note: if you execute send_xyz functions after eachother without calling wait(), the order in which messages are delivered might be wrong.*
See more in [examples](https://github.com/eternnoir/pyTelegramBotAPI/tree/master/examples/asynchronous_telebot)
### Sending large text messages
Sometimes you must send messages that exceed 5000 characters. The Telegram API can not handle that many characters in one request, so we need to split the message in multiples. Here is how to do that using the API:
@ -712,6 +714,52 @@ Result will be:
* ✔ [Bot API 2.0](https://core.telegram.org/bots/api-changelog#april-9-2016)
## AsyncTeleBot
### Asynchronous version of telebot
We have a fully asynchronous version of TeleBot.
This class is not controlled by threads. Asyncio tasks are created to execute all the stuff.
### EchoBot
Echo Bot example on AsyncTeleBot:
```python
# This is a simple echo bot using the decorator mechanism.
# It echoes any incoming text messages.
from telebot.async_telebot import AsyncTeleBot
bot = AsyncTeleBot('TOKEN')
# Handle '/start' and '/help'
@bot.message_handler(commands=['help', 'start'])
async def send_welcome(message):
await bot.reply_to(message, """\
Hi there, I am EchoBot.
I am here to echo your kind words back to you. Just say anything nice and I'll say the exact same thing to you!\
""")
# Handle all other messages with content_type 'text' (content_types defaults to ['text'])
@bot.message_handler(func=lambda message: True)
async def echo_message(message):
await bot.reply_to(message, message.text)
bot.polling()
```
As you can see here, keywords are await and async.
### Why should I use async?
Asynchronous tasks depend on processor performance. Many asynchronous tasks can run parallelly, while thread tasks will block each other.
### Differences in AsyncTeleBot
AsyncTeleBot has different middlewares. See example on [middlewares](https://github.com/coder2020official/pyTelegramBotAPI/tree/master/examples/asynchronous_telebot/middleware)
### Examples
See more examples in our [examples](https://github.com/coder2020official/pyTelegramBotAPI/tree/master/examples/asynchronous_telebot) folder
## F.A.Q.
### How can I distinguish a User and a GroupChat in message.chat?
@ -721,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

View File

@ -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()

View File

@ -0,0 +1,20 @@
import telebot
from telebot.async_telebot import AsyncTeleBot
bot = AsyncTeleBot('TOKEN')
@bot.message_handler(content_types=['photo'])
async def new_message(message: telebot.types.Message):
result_message = await bot.send_message(message.chat.id, '<i>Downloading your photo...</i>', parse_mode='HTML', disable_web_page_preview=True)
file_path = await bot.get_file(message.photo[-1].file_id)
downloaded_file = await bot.download_file(file_path.file_path)
with open('file.jpg', 'wb') as new_file:
new_file.write(downloaded_file)
await bot.edit_message_text(chat_id=message.chat.id, message_id=result_message.id, text='<i>Done!</i>', parse_mode='HTML')
bot.polling(skip_pending=True)

View File

@ -0,0 +1,27 @@
import telebot
from telebot.async_telebot import AsyncTeleBot
import logging
logger = telebot.logger
telebot.logger.setLevel(logging.DEBUG) # Outputs debug messages to console.
class ExceptionHandler(telebot.ExceptionHandler):
def handle(self, exception):
logger.error(exception)
bot = AsyncTeleBot('TOKEN',exception_handler=ExceptionHandler())
@bot.message_handler(commands=['photo'])
async def photo_send(message: telebot.types.Message):
await bot.send_message(message.chat.id, 'Hi, this is an example of exception handlers.')
raise Exception('test') # Exception goes to ExceptionHandler if it is set
bot.polling(skip_pending=True)

View File

@ -0,0 +1,39 @@
# Just a little example of middleware handlers
import telebot
from telebot.asyncio_handler_backends import BaseMiddleware
from telebot.async_telebot import AsyncTeleBot
from telebot.async_telebot import CancelUpdate
bot = AsyncTeleBot('TOKEN')
class SimpleMiddleware(BaseMiddleware):
def __init__(self, limit) -> None:
self.last_time = {}
self.limit = limit
self.update_types = ['message']
# Always specify update types, otherwise middlewares won't work
async def pre_process(self, message, data):
if not message.from_user.id in self.last_time:
# User is not in a dict, so lets add and cancel this function
self.last_time[message.from_user.id] = message.date
return
if message.date - self.last_time[message.from_user.id] < self.limit:
# User is flooding
await bot.send_message(message.chat.id, 'You are making request too often')
return CancelUpdate()
self.last_time[message.from_user.id] = message.date
async def post_process(self, message, data, exception):
pass
bot.setup_middleware(SimpleMiddleware(2))
@bot.message_handler(commands=['start'])
async def start(message):
await bot.send_message(message.chat.id, 'Hello!')
bot.polling()

View File

@ -0,0 +1,48 @@
#!/usr/bin/python
# This example shows how to implement i18n (internationalization) l10n (localization) to create
# multi-language bots with middleware handler.
#
# Also, you could check language code in handler itself too.
# But this example just to show the work of middlewares.
import telebot
from telebot.async_telebot import AsyncTeleBot
from telebot import asyncio_handler_backends
import logging
logger = telebot.logger
telebot.logger.setLevel(logging.DEBUG) # Outputs debug messages to console.
TRANSLATIONS = {
'hello': {
'en': 'hello',
'ru': 'привет',
'uz': 'salom'
}
}
bot = AsyncTeleBot('TOKEN')
class LanguageMiddleware(asyncio_handler_backends.BaseMiddleware):
def __init__(self):
self.update_types = ['message'] # Update types that will be handled by this middleware.
async def pre_process(self, message, data):
data['response'] = TRANSLATIONS['hello'][message.from_user.language_code]
async def post_process(self, message, data, exception):
if exception: # You can get exception occured in handler.
logger.exception(str(exception))
bot.setup_middleware(LanguageMiddleware()) # do not forget to setup
@bot.message_handler(commands=['start'])
async def start(message, data: dict):
# you can get the data in handler too.
# Not necessary to create data parameter in handler function.
await bot.send_message(message.chat.id, data['response'])
bot.polling()

View File

@ -0,0 +1,27 @@
import telebot
from telebot.async_telebot import AsyncTeleBot
bot = AsyncTeleBot('TOKEN')
@bot.message_handler(commands=['photo'])
async def photo_send(message: telebot.types.Message):
with open('test.png', 'rb') as new_file:
await bot.send_photo(message.chat.id, new_file)
@bot.message_handler(commands=['document'])
async def document_send(message: telebot.types.Message):
with open('test.docx', 'rb') as new_file:
await bot.send_document(message.chat.id, new_file)
@bot.message_handler(commands=['photos'])
async def photos_send(message: telebot.types.Message):
with open('test.png', 'rb') as new_file, open('test2.png', 'rb') as new_file2:
await bot.send_media_group(message.chat.id, [telebot.types.InputMediaPhoto(new_file), telebot.types.InputMediaPhoto(new_file2)])
bot.polling(skip_pending=True)

View File

@ -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()

View File

@ -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.

View File

@ -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):

View File

@ -1,3 +1,3 @@
# Versions should comply with PEP440.
# This line is parsed in setup.py:
__version__ = '4.2.0'
__version__ = '4.2.1'