mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-11 01:54:53 +00:00
Merge branch 'dev'
# Conflicts: # aiogram/__init__.py
This commit is contained in:
commit
a45dbe1e94
9 changed files with 222 additions and 50 deletions
|
|
@ -1,6 +1,6 @@
|
|||
from .bot import Bot
|
||||
from .utils.versions import Version, Stage
|
||||
|
||||
VERSION = Version(0, 4, stage=Stage.FINAL, build=0)
|
||||
VERSION = Version(0, 4, 1, stage=Stage.DEV, build=0)
|
||||
|
||||
__version__ = VERSION.version
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import aiohttp
|
|||
|
||||
from ..utils import json
|
||||
from ..utils.exceptions import ValidationError, TelegramAPIError, BadRequest, Unauthorized, NetworkError, RetryAfter, \
|
||||
MigrateToChat
|
||||
MigrateToChat, ConflictError
|
||||
from ..utils.helper import Helper, HelperMode, Item
|
||||
|
||||
# Main aiogram logger
|
||||
|
|
@ -66,6 +66,8 @@ async def _check_result(method_name, response):
|
|||
raise MigrateToChat(result_json['migrate_to_chat_id'])
|
||||
elif response.status == HTTPStatus.BAD_REQUEST:
|
||||
raise BadRequest(description)
|
||||
elif response.status == HTTPStatus.CONFLICT:
|
||||
raise ConflictError(description)
|
||||
elif response.status in [HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN]:
|
||||
raise Unauthorized(description)
|
||||
elif response.status == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import asyncio
|
||||
import functools
|
||||
import logging
|
||||
import typing
|
||||
|
||||
|
|
@ -8,6 +9,7 @@ from .storage import DisabledStorage, BaseStorage, FSMContext
|
|||
from .webhook import BaseResponse
|
||||
from ..bot import Bot
|
||||
from ..types.message import ContentType
|
||||
from ..utils.exceptions import TelegramAPIError, NetworkError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -77,7 +79,7 @@ class Dispatcher:
|
|||
"""
|
||||
tasks = []
|
||||
for update in updates:
|
||||
tasks.append(self.loop.create_task(self.updates_handler.notify(update)))
|
||||
tasks.append(self.updates_handler.notify(update))
|
||||
return await asyncio.gather(*tasks)
|
||||
|
||||
async def process_update(self, update):
|
||||
|
|
@ -125,10 +127,9 @@ class Dispatcher:
|
|||
while self._pooling:
|
||||
try:
|
||||
updates = await self.bot.get_updates(limit=limit, offset=offset, timeout=timeout)
|
||||
except Exception as e:
|
||||
log.exception('Cause exception while getting updates')
|
||||
if relax:
|
||||
await asyncio.sleep(relax)
|
||||
except NetworkError:
|
||||
log.exception('Cause exception while getting updates.')
|
||||
await asyncio.sleep(15)
|
||||
continue
|
||||
|
||||
if updates:
|
||||
|
|
@ -137,7 +138,8 @@ class Dispatcher:
|
|||
|
||||
self.loop.create_task(self._process_pooling_updates(updates))
|
||||
|
||||
await asyncio.sleep(relax)
|
||||
if relax:
|
||||
await asyncio.sleep(relax)
|
||||
|
||||
log.warning('Pooling is stopped.')
|
||||
|
||||
|
|
@ -157,7 +159,7 @@ class Dispatcher:
|
|||
if need_to_call:
|
||||
try:
|
||||
asyncio.gather(*need_to_call)
|
||||
except Exception as e:
|
||||
except TelegramAPIError:
|
||||
log.exception('Cause exception while processing updates.')
|
||||
|
||||
def stop_pooling(self):
|
||||
|
|
@ -711,3 +713,30 @@ class Dispatcher:
|
|||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None) -> FSMContext:
|
||||
return FSMContext(storage=self.storage, chat=chat, user=user)
|
||||
|
||||
def async_task(self, func):
|
||||
"""
|
||||
Execute handler as task and return None.
|
||||
Use that decorator for slow handlers (with timeouts)
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
@dp.message_handler(commands=['command'])
|
||||
@dp.async_task
|
||||
async def cmd_with_timeout(message: types.Message):
|
||||
await asyncio.sleep(120)
|
||||
return SendMessage(message.chat.id, 'KABOOM').reply(message)
|
||||
|
||||
:param func:
|
||||
:return:
|
||||
"""
|
||||
def process_response(task):
|
||||
response = task.result()
|
||||
self.loop.create_task(response.execute_response(self.bot))
|
||||
|
||||
@functools.wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
task = self.loop.create_task(func(*args, **kwargs))
|
||||
task.add_done_callback(process_response)
|
||||
|
||||
return wrapper
|
||||
|
|
|
|||
|
|
@ -5,18 +5,13 @@ from ..utils.helper import Helper, HelperMode, Item
|
|||
|
||||
|
||||
async def check_filter(filter_, args, kwargs):
|
||||
# TODO: Refactor that shit.
|
||||
if not callable(filter_):
|
||||
raise TypeError('Filter must be callable and/or awaitable!')
|
||||
|
||||
if any((inspect.isasyncgen(filter_),
|
||||
inspect.iscoroutine(filter_),
|
||||
inspect.isawaitable(filter_),
|
||||
inspect.isasyncgenfunction(filter_),
|
||||
inspect.iscoroutinefunction(filter_))):
|
||||
if inspect.isawaitable(filter_) or inspect.iscoroutinefunction(filter_):
|
||||
return await filter_(*args, **kwargs)
|
||||
elif callable(filter_):
|
||||
return filter_(*args, **kwargs)
|
||||
else:
|
||||
return True
|
||||
return filter_(*args, **kwargs)
|
||||
|
||||
|
||||
async def check_filters(filters, args, kwargs):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import asyncio
|
||||
import asyncio.tasks
|
||||
import datetime
|
||||
import functools
|
||||
import typing
|
||||
from typing import Union, Dict, Optional
|
||||
|
||||
|
|
@ -8,11 +11,15 @@ from .. import types
|
|||
from ..bot import api
|
||||
from ..bot.base import Integer, String, Boolean, Float
|
||||
from ..utils import json
|
||||
from ..utils.deprecated import warn_deprecated as warn
|
||||
from ..utils.exceptions import TimeoutWarning
|
||||
from ..utils.payload import prepare_arg
|
||||
|
||||
DEFAULT_WEB_PATH = '/webhook'
|
||||
BOT_DISPATCHER_KEY = 'BOT_DISPATCHER'
|
||||
|
||||
RESPONSE_TIMEOUT = 55
|
||||
|
||||
|
||||
class WebhookRequestHandler(web.View):
|
||||
"""
|
||||
|
|
@ -66,12 +73,83 @@ class WebhookRequestHandler(web.View):
|
|||
"""
|
||||
dispatcher = self.get_dispatcher()
|
||||
update = await self.parse_update(dispatcher.bot)
|
||||
results = await dispatcher.process_update(update)
|
||||
|
||||
results = await self.process_update(update)
|
||||
response = self.get_response(results)
|
||||
|
||||
if response:
|
||||
return response.get_web_response()
|
||||
return web.Response(text='ok')
|
||||
|
||||
async def process_update(self, update):
|
||||
"""
|
||||
Need respond in less than 60 seconds in to webhook.
|
||||
|
||||
So... If you respond greater than 55 seconds webhook automatically respond 'ok'
|
||||
and execute callback response via simple HTTP request.
|
||||
|
||||
:param update:
|
||||
:return:
|
||||
"""
|
||||
dispatcher = self.get_dispatcher()
|
||||
loop = dispatcher.loop
|
||||
|
||||
# Analog of `asyncio.wait_for` but without cancelling task
|
||||
waiter = loop.create_future()
|
||||
timeout_handle = loop.call_later(RESPONSE_TIMEOUT, asyncio.tasks._release_waiter, waiter)
|
||||
cb = functools.partial(asyncio.tasks._release_waiter, waiter)
|
||||
|
||||
fut = asyncio.ensure_future(dispatcher.process_update(update), loop=loop)
|
||||
fut.add_done_callback(cb)
|
||||
|
||||
try:
|
||||
try:
|
||||
await waiter
|
||||
except asyncio.futures.CancelledError:
|
||||
fut.remove_done_callback(cb)
|
||||
fut.cancel()
|
||||
raise
|
||||
|
||||
if fut.done():
|
||||
return fut.result()
|
||||
else:
|
||||
fut.remove_done_callback(cb)
|
||||
fut.add_done_callback(self.respond_via_request)
|
||||
finally:
|
||||
timeout_handle.cancel()
|
||||
|
||||
def respond_via_request(self, task):
|
||||
"""
|
||||
Handle response after 55 second.
|
||||
|
||||
:param task:
|
||||
:return:
|
||||
"""
|
||||
warn(f"Detected slow response into webhook. "
|
||||
f"(Greater than {RESPONSE_TIMEOUT} seconds)\n"
|
||||
f"Recommended to use 'async_task' decorator from Dispatcher for handler with long timeouts.",
|
||||
TimeoutWarning)
|
||||
|
||||
dispatcher = self.get_dispatcher()
|
||||
loop = dispatcher.loop
|
||||
|
||||
results = task.result()
|
||||
response = self.get_response(results)
|
||||
if response is not None:
|
||||
asyncio.ensure_future(response.execute_response(self.get_dispatcher().bot), loop=loop)
|
||||
|
||||
def get_response(self, results):
|
||||
"""
|
||||
Get response object from results.
|
||||
|
||||
:param results: list
|
||||
:return:
|
||||
"""
|
||||
if results is None:
|
||||
return None
|
||||
for result in results:
|
||||
if isinstance(result, BaseResponse):
|
||||
return result.get_web_response()
|
||||
return web.Response(text='ok')
|
||||
return result
|
||||
|
||||
|
||||
def configure_app(dispatcher, app: web.Application, path=DEFAULT_WEB_PATH):
|
||||
|
|
@ -156,7 +234,23 @@ class BaseResponse:
|
|||
return await bot.request(self.method, self.cleanup())
|
||||
|
||||
|
||||
class SendMessage(BaseResponse):
|
||||
class ReplyToMixin:
|
||||
"""
|
||||
Mixin for responses where from which can reply to messages.
|
||||
"""
|
||||
|
||||
def reply(self, message: typing.Union[int, types.Message]):
|
||||
"""
|
||||
Reply to message
|
||||
|
||||
:param message: :obj:`int` or :obj:`types.Message`
|
||||
:return: self
|
||||
"""
|
||||
setattr(self, 'reply_to_message_id', message.message_id if isinstance(message, types.Message) else message)
|
||||
return self
|
||||
|
||||
|
||||
class SendMessage(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
You can send message with webhook by using this instance of this object.
|
||||
All arguments is equal with :method:`Bot.send_message` method.
|
||||
|
|
@ -245,7 +339,7 @@ class ForwardMessage(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendPhoto(BaseResponse):
|
||||
class SendPhoto(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send photo on to webhook.
|
||||
"""
|
||||
|
|
@ -294,7 +388,7 @@ class SendPhoto(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendAudio(BaseResponse):
|
||||
class SendAudio(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send audio on to webhook.
|
||||
"""
|
||||
|
|
@ -356,7 +450,7 @@ class SendAudio(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendDocument(BaseResponse):
|
||||
class SendDocument(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send document on to webhook.
|
||||
"""
|
||||
|
|
@ -406,7 +500,7 @@ class SendDocument(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendVideo(BaseResponse):
|
||||
class SendVideo(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send video on to webhook.
|
||||
"""
|
||||
|
|
@ -469,7 +563,7 @@ class SendVideo(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendVoice(BaseResponse):
|
||||
class SendVoice(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send voice on to webhook.
|
||||
"""
|
||||
|
|
@ -523,7 +617,7 @@ class SendVoice(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendVideoNote(BaseResponse):
|
||||
class SendVideoNote(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send video note on to webhook.
|
||||
"""
|
||||
|
|
@ -576,7 +670,7 @@ class SendVideoNote(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendLocation(BaseResponse):
|
||||
class SendLocation(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send location on to webhook.
|
||||
"""
|
||||
|
|
@ -621,7 +715,7 @@ class SendLocation(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendVenue(BaseResponse):
|
||||
class SendVenue(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send venue on to webhook.
|
||||
"""
|
||||
|
|
@ -680,7 +774,7 @@ class SendVenue(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendContact(BaseResponse):
|
||||
class SendContact(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send contact on to webhook.
|
||||
"""
|
||||
|
|
@ -1278,7 +1372,7 @@ class DeleteMessage(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendSticker(BaseResponse):
|
||||
class SendSticker(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send sticker on to webhook.
|
||||
"""
|
||||
|
|
@ -1524,7 +1618,7 @@ class AnswerInlineQuery(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendInvoice(BaseResponse):
|
||||
class SendInvoice(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send invoice on to webhook.
|
||||
"""
|
||||
|
|
@ -1705,7 +1799,7 @@ class AnswerPreCheckoutQuery(BaseResponse):
|
|||
}
|
||||
|
||||
|
||||
class SendGame(BaseResponse):
|
||||
class SendGame(BaseResponse, ReplyToMixin):
|
||||
"""
|
||||
Use that response type for send game on to webhook.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from aiogram.utils.markdown import hlink, link
|
||||
from .base import Deserializable
|
||||
from .chat_photo import ChatPhoto
|
||||
from ..utils.helper import Helper, HelperMode, Item
|
||||
|
|
@ -11,7 +12,9 @@ class Chat(Deserializable):
|
|||
"""
|
||||
|
||||
def __init__(self, id, type, title, username, first_name, last_name, all_members_are_administrators, photo,
|
||||
description, invite_link):
|
||||
description, invite_link, pinned_message):
|
||||
from .message import Message
|
||||
|
||||
self.id: int = id
|
||||
self.type: str = type
|
||||
self.title: str = title
|
||||
|
|
@ -22,9 +25,12 @@ class Chat(Deserializable):
|
|||
self.photo: ChatPhoto = photo
|
||||
self.description: str = description
|
||||
self.invite_link: str = invite_link
|
||||
self.pinned_message: Message = pinned_message
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, raw_data) -> 'Chat':
|
||||
from .message import Message
|
||||
|
||||
id: int = raw_data.get('id')
|
||||
type: str = raw_data.get('type')
|
||||
title: str = raw_data.get('title')
|
||||
|
|
@ -35,9 +41,10 @@ class Chat(Deserializable):
|
|||
photo = raw_data.get('photo')
|
||||
description = raw_data.get('description')
|
||||
invite_link = raw_data.get('invite_link')
|
||||
pinned_message: Message = Message.deserialize(raw_data.get('pinned_message'))
|
||||
|
||||
return Chat(id, type, title, username, first_name, last_name, all_members_are_administrators, photo,
|
||||
description, invite_link)
|
||||
description, invite_link, pinned_message)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
|
|
@ -59,6 +66,20 @@ class Chat(Deserializable):
|
|||
return self.full_name
|
||||
return None
|
||||
|
||||
@property
|
||||
def user_url(self):
|
||||
if self.type != ChatType.PRIVATE:
|
||||
raise TypeError('This property available only in private chats.')
|
||||
|
||||
return f"tg://user?id={self.id}"
|
||||
|
||||
def get_mention(self, name=None, as_html=False):
|
||||
if name is None:
|
||||
name = self.mention
|
||||
if as_html:
|
||||
return hlink(name, self.user_url)
|
||||
return link(name, self.user_url)
|
||||
|
||||
async def set_photo(self, photo):
|
||||
return await self.bot.set_chat_photo(self.id, photo)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ class Message(Deserializable):
|
|||
"""
|
||||
|
||||
def __init__(self, message_id, from_user, date, chat, forward_from, forward_from_chat, forward_from_message_id,
|
||||
forward_date, reply_to_message, edit_date, text, entities, audio, document, game, photo, sticker,
|
||||
video, voice, video_note, new_chat_members, caption, contact, location, venue, left_chat_member,
|
||||
new_chat_title, new_chat_photo, delete_chat_photo, group_chat_created, supergroup_chat_created,
|
||||
channel_chat_created, migrate_to_chat_id, migrate_from_chat_id, pinned_message, invoice,
|
||||
successful_payment, content_type):
|
||||
forward_signature, forward_date, reply_to_message, edit_date, author_signature, text, entities, audio,
|
||||
document, game, photo, sticker, video, voice, video_note, new_chat_members, caption, contact, location,
|
||||
venue, left_chat_member, new_chat_title, new_chat_photo, delete_chat_photo, group_chat_created,
|
||||
supergroup_chat_created, channel_chat_created, migrate_to_chat_id, migrate_from_chat_id,
|
||||
pinned_message, invoice, successful_payment, content_type):
|
||||
self.message_id: int = message_id
|
||||
self.from_user: User = from_user
|
||||
self.date: datetime.datetime = date
|
||||
|
|
@ -41,9 +41,11 @@ class Message(Deserializable):
|
|||
self.forward_from: User = forward_from
|
||||
self.forward_from_chat: Chat = forward_from_chat
|
||||
self.forward_from_message_id: int = forward_from_message_id
|
||||
self.forward_signature: str = forward_signature
|
||||
self.forward_date: datetime.datetime = forward_date
|
||||
self.reply_to_message: Message = reply_to_message
|
||||
self.edit_date: datetime.datetime = edit_date
|
||||
self.author_signature: str = author_signature
|
||||
self.text: str = text
|
||||
self.entities = entities
|
||||
self.audio = audio
|
||||
|
|
@ -83,9 +85,11 @@ class Message(Deserializable):
|
|||
forward_from = User.deserialize(raw_data.get('forward_from', {}))
|
||||
forward_from_chat = Chat.deserialize(raw_data.get('forward_from_chat', {}))
|
||||
forward_from_message_id = raw_data.get('forward_from_message_id')
|
||||
forward_signature = raw_data.get('forward_signature')
|
||||
forward_date = cls._parse_date(raw_data.get('forward_date', 0))
|
||||
reply_to_message = Message.deserialize(raw_data.get('reply_to_message', {}))
|
||||
edit_date = cls._parse_date(raw_data.get('edit_date', 0))
|
||||
author_signature = raw_data.get('author_signature')
|
||||
text = raw_data.get('text')
|
||||
entities = MessageEntity.deserialize(raw_data.get('entities'))
|
||||
audio = Audio.deserialize(raw_data.get('audio'))
|
||||
|
|
@ -142,11 +146,11 @@ class Message(Deserializable):
|
|||
content_type = ContentType.UNKNOWN[0]
|
||||
|
||||
return Message(message_id, from_user, date, chat, forward_from, forward_from_chat, forward_from_message_id,
|
||||
forward_date, reply_to_message, edit_date, text, entities, audio, document, game, photo, sticker,
|
||||
video, voice, video_note, new_chat_members, caption, contact, location, venue, left_chat_member,
|
||||
new_chat_title, new_chat_photo, delete_chat_photo, group_chat_created, supergroup_chat_created,
|
||||
channel_chat_created, migrate_to_chat_id, migrate_from_chat_id, pinned_message, invoice,
|
||||
successful_payment, content_type)
|
||||
forward_signature, forward_date, reply_to_message, edit_date, author_signature, text, entities,
|
||||
audio, document, game, photo, sticker, video, voice, video_note, new_chat_members, caption,
|
||||
contact, location, venue, left_chat_member, new_chat_title, new_chat_photo, delete_chat_photo,
|
||||
group_chat_created, supergroup_chat_created, channel_chat_created, migrate_to_chat_id,
|
||||
migrate_from_chat_id, pinned_message, invoice, successful_payment, content_type)
|
||||
|
||||
def is_command(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from ..utils.markdown import link, hlink
|
||||
|
||||
try:
|
||||
import babel
|
||||
except ImportError:
|
||||
|
|
@ -13,8 +15,9 @@ class User(Deserializable):
|
|||
https://core.telegram.org/bots/api#user
|
||||
"""
|
||||
|
||||
def __init__(self, id, first_name, last_name, username, language_code):
|
||||
def __init__(self, id, is_bot, first_name, last_name, username, language_code):
|
||||
self.id: int = id
|
||||
self.is_bot: bool = is_bot
|
||||
self.first_name: str = first_name
|
||||
self.last_name: str = last_name
|
||||
self.username: str = username
|
||||
|
|
@ -23,12 +26,13 @@ class User(Deserializable):
|
|||
@classmethod
|
||||
def de_json(cls, raw_data: str or dict) -> 'User':
|
||||
id = raw_data.get('id')
|
||||
is_bot = raw_data.get('is_bot')
|
||||
first_name = raw_data.get('first_name')
|
||||
last_name = raw_data.get('last_name')
|
||||
username = raw_data.get('username')
|
||||
language_code = raw_data.get('language_code')
|
||||
|
||||
return User(id, first_name, last_name, username, language_code)
|
||||
return User(id, is_bot, first_name, last_name, username, language_code)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
|
|
@ -69,5 +73,16 @@ class User(Deserializable):
|
|||
setattr(self, '_locale', babel.core.Locale.parse(self.language_code, sep='-'))
|
||||
return getattr(self, '_locale')
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return f"tg://user?id={self.id}"
|
||||
|
||||
def get_mention(self, name=None, as_html=False):
|
||||
if name is None:
|
||||
name = self.mention
|
||||
if as_html:
|
||||
return hlink(name, self.url)
|
||||
return link(name, self.url)
|
||||
|
||||
async def get_user_profile_photos(self, offset=None, limit=None):
|
||||
return await self.bot.get_user_profile_photos(self.id, offset, limit)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
_PREFIXES = ['Error: ', '[Error]: ', 'Bad Request: ']
|
||||
_PREFIXES = ['Error: ', '[Error]: ', 'Bad Request: ', 'Conflict: ']
|
||||
|
||||
|
||||
def _clean_message(text):
|
||||
for prefix in _PREFIXES:
|
||||
if text.startswith(prefix):
|
||||
text = text[len(prefix):]
|
||||
return text
|
||||
return (text[0].upper() + text[1:]).strip()
|
||||
|
||||
|
||||
class TelegramAPIError(Exception):
|
||||
|
|
@ -13,6 +13,14 @@ class TelegramAPIError(Exception):
|
|||
super(TelegramAPIError, self).__init__(_clean_message(message))
|
||||
|
||||
|
||||
class AIOGramWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
class TimeoutWarning(AIOGramWarning):
|
||||
pass
|
||||
|
||||
|
||||
class ValidationError(TelegramAPIError):
|
||||
pass
|
||||
|
||||
|
|
@ -21,6 +29,10 @@ class BadRequest(TelegramAPIError):
|
|||
pass
|
||||
|
||||
|
||||
class ConflictError(TelegramAPIError):
|
||||
pass
|
||||
|
||||
|
||||
class Unauthorized(TelegramAPIError):
|
||||
pass
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue