diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index 29a53606..bb92f8b2 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -1,12 +1,12 @@ -import os import logging +import os from http import HTTPStatus import aiohttp from .. import types -from ..utils import json from ..utils import exceptions +from ..utils import json from ..utils.helper import Helper, HelperMode, Item # Main aiogram logger @@ -67,10 +67,48 @@ async def _check_result(method_name, response): elif 'migrate_to_chat_id' in result_json: raise exceptions.MigrateToChat(result_json['migrate_to_chat_id']) elif response.status == HTTPStatus.BAD_REQUEST: + if exceptions.MessageNotModified.check(description): + exceptions.MessageNotModified.throw() + elif exceptions.MessageToForwardNotFound.check(description): + exceptions.MessageToForwardNotFound.throw() + elif exceptions.MessageIdentifierNotSpecified.check(description): + exceptions.MessageIdentifierNotSpecified.throw() + elif exceptions.ChatNotFound.check(description): + exceptions.ChatNotFound.throw() + elif exceptions.InvalidQueryID.check(description): + exceptions.InvalidQueryID.throw() + elif exceptions.InvalidHTTPUrlContent.check(description): + exceptions.InvalidHTTPUrlContent.throw() + elif exceptions.GroupDeactivated.check(description): + exceptions.GroupDeactivated.throw() + elif exceptions.WrongFileIdentifier.check(description): + exceptions.WrongFileIdentifier.throw() + elif exceptions.InvalidPeerID.check(description): + exceptions.InvalidPeerID.throw() + elif exceptions.WebhookRequireHTTPS.check(description): + exceptions.WebhookRequireHTTPS.throw() + elif exceptions.BadWebhookPort.check(description): + exceptions.BadWebhookPort.throw() + elif exceptions.CantParseUrl.check(description): + exceptions.CantParseUrl.throw() raise exceptions.BadRequest(description) + elif response.status == HTTPStatus.NOT_FOUND: + if exceptions.MethodNotKnown.check(description): + exceptions.MethodNotKnown.throw() + raise exceptions.NotFound(description) elif response.status == HTTPStatus.CONFLICT: + if exceptions.TerminatedByOtherGetUpdates.match(description): + exceptions.TerminatedByOtherGetUpdates.throw() + if exceptions.CantGetUpdates.match(description): + exceptions.CantGetUpdates.throw() raise exceptions.ConflictError(description) elif response.status in [HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN]: + if exceptions.BotKicked.match(description): + exceptions.BotKicked.throw() + elif exceptions.BotBlocked.match(description): + exceptions.BotBlocked.throw() + elif exceptions.UserDeactivated.match(description): + exceptions.UserDeactivated.throw() raise exceptions.Unauthorized(description) elif response.status == HTTPStatus.REQUEST_ENTITY_TOO_LARGE: raise exceptions.NetworkError('File too large for uploading. ' diff --git a/aiogram/dispatcher/storage.py b/aiogram/dispatcher/storage.py index b7e733d7..76a23ee6 100644 --- a/aiogram/dispatcher/storage.py +++ b/aiogram/dispatcher/storage.py @@ -1,5 +1,8 @@ import typing +from ..utils.deprecated import warn_deprecated as warn +from ..utils.exceptions import FSMStorageWarning + # Leak bucket KEY = 'key' LAST_CALL = 'called_at' @@ -324,22 +327,29 @@ class DisabledStorage(BaseStorage): chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, default: typing.Optional[str] = None) -> typing.Dict: + self._warn() return {} async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, data: typing.Dict = None, **kwargs): - pass + self._warn() async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, state: typing.Optional[typing.AnyStr] = None): - pass + self._warn() async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, data: typing.Dict = None): - pass + self._warn() + + @staticmethod + def _warn(): + warn(f"You haven’t set any storage yet so no states and no data will be saved. \n" + f"You can connect MemoryStorage for debug purposes or non-essential data.", + FSMStorageWarning, 5) diff --git a/aiogram/utils/deprecated.py b/aiogram/utils/deprecated.py index baf837c7..1ea2561d 100644 --- a/aiogram/utils/deprecated.py +++ b/aiogram/utils/deprecated.py @@ -69,7 +69,7 @@ def deprecated(reason): raise TypeError(repr(type(reason))) -def warn_deprecated(message, warning=DeprecationWarning): +def warn_deprecated(message, warning=DeprecationWarning, stacklevel=2): warnings.simplefilter('always', warning) - warnings.warn(message, category=warning, stacklevel=2) + warnings.warn(message, category=warning, stacklevel=stacklevel) warnings.simplefilter('default', warning) diff --git a/aiogram/utils/exceptions.py b/aiogram/utils/exceptions.py index d0a40c0d..f7296eae 100644 --- a/aiogram/utils/exceptions.py +++ b/aiogram/utils/exceptions.py @@ -1,6 +1,42 @@ +""" +TelegramAPIError + ValidationError + Throttled + BadRequest + MessageError + MessageNotModified + MessageToForwardNotFound + MessageToDeleteNotFound + MessageIdentifierNotSpecified + ChatNotFound + InvalidQueryID + InvalidPeerID + InvalidHTTPUrlContent + WrongFileIdentifier + GroupDeactivated + BadWebhook + WebhookRequireHTTPS + BadWebhookPort + CantParseUrl + NotFound + MethodNotKnown + ConflictError + TerminatedByOtherGetUpdates + CantGetUpdates + Unauthorized + BotKicked + BotBlocked + UserDeactivated + NetworkError + RetryAfter + MigrateToChat + +AIOGramWarning + TimeoutWarning +""" import time -_PREFIXES = ['Error: ', '[Error]: ', 'Bad Request: ', 'Conflict: '] +_PREFIXES = ['Error: ', '[Error]: ', 'Bad Request: ', 'Conflict: ', 'Not Found: '] def _clean_message(text): @@ -11,10 +47,23 @@ def _clean_message(text): class TelegramAPIError(Exception): - def __init__(self, message): + def __init__(self, message=None): super(TelegramAPIError, self).__init__(_clean_message(message)) +class _MatchErrorMixin: + match = '' + text = None + + @classmethod + def check(cls, message): + return cls.match in message + + @classmethod + def throw(cls): + raise cls(cls.text or cls.match) + + class AIOGramWarning(Warning): pass @@ -23,6 +72,10 @@ class TimeoutWarning(AIOGramWarning): pass +class FSMStorageWarning(AIOGramWarning): + pass + + class ValidationError(TelegramAPIError): pass @@ -31,14 +84,108 @@ class BadRequest(TelegramAPIError): pass +class MessageError(BadRequest): + pass + + +class MessageNotModified(MessageError, _MatchErrorMixin): + match = 'message is not modified' + + +class MessageToForwardNotFound(MessageError, _MatchErrorMixin): + match = 'message to forward not found' + + +class MessageToDeleteNotFound(MessageError, _MatchErrorMixin): + match = 'message to delete not found' + + +class MessageIdentifierNotSpecified(MessageError, _MatchErrorMixin): + match = 'message identifier is not specified' + + +class ChatNotFound(BadRequest, _MatchErrorMixin): + match = 'chat not found' + + +class InvalidQueryID(BadRequest, _MatchErrorMixin): + match = 'QUERY_ID_INVALID' + text = 'Invalid query ID' + + +class InvalidPeerID(BadRequest, _MatchErrorMixin): + match = 'PEER_ID_INVALID' + text = 'Invalid peer ID' + + +class InvalidHTTPUrlContent(BadRequest, _MatchErrorMixin): + match = 'Failed to get HTTP URL content' + + +class WrongFileIdentifier(BadRequest, _MatchErrorMixin): + match = 'wrong file identifier/HTTP URL specified' + + +class GroupDeactivated(BadRequest, _MatchErrorMixin): + match = 'group is deactivated' + + +class BadWebhook(BadRequest): + pass + + +class WebhookRequireHTTPS(BadRequest, _MatchErrorMixin): + match = 'HTTPS url must be provided for webhook' + text = 'bad webhook: ' + match + + +class BadWebhookPort(BadRequest, _MatchErrorMixin): + match = 'Webhook can be set up only on ports 80, 88, 443 or 8443' + text = 'bad webhook: ' + match + + +class CantParseUrl(BadRequest, _MatchErrorMixin): + match = 'can\'t parse URL' + + +class NotFound(TelegramAPIError): + pass + + +class MethodNotKnown(NotFound, _MatchErrorMixin): + match = 'method not found' + + class ConflictError(TelegramAPIError): pass +class TerminatedByOtherGetUpdates(ConflictError, _MatchErrorMixin): + match = 'terminated by other getUpdates request' + text = 'Terminated by other getUpdates request; ' \ + 'Make sure that only one bot instance is running' + + +class CantGetUpdates(ConflictError, _MatchErrorMixin): + match = 'can\'t use getUpdates method while webhook is active' + + class Unauthorized(TelegramAPIError): pass +class BotKicked(Unauthorized, _MatchErrorMixin): + match = 'Bot was kicked from a chat' + + +class BotBlocked(Unauthorized, _MatchErrorMixin): + match = 'bot was blocked by the user' + + +class UserDeactivated(Unauthorized, _MatchErrorMixin): + match = 'user is deactivated' + + class NetworkError(TelegramAPIError): pass @@ -55,7 +202,7 @@ class MigrateToChat(TelegramAPIError): self.migrate_to_chat_id = chat_id -class Throttled(Exception): +class Throttled(TelegramAPIError): def __init__(self, **kwargs): from ..dispatcher.storage import DELTA, EXCEEDED_COUNT, KEY, LAST_CALL, RATE_LIMIT, RESULT self.key = kwargs.pop(KEY, '')