mirror of
https://github.com/eternnoir/pyTelegramBotAPI.git
synced 2023-08-10 21:12:57 +03:00
Merge branch 'bugfixes' of https://github.com/coder2020official/pyTelegramBotAPI into bugfixes
This commit is contained in:
commit
194bf6e95d
1
.gitignore
vendored
1
.gitignore
vendored
@ -64,6 +64,7 @@ testMain.py
|
||||
#VS Code
|
||||
.vscode/
|
||||
.DS_Store
|
||||
*.code-workspace
|
||||
|
||||
# documentation
|
||||
_build/
|
||||
|
30
README.md
30
README.md
@ -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.**
|
||||
|
@ -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 ---------------------------------------------------
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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}">⁠</a>'
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
# Versions should comply with PEP440.
|
||||
# This line is parsed in setup.py:
|
||||
__version__ = '4.5.1'
|
||||
__version__ = '4.6.0'
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user