1
0
mirror of https://github.com/eternnoir/pyTelegramBotAPI.git synced 2023-08-10 21:12:57 +03:00
This commit is contained in:
_run 2022-07-11 23:23:10 +05:00
commit 194bf6e95d
23 changed files with 490 additions and 90 deletions

1
.gitignore vendored
View File

@ -64,6 +64,7 @@ testMain.py
#VS Code
.vscode/
.DS_Store
*.code-workspace
# documentation
_build/

View File

@ -11,7 +11,7 @@
<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">Both synchronous and asynchronous.</p>
## <p align="center">Supported Bot API version: <a href="https://core.telegram.org/bots/api#april-16-2022">6.0</a>!
## <p align="center">Supported Bot API version: <a href="https://core.telegram.org/bots/api#june-20-2022">6.1</a>!
<h2><a href='https://pytba.readthedocs.io/en/latest/index.html'>Official documentation</a></h2>
@ -358,7 +358,25 @@ def start(message):
There are other examples using middleware handler in the [examples/middleware](examples/middleware) directory.
#### Class-based middlewares
There are class-based middlewares. Check out in [examples](https://github.com/eternnoir/pyTelegramBotAPI/tree/master/examples/middleware/class_based)
There are class-based middlewares.
Basic class-based middleware looks like this:
```python
class Middleware(BaseMiddleware):
def __init__(self):
self.update_types = ['message']
def pre_process(self, message, data):
data['foo'] = 'Hello' # just for example
# we edited the data. now, this data is passed to handler.
# return SkipHandler() -> this will skip handler
# return CancelUpdate() -> this will cancel update
def post_process(self, message, data, exception=None):
print(data['foo'])
if exception: # check for exception
print(exception)
```
Class-based middleware should have to functions: post and pre process.
So, as you can see, class-based middlewares work before and after handler execution.
For more, check out in [examples](https://github.com/eternnoir/pyTelegramBotAPI/tree/master/examples/middleware/class_based)
#### Custom filters
Also, you can use built-in custom filters. Or, you can create your own filter.
@ -380,7 +398,7 @@ Here is example of creating filter-class:
```python
class IsAdmin(telebot.custom_filters.SimpleCustomFilter):
# Class will check whether the user is admin or creator in group or not
key='is_admin'
key='is_chat_admin'
@staticmethod
def check(message: telebot.types.Message):
return bot.get_chat_member(message.chat.id,message.from_user.id).status in ['administrator','creator']
@ -389,7 +407,7 @@ class IsAdmin(telebot.custom_filters.SimpleCustomFilter):
bot.add_custom_filter(IsAdmin())
# Now, you can use it in handler.
@bot.message_handler(is_admin=True)
@bot.message_handler(is_chat_admin=True)
def admin_of_group(message):
bot.send_message(message.chat.id, 'You are admin of this group!')
@ -709,6 +727,7 @@ Result will be:
## API conformance
* ✔ [Bot API 6.1](https://core.telegram.org/bots/api#june-20-2022)
* ✔ [Bot API 6.0](https://core.telegram.org/bots/api#april-16-2022)
* ✔ [Bot API 5.7](https://core.telegram.org/bots/api#january-31-2022)
* ✔ [Bot API 5.6](https://core.telegram.org/bots/api#december-30-2021)
@ -878,5 +897,8 @@ Here are some examples of template:
* [Pyfram-telegram-bot](https://github.com/skelly37/pyfram-telegram-bot) Query wolframalpha.com and make use of its API through Telegram.
* [TranslateThisVideoBot](https://gitlab.com/WuerfelDev/translatethisvideo) This Bot can understand spoken text in videos and translate it to English
* [Zyprexa](https://t.me/mathemathicsBot) ([source](https://github.com/atif5/zyprexa)) Zyprexa can solve, help you solve any mathematical problem you encounter and convert your regular mathematical expressions into beautiful imagery using LaTeX.
* [Bincode-telegram-bot](https://github.com/tusharhero/bincode-telegram-bot) by [tusharhero](https://github.com/tusharhero) - Makes [bincodes](https://github.com/tusharhero/bincode) from text provides and also converts them back to text.
* [hydrolib_bot](https://github.com/Mayson90/hydrolib_bot) Toolset for Hydrophilia tabletop game (game cards, rules, structure...).
* [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.
**Want to have your bot listed here? Just make a pull request. Only bots with public source code are accepted.**

View File

@ -22,7 +22,7 @@ copyright = '2022, coder2020official'
author = 'coder2020official'
# The full version, including alpha/beta/rc tags
release = '4.5.1'
release = '4.6.0'
# -- General configuration ---------------------------------------------------

View File

@ -36,6 +36,8 @@ async def start_message(message):
formatting.hunderline(message.from_user.first_name),
formatting.hstrikethrough(message.from_user.first_name),
formatting.hcode(message.from_user.first_name),
# hide_link is only for html
formatting.hide_link('https://telegra.ph/file/c158e3a6e2a26a160b253.jpg'),
separator=" "
),
parse_mode='HTML'

View File

@ -56,7 +56,7 @@ import keyboards
from telebot import types
from telebot.async_telebot import AsyncTeleBot
from telebot.asyncio_filters import TextMatchFilter, TextFilter
from i18n_base_midddleware import I18N
from i18n_base_middleware import I18N
from telebot.asyncio_storage.memory_storage import StateMemoryStorage

View File

@ -1,7 +1,6 @@
import telebot, threading
import telebot
from time import sleep, time
from telebot import InlineKeyboardMarkup as ikm #Only for creating Inline Buttons, not necessary for creating Invite Links
from telebot import InlineKeyboardButton as ikb #Only for creating Inline Buttons, not necessary for creating Invite Links
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton #Only for creating Inline Buttons, not necessary for creating Invite Links
Token = "api_token" #Your Bot Access Token
Group_ID = -1234567890 #Group ID for which invite link is to be created
@ -19,9 +18,11 @@ def newmember(msg):
#Create an invite link class that contains info about the created invite link using create_chat_invite_link() with parameters
invite = bot.create_chat_invite_link(Group_ID, member_limit=1, expire_date=int(time())+45) #Here, the link will auto-expire in 45 seconds
InviteLink = invite.invite_link #Get the actual invite link from 'invite' class
mrkplink = ikm() #Created Inline Markup Keyboard
mrkplink.add(ikb("Join our group 🚀", url=InviteLink)) #Added Invite Link to Inline Markup Keyboard
bot.send_message(msg.chat.id, f"Hey there {msg.from_user.first_name}, Click the link below to join our Official Group." reply_markup=mrkplink)
mrkplink = InlineKeyboardMarkup() #Created Inline Keyboard Markup
mrkplink.add(InlineKeyboardButton("Join our group 🚀", url=InviteLink)) #Added Invite Link to Inline Keyboard
bot.send_message(msg.chat.id, f"Hey there {msg.from_user.first_name}, Click the link below to join our Official Group.", reply_markup=mrkplink)
#This will send a message with the newly-created invite link as markup button.
#The member limit will be 1 and expiring time will be 45 sec.
@ -29,8 +30,4 @@ def newmember(msg):
while True:
try:
bot.infinity_polling()
except:
sleep(0.04)
bot.infinity_polling()

View File

@ -36,6 +36,8 @@ def start_message(message):
formatting.hunderline(message.from_user.first_name),
formatting.hstrikethrough(message.from_user.first_name),
formatting.hcode(message.from_user.first_name),
# hide_link is only for html
formatting.hide_link('https://telegra.ph/file/c158e3a6e2a26a160b253.jpg'),
separator=" "
),
parse_mode='HTML'

View File

@ -53,7 +53,7 @@ import asyncio
from typing import Union
import keyboards
from i18n_base_midddleware import I18N
from i18n_base_middleware import I18N
from telebot import TeleBot
from telebot import types, StateMemoryStorage
from telebot.custom_filters import TextMatchFilter, TextFilter

View File

@ -9,6 +9,7 @@ import time
import traceback
from typing import Any, Callable, List, Optional, Union
# these imports are used to avoid circular import error
import telebot.util
import telebot.types
@ -33,7 +34,7 @@ logger.addHandler(console_output_handler)
logger.setLevel(logging.ERROR)
from telebot import apihelper, util, types
from telebot.handler_backends import MemoryHandlerBackend, FileHandlerBackend, BaseMiddleware, CancelUpdate, SkipHandler
from telebot.handler_backends import MemoryHandlerBackend, FileHandlerBackend, BaseMiddleware, CancelUpdate, SkipHandler, State
from telebot.custom_filters import SimpleCustomFilter, AdvancedCustomFilter
@ -261,7 +262,7 @@ class TeleBot:
self.reply_backend.load_handlers(filename, del_file_after_loading)
def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None,
drop_pending_updates = None, timeout=None):
drop_pending_updates = None, timeout=None, secret_token=None):
"""
Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an
update for the bot, we will send an HTTPS POST request to the specified url,
@ -286,10 +287,11 @@ class TeleBot:
resolved through DNS
:param drop_pending_updates: Pass True to drop all pending updates
:param timeout: Integer. Request connection timeout
:param secret_token: Secret token to be used to verify the webhook request.
:return: API reply.
"""
return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates, ip_address,
drop_pending_updates, timeout)
drop_pending_updates, timeout, secret_token)
def delete_webhook(self, drop_pending_updates=None, timeout=None):
"""
@ -384,7 +386,7 @@ class TeleBot:
new_poll_answers = None
new_my_chat_members = None
new_chat_members = None
chat_join_request = None
new_chat_join_request = None
for update in updates:
if apihelper.ENABLE_MIDDLEWARE:
@ -441,8 +443,8 @@ class TeleBot:
if new_chat_members is None: new_chat_members = []
new_chat_members.append(update.chat_member)
if update.chat_join_request:
if chat_join_request is None: chat_join_request = []
chat_join_request.append(update.chat_join_request)
if new_chat_join_request is None: new_chat_join_request = []
new_chat_join_request.append(update.chat_join_request)
if new_messages:
self.process_new_messages(new_messages)
@ -470,8 +472,8 @@ class TeleBot:
self.process_new_my_chat_member(new_my_chat_members)
if new_chat_members:
self.process_new_chat_member(new_chat_members)
if chat_join_request:
self.process_new_chat_join_request(chat_join_request)
if new_chat_join_request:
self.process_new_chat_join_request(new_chat_join_request)
def process_new_messages(self, new_messages):
self._notify_next_handlers(new_messages)
@ -2462,6 +2464,69 @@ class TeleBot:
max_tip_amount, suggested_tip_amounts, protect_content)
return types.Message.de_json(result)
def create_invoice_link(self,
title: str, description: str, payload:str, provider_token: str,
currency: str, prices: List[types.LabeledPrice],
max_tip_amount: Optional[int] = None,
suggested_tip_amounts: Optional[List[int]]=None,
provider_data: Optional[str]=None,
photo_url: Optional[str]=None,
photo_size: Optional[int]=None,
photo_width: Optional[int]=None,
photo_height: Optional[int]=None,
need_name: Optional[bool]=None,
need_phone_number: Optional[bool]=None,
need_email: Optional[bool]=None,
need_shipping_address: Optional[bool]=None,
send_phone_number_to_provider: Optional[bool]=None,
send_email_to_provider: Optional[bool]=None,
is_flexible: Optional[bool]=None) -> str:
"""
Use this method to create a link for an invoice.
Returns the created invoice link as String on success.
Telegram documentation:
https://core.telegram.org/bots/api#createinvoicelink
:param title: Product name, 1-32 characters
:param description: Product description, 1-255 characters
:param payload: Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user,
use for your internal processes.
:param provider_token: Payments provider token, obtained via @Botfather
:param currency: Three-letter ISO 4217 currency code,
see https://core.telegram.org/bots/payments#supported-currencies
:param prices: Price breakdown, a list of components
(e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)
:param max_tip_amount: The maximum accepted amount for tips in the smallest units of the currency
:param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the smallest
:param provider_data: A JSON-serialized data about the invoice, which will be shared with the payment provider.
A detailed description of required fields should be provided by the payment provider.
:param photo_url: URL of the product photo for the invoice. Can be a photo of the goods
:param photo_size: Photo size in bytes
:param photo_width: Photo width
:param photo_height: Photo height
:param need_name: Pass True, if you require the user's full name to complete the order
:param need_phone_number: Pass True, if you require the user's phone number to complete the order
:param need_email: Pass True, if you require the user's email to complete the order
:param need_shipping_address: Pass True, if you require the user's shipping address to complete the order
:param send_phone_number_to_provider: Pass True, if user's phone number should be sent to provider
:param send_email_to_provider: Pass True, if user's email address should be sent to provider
:param is_flexible: Pass True, if the final price depends on the shipping method
:return: Created invoice link as String on success.
"""
result = apihelper.create_invoice_link(
self.token, title, description, payload, provider_token,
currency, prices, max_tip_amount, suggested_tip_amounts, provider_data,
photo_url, photo_size, photo_width, photo_height, need_name, need_phone_number,
need_email, need_shipping_address, send_phone_number_to_provider,
send_email_to_provider, is_flexible)
return result
# noinspection PyShadowingBuiltins
# TODO: rewrite this method like in API
def send_poll(
@ -2510,6 +2575,8 @@ class TeleBot:
if isinstance(question, types.Poll):
raise RuntimeError("The send_poll signature was changed, please see send_poll function details.")
explanation_parse_mode = self.parse_mode if (explanation_parse_mode is None) else explanation_parse_mode
return types.Message.de_json(
apihelper.send_poll(
self.token, chat_id,
@ -2860,7 +2927,7 @@ class TeleBot:
def set_state(self, user_id: int, state: Union[int, str], chat_id: int=None) -> None:
def set_state(self, user_id: int, state: Union[int, str, State], chat_id: int=None) -> None:
"""
Sets a new state of a user.
@ -3911,7 +3978,7 @@ class TeleBot:
middlewares = [i for i in self.middlewares if update_type in i.update_types]
return middlewares
def _run_middlewares_and_handler(self, message, handlers, middlewares, *args, **kwargs):
def _run_middlewares_and_handler(self, message, handlers, middlewares):
"""
This class is made to run handler and middleware in queue.
@ -3932,6 +3999,7 @@ class TeleBot:
return
elif isinstance(result, SkipHandler) and skip_handler is False:
skip_handler = True
try:
if handlers and not skip_handler:
@ -3943,24 +4011,31 @@ class TeleBot:
params.append(i)
if len(params) == 1:
handler['function'](message)
elif len(params) == 2:
if handler.get('pass_bot') is True:
handler['function'](message, self)
elif handler.get('pass_bot') is False:
handler['function'](message, data)
elif len(params) == 3:
if params[2] == 'bot' and handler.get('pass_bot') is True:
handler['function'](message, data, self)
else:
if "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
elif not handler.get('pass_bot'):
raise RuntimeError('Your handler accepts 3 parameters but pass_bot is False. Please re-check your handler.')
else:
handler['function'](message, self, data)
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 data than the handler needs. Check your handler: {}".format(handler['function']))
return
handler["function"](message, **data_copy)
except Exception as e:
handler_error = e

View File

@ -268,7 +268,7 @@ def send_message(
def set_webhook(token, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None,
drop_pending_updates = None, timeout=None):
drop_pending_updates = None, timeout=None, secret_token=None):
method_url = r'setWebhook'
payload = {
'url': url if url else "",
@ -286,6 +286,8 @@ def set_webhook(token, url=None, certificate=None, max_connections=None, allowed
payload['drop_pending_updates'] = drop_pending_updates
if timeout:
payload['timeout'] = timeout
if secret_token:
payload['secret_token'] = secret_token
return _make_request(token, method_url, params=payload, files=files)
@ -1622,6 +1624,45 @@ def answer_web_app_query(token, web_app_query_id, result: types.InlineQueryResul
return _make_request(token, method_url, params=payload, method='post')
def create_invoice_link(token, title, description, payload, provider_token,
currency, prices, max_tip_amount=None, suggested_tip_amounts=None, provider_data=None,
photo_url=None, photo_size=None, photo_width=None, photo_height=None, need_name=None, need_phone_number=None,
need_email=None, need_shipping_address=None, send_phone_number_to_provider=None,
send_email_to_provider=None, is_flexible=None):
method_url = r'createInvoiceLink'
payload = {'title': title, 'description': description, 'payload': payload, 'provider_token': provider_token,
'currency': currency, 'prices': _convert_list_json_serializable(prices)}
if max_tip_amount:
payload['max_tip_amount'] = max_tip_amount
if suggested_tip_amounts:
payload['suggested_tip_amounts'] = json.dumps(suggested_tip_amounts)
if provider_data:
payload['provider_data'] = provider_data
if photo_url:
payload['photo_url'] = photo_url
if photo_size:
payload['photo_size'] = photo_size
if photo_width:
payload['photo_width'] = photo_width
if photo_height:
payload['photo_height'] = photo_height
if need_name is not None:
payload['need_name'] = need_name
if need_phone_number is not None:
payload['need_phone_number'] = need_phone_number
if need_email is not None:
payload['need_email'] = need_email
if need_shipping_address is not None:
payload['need_shipping_address'] = need_shipping_address
if send_phone_number_to_provider is not None:
payload['send_phone_number_to_provider'] = send_phone_number_to_provider
if send_email_to_provider is not None:
payload['send_email_to_provider'] = send_email_to_provider
if is_flexible is not None:
payload['is_flexible'] = is_flexible
return _make_request(token, method_url, params=payload, method='post')
# noinspection PyShadowingBuiltins
def send_poll(
token, chat_id,

View File

@ -14,7 +14,7 @@ import telebot.types
# storages
from telebot.asyncio_storage import StateMemoryStorage, StatePickleStorage
from telebot.asyncio_handler_backends import CancelUpdate, SkipHandler
from telebot.asyncio_handler_backends import CancelUpdate, SkipHandler, State
from inspect import signature
@ -108,7 +108,6 @@ class AsyncTeleBot:
self.current_states = state_storage
self.middlewares = []
async def close_session(self):
@ -117,8 +116,22 @@ class AsyncTeleBot:
Use this function if you stop polling.
"""
await asyncio_helper.session_manager.session.close()
async def get_updates(self, offset: Optional[int]=None, limit: Optional[int]=None,
timeout: Optional[int]=None, allowed_updates: Optional[List]=None, request_timeout: Optional[int]=None) -> List[types.Update]:
timeout: Optional[int]=20, allowed_updates: Optional[List]=None, request_timeout: Optional[int]=None) -> List[types.Update]:
"""
Use this method to receive incoming updates using long polling (wiki).
An Array of Update objects is returned.
Telegram documentation: https://core.telegram.org/bots/api#making-requests
:param allowed_updates: Array of string. List the types of updates you want your bot to receive.
:param offset: Integer. Identifier of the first update to be returned.
:param limit: Integer. Limits the number of updates to be retrieved.
:param timeout: Integer. Request connection timeout
:param request_timeout: Timeout in seconds for a request.
:return: array of Updates
"""
json_updates = await asyncio_helper.get_updates(self.token, offset, limit, timeout, allowed_updates, request_timeout)
return [types.Update.de_json(ju) for ju in json_updates]
@ -277,7 +290,7 @@ class AsyncTeleBot:
handler_error = None
data = {}
process_handler = True
params = []
if middlewares:
for middleware in middlewares:
middleware_result = await middleware.pre_process(message, data)
@ -295,26 +308,37 @@ class AsyncTeleBot:
continue
elif process_update:
try:
params = []
for i in signature(handler['function']).parameters:
params.append(i)
if len(params) == 1:
await handler['function'](message)
break
elif len(params) == 2:
if handler['pass_bot']:
await handler['function'](message, self)
break
else:
await handler['function'](message, data)
break
elif len(params) == 3:
if handler['pass_bot'] and params[1] == 'bot':
await handler['function'](message, self, data)
break
else:
await handler['function'](message, data)
else:
if "data" in params:
if len(params) == 2:
await handler['function'](message, data)
break
elif len(params) == 3:
await handler['function'](message, data=data, bot=self)
break
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 data than the handler needs. Check your handler: {}".format(handler['function']))
return
await handler["function"](message, **data_copy)
break
except Exception as e:
handler_error = e
@ -1383,7 +1407,7 @@ class AsyncTeleBot:
self.current_states = StatePickleStorage(file_path=filename)
async def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None,
drop_pending_updates = None, timeout=None):
drop_pending_updates = None, timeout=None, secret_token=None):
"""
Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an
update for the bot, we will send an HTTPS POST request to the specified url,
@ -1408,10 +1432,11 @@ class AsyncTeleBot:
resolved through DNS
:param drop_pending_updates: Pass True to drop all pending updates
:param timeout: Integer. Request connection timeout
:param secret_token: Secret token to be used to verify the webhook
:return:
"""
return await asyncio_helper.set_webhook(self.token, url, certificate, max_connections, allowed_updates, ip_address,
drop_pending_updates, timeout)
drop_pending_updates, timeout, secret_token)
@ -1673,6 +1698,8 @@ class AsyncTeleBot:
:param protect_content:
:return: API reply.
"""
parse_mode = self.parse_mode if (parse_mode is None) else parse_mode
return types.MessageID.de_json(
await asyncio_helper.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,
@ -3063,6 +3090,67 @@ class AsyncTeleBot:
max_tip_amount, suggested_tip_amounts, protect_content)
return types.Message.de_json(result)
async def create_invoice_link(self,
title: str, description: str, payload:str, provider_token: str,
currency: str, prices: List[types.LabeledPrice],
max_tip_amount: Optional[int] = None,
suggested_tip_amounts: Optional[List[int]]=None,
provider_data: Optional[str]=None,
photo_url: Optional[str]=None,
photo_size: Optional[int]=None,
photo_width: Optional[int]=None,
photo_height: Optional[int]=None,
need_name: Optional[bool]=None,
need_phone_number: Optional[bool]=None,
need_email: Optional[bool]=None,
need_shipping_address: Optional[bool]=None,
send_phone_number_to_provider: Optional[bool]=None,
send_email_to_provider: Optional[bool]=None,
is_flexible: Optional[bool]=None) -> str:
"""
Use this method to create a link for an invoice.
Returns the created invoice link as String on success.
Telegram documentation:
https://core.telegram.org/bots/api#createinvoicelink
:param title: Product name, 1-32 characters
:param description: Product description, 1-255 characters
:param payload: Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user,
use for your internal processes.
:param provider_token: Payments provider token, obtained via @Botfather
:param currency: Three-letter ISO 4217 currency code,
see https://core.telegram.org/bots/payments#supported-currencies
:param prices: Price breakdown, a list of components
(e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)
:param max_tip_amount: The maximum accepted amount for tips in the smallest units of the currency
:param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the smallest
:param provider_data: A JSON-serialized data about the invoice, which will be shared with the payment provider.
A detailed description of required fields should be provided by the payment provider.
:param photo_url: URL of the product photo for the invoice. Can be a photo of the goods
:param photo_size: Photo size in bytes
:param photo_width: Photo width
:param photo_height: Photo height
:param need_name: Pass True, if you require the user's full name to complete the order
:param need_phone_number: Pass True, if you require the user's phone number to complete the order
:param need_email: Pass True, if you require the user's email to complete the order
:param need_shipping_address: Pass True, if you require the user's shipping address to complete the order
:param send_phone_number_to_provider: Pass True, if user's phone number should be sent to provider
:param send_email_to_provider: Pass True, if user's email address should be sent to provider
:param is_flexible: Pass True, if the final price depends on the shipping method
:return: Created invoice link as String on success.
"""
result = await asyncio_helper.create_invoice_link(
self.token, title, description, payload, provider_token,
currency, prices, max_tip_amount, suggested_tip_amounts, provider_data,
photo_url, photo_size, photo_width, photo_height, need_name, need_phone_number,
need_email, need_shipping_address, send_phone_number_to_provider,
send_email_to_provider, is_flexible)
return result
# noinspection PyShadowingBuiltins
async def send_poll(
self, chat_id: Union[int, str], question: str, options: List[str],
@ -3111,6 +3199,8 @@ class AsyncTeleBot:
if isinstance(question, types.Poll):
raise RuntimeError("The send_poll signature was changed, please see send_poll function details.")
explanation_parse_mode = self.parse_mode if (explanation_parse_mode is None) else explanation_parse_mode
return types.Message.de_json(
await asyncio_helper.send_poll(
self.token, chat_id,
@ -3381,7 +3471,7 @@ class AsyncTeleBot:
return await asyncio_helper.delete_sticker_from_set(self.token, sticker)
async def set_state(self, user_id: int, state: str, chat_id: int=None):
async def set_state(self, user_id: int, state: Union[State, int, str], chat_id: int=None):
"""
Sets a new state of a user.

View File

@ -201,6 +201,8 @@ class ChatFilter(AdvancedCustomFilter):
key = 'chat_id'
async def check(self, message, text):
if isinstance(message, types.CallbackQuery):
return message.message.chat.id in text
return message.chat.id in text
@ -216,7 +218,7 @@ class ForwardFilter(SimpleCustomFilter):
key = 'is_forwarded'
async def check(self, message):
return message.forward_from_chat is not None
return message.forward_date is not None
class IsReplyFilter(SimpleCustomFilter):
@ -231,6 +233,8 @@ class IsReplyFilter(SimpleCustomFilter):
key = 'is_reply'
async def check(self, message):
if isinstance(message, types.CallbackQuery):
return message.message.reply_to_message is not None
return message.reply_to_message is not None
@ -266,6 +270,9 @@ class IsAdminFilter(SimpleCustomFilter):
self._bot = bot
async def check(self, message):
if isinstance(message, types.CallbackQuery):
result = await self._bot.get_chat_member(message.message.chat.id, message.from_user.id)
return result.status ('creator', 'administrator')
result = await self._bot.get_chat_member(message.chat.id, message.from_user.id)
return result.status in ['creator', 'administrator']
@ -307,7 +314,7 @@ class StateFilter(AdvancedCustomFilter):
elif isinstance(text, State):
text = text.name
if message.chat.type == 'group':
if message.chat.type in ['group', 'supergroup']:
group_state = await self.bot.current_states.get_state(user_id, chat_id)
if group_state == text:
return True

View File

@ -29,6 +29,7 @@ class StatesGroup:
if not name.startswith('__') and not callable(value) and isinstance(value, State):
# change value of that variable
value.name = ':'.join((cls.__name__, name))
value.group = cls
class SkipHandler:

View File

@ -149,10 +149,10 @@ async def get_file(token, file_id):
async def get_file_url(token, file_id):
if FILE_URL is None:
return "https://api.telegram.org/file/bot{0}/{1}".format(token, get_file(token, file_id)['file_path'])
return "https://api.telegram.org/file/bot{0}/{1}".format(token, await get_file(token, file_id)['file_path'])
else:
# noinspection PyUnresolvedReferences
return FILE_URL.format(token, get_file(token, file_id)['file_path'])
return FILE_URL.format(token, await get_file(token, file_id)['file_path'])
async def download_file(token, file_path):
@ -171,7 +171,7 @@ async def download_file(token, file_path):
async def set_webhook(token, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None,
drop_pending_updates = None, timeout=None):
drop_pending_updates = None, timeout=None, secret_token=None):
method_url = r'setWebhook'
payload = {
'url': url if url else "",
@ -189,6 +189,8 @@ async def set_webhook(token, url=None, certificate=None, max_connections=None, a
payload['drop_pending_updates'] = drop_pending_updates
if timeout:
payload['timeout'] = timeout
if secret_token:
payload['secret_token'] = secret_token
return await _process_request(token, method_url, params=payload, files=files)
@ -217,11 +219,11 @@ async def get_updates(token, offset=None, limit=None,
params = {}
if offset:
params['offset'] = offset
elif limit:
if limit:
params['limit'] = limit
elif timeout:
if timeout:
params['timeout'] = timeout
elif allowed_updates:
if allowed_updates:
params['allowed_updates'] = allowed_updates
return await _process_request(token, method_name, params=params, request_timeout=request_timeout)
@ -1599,6 +1601,47 @@ async def delete_sticker_from_set(token, sticker):
return await _process_request(token, method_url, params=payload, method='post')
async def create_invoice_link(token, title, description, payload, provider_token,
currency, prices, max_tip_amount=None, suggested_tip_amounts=None, provider_data=None,
photo_url=None, photo_size=None, photo_width=None, photo_height=None, need_name=None, need_phone_number=None,
need_email=None, need_shipping_address=None, send_phone_number_to_provider=None,
send_email_to_provider=None, is_flexible=None):
method_url = r'createInvoiceLink'
payload = {'title': title, 'description': description, 'payload': payload, 'provider_token': provider_token,
'currency': currency, 'prices': await _convert_list_json_serializable(prices)}
if max_tip_amount:
payload['max_tip_amount'] = max_tip_amount
if suggested_tip_amounts:
payload['suggested_tip_amounts'] = json.dumps(suggested_tip_amounts)
if provider_data:
payload['provider_data'] = provider_data
if photo_url:
payload['photo_url'] = photo_url
if photo_size:
payload['photo_size'] = photo_size
if photo_width:
payload['photo_width'] = photo_width
if photo_height:
payload['photo_height'] = photo_height
if need_name is not None:
payload['need_name'] = need_name
if need_phone_number is not None:
payload['need_phone_number'] = need_phone_number
if need_email is not None:
payload['need_email'] = need_email
if need_shipping_address is not None:
payload['need_shipping_address'] = need_shipping_address
if send_phone_number_to_provider is not None:
payload['send_phone_number_to_provider'] = send_phone_number_to_provider
if send_email_to_provider is not None:
payload['send_email_to_provider'] = send_email_to_provider
if is_flexible is not None:
payload['is_flexible'] = is_flexible
return await _process_request(token, method_url, params=payload, method='post')
# noinspection PyShadowingBuiltins
async def send_poll(
token, chat_id,
@ -1778,4 +1821,4 @@ class RequestTimeout(Exception):
"""
This class represents a request timeout.
"""
pass
pass

View File

@ -1,3 +1,30 @@
"""
Copyright (c) 2017-2018 Alex Root Junior
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
This file was added during the pull request. The maintainers overlooked that it was copied
"as is" from another project and they do not consider it as a right way to develop a project.
However, due to backward compatibility we had to leave this file in the project with the above
copyright added, as it is required by the original project license.
"""
import typing

View File

@ -208,6 +208,8 @@ class ChatFilter(AdvancedCustomFilter):
key = 'chat_id'
def check(self, message, text):
if isinstance(message, types.CallbackQuery):
return message.message.chat.id in text
return message.chat.id in text
@ -223,7 +225,7 @@ class ForwardFilter(SimpleCustomFilter):
key = 'is_forwarded'
def check(self, message):
return message.forward_from_chat is not None
return message.forward_date is not None
class IsReplyFilter(SimpleCustomFilter):
@ -238,6 +240,8 @@ class IsReplyFilter(SimpleCustomFilter):
key = 'is_reply'
def check(self, message):
if isinstance(message, types.CallbackQuery):
return message.message.reply_to_message is not None
return message.reply_to_message is not None
@ -273,6 +277,8 @@ class IsAdminFilter(SimpleCustomFilter):
self._bot = bot
def check(self, message):
if isinstance(message, types.CallbackQuery):
return self._bot.get_chat_member(message.message.chat.id, message.from_user.id).status in ['creator', 'administrator']
return self._bot.get_chat_member(message.chat.id, message.from_user.id).status in ['creator', 'administrator']
@ -315,7 +321,7 @@ class StateFilter(AdvancedCustomFilter):
elif isinstance(text, State):
text = text.name
if message.chat.type == 'group':
if message.chat.type in ['group', 'supergroup']:
group_state = self.bot.current_states.get_state(user_id, chat_id)
if group_state == text:
return True

View File

@ -198,4 +198,14 @@ def hpre(content: str, escape: bool=True, language: str="") -> str:
:param content: The string to preformatted.
:param escape: True if you need to escape special characters.
"""
return '<pre><code class="{}">{}</code></pre>'.format(language, escape_html(content) if escape else content)
return '<pre><code class="{}">{}</code></pre>'.format(language, escape_html(content) if escape else content)
def hide_link(url: str) -> str:
"""
Hide url of an image.
:param url:
:return: str
"""
return f'<a href="{url}">&#8288;</a>'

View File

@ -163,6 +163,10 @@ class StatesGroup:
if not name.startswith('__') and not callable(value) and isinstance(value, State):
# change value of that variable
value.name = ':'.join((cls.__name__, name))
value.group = cls
class BaseMiddleware:

View File

@ -215,7 +215,8 @@ class User(JsonDeserializable, Dictionaryable, JsonSerializable):
return cls(**obj)
def __init__(self, id, is_bot, first_name, last_name=None, username=None, language_code=None,
can_join_groups=None, can_read_all_group_messages=None, supports_inline_queries=None, **kwargs):
can_join_groups=None, can_read_all_group_messages=None, supports_inline_queries=None,
is_premium=None, added_to_attachment_menu=None, **kwargs):
self.id: int = id
self.is_bot: bool = is_bot
self.first_name: str = first_name
@ -225,6 +226,9 @@ class User(JsonDeserializable, Dictionaryable, JsonSerializable):
self.can_join_groups: bool = can_join_groups
self.can_read_all_group_messages: bool = can_read_all_group_messages
self.supports_inline_queries: bool = supports_inline_queries
self.is_premium: bool = is_premium
self.added_to_attachment_menu: bool = added_to_attachment_menu
@property
def full_name(self):
@ -280,7 +284,8 @@ class Chat(JsonDeserializable):
description=None, invite_link=None, pinned_message=None,
permissions=None, slow_mode_delay=None,
message_auto_delete_time=None, has_protected_content=None, sticker_set_name=None,
can_set_sticker_set=None, linked_chat_id=None, location=None, **kwargs):
can_set_sticker_set=None, linked_chat_id=None, location=None,
join_to_send_messages=None, join_by_request=None, **kwargs):
self.id: int = id
self.type: str = type
self.title: str = title
@ -289,6 +294,8 @@ class Chat(JsonDeserializable):
self.last_name: str = last_name
self.photo: ChatPhoto = photo
self.bio: str = bio
self.join_to_send_messages: bool = join_to_send_messages
self.join_by_request: bool = join_by_request
self.has_private_forwards: bool = has_private_forwards
self.description: str = description
self.invite_link: str = invite_link
@ -1241,9 +1248,10 @@ class CallbackQuery(JsonDeserializable):
obj['from_user'] = User.de_json(obj.pop('from'))
if 'message' in obj:
obj['message'] = Message.de_json(obj.get('message'))
obj['json_string'] = json_string
return cls(**obj)
def __init__(self, id, from_user, data, chat_instance, message=None, inline_message_id=None, game_short_name=None, **kwargs):
def __init__(self, id, from_user, data, chat_instance, json_string, message=None, inline_message_id=None, game_short_name=None, **kwargs):
self.id: int = id
self.from_user: User = from_user
self.message: Message = message
@ -1251,6 +1259,7 @@ class CallbackQuery(JsonDeserializable):
self.chat_instance: str = chat_instance
self.data: str = data
self.game_short_name: str = game_short_name
self.json = json_string
class ChatPhoto(JsonDeserializable):
@ -1273,7 +1282,23 @@ class ChatMember(JsonDeserializable):
if json_string is None: return None
obj = cls.check_json(json_string)
obj['user'] = User.de_json(obj['user'])
return cls(**obj)
member_type = obj['status']
# Ordered according to estimated appearance frequency.
if member_type == "member":
return ChatMemberMember(**obj)
elif member_type == "left":
return ChatMemberLeft(**obj)
elif member_type == "kicked":
return ChatMemberBanned(**obj)
elif member_type == "restricted":
return ChatMemberRestricted(**obj)
elif member_type == "administrator":
return ChatMemberAdministrator(**obj)
elif member_type == "creator":
return ChatMemberOwner(**obj)
else:
# Should not be here. For "if something happen" compatibility
return cls(**obj)
def __init__(self, user, status, custom_title=None, is_anonymous=None, can_be_edited=None,
can_post_messages=None, can_edit_messages=None, can_delete_messages=None,
@ -1311,6 +1336,7 @@ class ChatMember(JsonDeserializable):
class ChatMemberOwner(ChatMember):
pass
class ChatMemberAdministrator(ChatMember):
pass
@ -2576,10 +2602,13 @@ class Sticker(JsonDeserializable):
obj['thumb'] = None
if 'mask_position' in obj:
obj['mask_position'] = MaskPosition.de_json(obj['mask_position'])
if 'premium_animation' in obj:
obj['premium_animation'] = File.de_json(obj['premium_animation'])
return cls(**obj)
def __init__(self, file_id, file_unique_id, width, height, is_animated,
is_video, thumb=None, emoji=None, set_name=None, mask_position=None, file_size=None, **kwargs):
is_video, thumb=None, emoji=None, set_name=None, mask_position=None, file_size=None,
premium_animation=None, **kwargs):
self.file_id: str = file_id
self.file_unique_id: str = file_unique_id
self.width: int = width
@ -2591,6 +2620,7 @@ class Sticker(JsonDeserializable):
self.set_name: str = set_name
self.mask_position: MaskPosition = mask_position
self.file_size: int = file_size
self.premium_animation: File = premium_animation

View File

@ -5,6 +5,9 @@ import string
import threading
import traceback
from typing import Any, Callable, List, Dict, Optional, Union
import hmac
from hashlib import sha256
from urllib.parse import parse_qsl
# noinspection PyPep8Naming
import queue as Queue
@ -45,7 +48,7 @@ content_type_service = [
]
update_types = [
"update_id", "message", "edited_message", "channel_post", "edited_channel_post", "inline_query",
"message", "edited_message", "channel_post", "edited_channel_post", "inline_query",
"chosen_inline_result", "callback_query", "shipping_query", "pre_checkout_query", "poll", "poll_answer",
"my_chat_member", "chat_member", "chat_join_request"
]
@ -370,7 +373,8 @@ def quick_markup(values: Dict[str, Dict[str, Any]], row_width: int=2) -> types.I
'switch_inline_query_current_chat': None,
'callback_game': None,
'pay': None,
'login_url': None
'login_url': None,
'web_app': None
}
:param values: a dict containing all buttons to create in this format: {text: kwargs} {str:}
@ -517,3 +521,36 @@ def antiflood(function, *args, **kwargs):
msg = function(*args, **kwargs)
finally:
return msg
def parse_web_app_data(token: str, raw_init_data: str):
is_valid = validate_web_app_data(token, raw_init_data)
if not is_valid:
return False
result = {}
for key, value in parse_qsl(raw_init_data):
try:
value = json.loads(value)
except json.JSONDecodeError:
result[key] = value
else:
result[key] = value
return result
def validate_web_app_data(token, raw_init_data):
try:
parsed_data = dict(parse_qsl(raw_init_data))
except ValueError:
return False
if "hash" not in parsed_data:
return False
init_data_hash = parsed_data.pop('hash')
data_check_string = "\n".join(f"{key}={value}" for key, value in sorted(parsed_data.items()))
secret_key = hmac.new(key=b"WebAppData", msg=token.encode(), digestmod=sha256)
return hmac.new(secret_key.digest(), data_check_string.encode(), sha256).hexdigest() == init_data_hash

View File

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

View File

@ -6,10 +6,13 @@ from telebot import types
def test_json_user():
jsonstring = r'{"id":101176298,"first_name":"RDSSBOT","last_name":")))","username":"rdss_bot","is_bot":true}'
jsonstring = r'{"id":101176298,"first_name":"RDSSBOT","last_name":")))","username":"rdss_bot","is_bot":true, "is_premium":true, "added_to_attachment_menu": true}'
u = types.User.de_json(jsonstring)
assert u.id == 101176298
assert u.full_name == 'RDSSBOT )))'
assert u.is_premium is True
assert u.added_to_attachment_menu is True
def test_json_message():
@ -155,12 +158,14 @@ def test_json_update():
def test_json_chat():
json_string = r'{"id": -111111,"title": "Test Title","type": "group"}'
json_string = r'{"id": -111111,"title": "Test Title","type": "group", "join_to_send_messages": true, "join_by_request": true}'
chat = types.Chat.de_json(json_string)
assert chat.id == -111111
assert chat.type == 'group'
assert chat.title == 'Test Title'
assert chat.join_to_send_messages is True
assert chat.join_by_request is True
def test_InlineQueryResultCachedPhoto():
iq = types.InlineQueryResultCachedPhoto('aaa', 'Fileid')