mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Added more aliases, refactor CallbackData factory, added base exceptions classification mechanism
This commit is contained in:
parent
9451a085d1
commit
f022b4441c
18 changed files with 364 additions and 664 deletions
|
|
@ -302,7 +302,7 @@ class Bot(ContextInstanceMixin["Bot"]):
|
||||||
:param method:
|
:param method:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
return await self.session.make_request(self, method, timeout=request_timeout)
|
return await self.session(self, method, timeout=request_timeout)
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ from typing import (
|
||||||
Optional,
|
Optional,
|
||||||
Tuple,
|
Tuple,
|
||||||
Type,
|
Type,
|
||||||
TypeVar,
|
|
||||||
Union,
|
Union,
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
@ -19,12 +18,12 @@ from aiohttp import BasicAuth, ClientSession, FormData, TCPConnector
|
||||||
|
|
||||||
from aiogram.methods import Request, TelegramMethod
|
from aiogram.methods import Request, TelegramMethod
|
||||||
|
|
||||||
|
from ...methods.base import TelegramType
|
||||||
from .base import UNSET, BaseSession
|
from .base import UNSET, BaseSession
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from ..bot import Bot
|
from ..bot import Bot
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
_ProxyBasic = Union[str, Tuple[str, BasicAuth]]
|
_ProxyBasic = Union[str, Tuple[str, BasicAuth]]
|
||||||
_ProxyChain = Iterable[_ProxyBasic]
|
_ProxyChain = Iterable[_ProxyBasic]
|
||||||
_ProxyType = Union[_ProxyChain, _ProxyBasic]
|
_ProxyType = Union[_ProxyChain, _ProxyBasic]
|
||||||
|
|
@ -76,6 +75,8 @@ def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"]
|
||||||
|
|
||||||
class AiohttpSession(BaseSession):
|
class AiohttpSession(BaseSession):
|
||||||
def __init__(self, proxy: Optional[_ProxyType] = None):
|
def __init__(self, proxy: Optional[_ProxyType] = None):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
self._session: Optional[ClientSession] = None
|
self._session: Optional[ClientSession] = None
|
||||||
self._connector_type: Type[TCPConnector] = TCPConnector
|
self._connector_type: Type[TCPConnector] = TCPConnector
|
||||||
self._connector_init: Dict[str, Any] = {}
|
self._connector_init: Dict[str, Any] = {}
|
||||||
|
|
@ -86,7 +87,7 @@ class AiohttpSession(BaseSession):
|
||||||
try:
|
try:
|
||||||
self._setup_proxy_connector(proxy)
|
self._setup_proxy_connector(proxy)
|
||||||
except ImportError as exc: # pragma: no cover
|
except ImportError as exc: # pragma: no cover
|
||||||
raise UserWarning(
|
raise RuntimeError(
|
||||||
"In order to use aiohttp client for proxy requests, install "
|
"In order to use aiohttp client for proxy requests, install "
|
||||||
"https://pypi.org/project/aiohttp-socks/"
|
"https://pypi.org/project/aiohttp-socks/"
|
||||||
) from exc
|
) from exc
|
||||||
|
|
@ -130,8 +131,8 @@ class AiohttpSession(BaseSession):
|
||||||
return form
|
return form
|
||||||
|
|
||||||
async def make_request(
|
async def make_request(
|
||||||
self, bot: Bot, call: TelegramMethod[T], timeout: Optional[int] = None
|
self, bot: Bot, call: TelegramMethod[TelegramType], timeout: Optional[int] = None
|
||||||
) -> T:
|
) -> TelegramType:
|
||||||
session = await self.create_session()
|
session = await self.create_session()
|
||||||
|
|
||||||
request = call.build_request(bot)
|
request = call.build_request(bot)
|
||||||
|
|
@ -141,11 +142,10 @@ class AiohttpSession(BaseSession):
|
||||||
async with session.post(
|
async with session.post(
|
||||||
url, data=form, timeout=self.timeout if timeout is None else timeout
|
url, data=form, timeout=self.timeout if timeout is None else timeout
|
||||||
) as resp:
|
) as resp:
|
||||||
raw_result = await resp.json(loads=self.json_loads)
|
raw_result = await resp.text()
|
||||||
|
|
||||||
response = call.build_response(raw_result)
|
response = self.check_response(method=call, status_code=resp.status, content=raw_result)
|
||||||
self.raise_for_status(response)
|
return cast(TelegramType, response.result)
|
||||||
return cast(T, response.result)
|
|
||||||
|
|
||||||
async def stream_content(
|
async def stream_content(
|
||||||
self, url: str, timeout: int, chunk_size: int
|
self, url: str, timeout: int, chunk_size: int
|
||||||
|
|
|
||||||
|
|
@ -3,32 +3,44 @@ from __future__ import annotations
|
||||||
import abc
|
import abc
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
from functools import partial
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
AsyncGenerator,
|
AsyncGenerator,
|
||||||
|
Awaitable,
|
||||||
Callable,
|
Callable,
|
||||||
ClassVar,
|
ClassVar,
|
||||||
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
Type,
|
Type,
|
||||||
TypeVar,
|
|
||||||
Union,
|
Union,
|
||||||
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
from aiogram.utils.exceptions import TelegramAPIError
|
from aiogram.utils.exceptions.base import TelegramAPIError
|
||||||
from aiogram.utils.helper import Default
|
from aiogram.utils.helper import Default
|
||||||
|
|
||||||
from ...methods import Response, TelegramMethod
|
from ...methods import Response, TelegramMethod
|
||||||
from ...types import UNSET
|
from ...methods.base import TelegramType
|
||||||
|
from ...types import UNSET, TelegramObject
|
||||||
|
from ...utils.exceptions.special import MigrateToChat, RetryAfter
|
||||||
|
from ..errors_middleware import RequestErrorMiddleware
|
||||||
from ..telegram import PRODUCTION, TelegramAPIServer
|
from ..telegram import PRODUCTION, TelegramAPIServer
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from ..bot import Bot
|
from ..bot import Bot
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
_JsonLoads = Callable[..., Any]
|
_JsonLoads = Callable[..., Any]
|
||||||
_JsonDumps = Callable[..., str]
|
_JsonDumps = Callable[..., str]
|
||||||
|
NextRequestMiddlewareType = Callable[
|
||||||
|
["Bot", TelegramMethod[TelegramObject]], Awaitable[Response[TelegramObject]]
|
||||||
|
]
|
||||||
|
RequestMiddlewareType = Callable[
|
||||||
|
["Bot", TelegramMethod[TelegramType], NextRequestMiddlewareType],
|
||||||
|
Awaitable[Response[TelegramType]],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class BaseSession(abc.ABC):
|
class BaseSession(abc.ABC):
|
||||||
|
|
@ -43,16 +55,40 @@ class BaseSession(abc.ABC):
|
||||||
timeout: Default[float] = Default(fget=lambda self: float(self.__class__.default_timeout))
|
timeout: Default[float] = Default(fget=lambda self: float(self.__class__.default_timeout))
|
||||||
"""Session scope request timeout"""
|
"""Session scope request timeout"""
|
||||||
|
|
||||||
@classmethod
|
errors_middleware: ClassVar[RequestErrorMiddleware] = RequestErrorMiddleware()
|
||||||
def raise_for_status(cls, response: Response[T]) -> None:
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.middlewares: List[RequestMiddlewareType[TelegramObject]] = [
|
||||||
|
self.errors_middleware,
|
||||||
|
]
|
||||||
|
|
||||||
|
def check_response(
|
||||||
|
self, method: TelegramMethod[TelegramType], status_code: int, content: str
|
||||||
|
) -> Response[TelegramType]:
|
||||||
"""
|
"""
|
||||||
Check response status
|
Check response status
|
||||||
|
|
||||||
:param response: Response instance
|
|
||||||
"""
|
"""
|
||||||
|
json_data = self.json_loads(content)
|
||||||
|
response = method.build_response(json_data)
|
||||||
if response.ok:
|
if response.ok:
|
||||||
return
|
return response
|
||||||
raise TelegramAPIError(response.description)
|
|
||||||
|
description = cast(str, response.description)
|
||||||
|
if parameters := response.parameters:
|
||||||
|
if parameters.retry_after:
|
||||||
|
raise RetryAfter(
|
||||||
|
method=method, message=description, retry_after=parameters.retry_after
|
||||||
|
)
|
||||||
|
if parameters.migrate_to_chat_id:
|
||||||
|
raise MigrateToChat(
|
||||||
|
method=method,
|
||||||
|
message=description,
|
||||||
|
migrate_to_chat_id=parameters.migrate_to_chat_id,
|
||||||
|
)
|
||||||
|
raise TelegramAPIError(
|
||||||
|
method=method,
|
||||||
|
message=description,
|
||||||
|
)
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
async def close(self) -> None: # pragma: no cover
|
async def close(self) -> None: # pragma: no cover
|
||||||
|
|
@ -63,8 +99,8 @@ class BaseSession(abc.ABC):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
async def make_request(
|
async def make_request(
|
||||||
self, bot: Bot, method: TelegramMethod[T], timeout: Optional[int] = UNSET
|
self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET
|
||||||
) -> T: # pragma: no cover
|
) -> TelegramType: # pragma: no cover
|
||||||
"""
|
"""
|
||||||
Make request to Telegram Bot API
|
Make request to Telegram Bot API
|
||||||
|
|
||||||
|
|
@ -111,6 +147,20 @@ class BaseSession(abc.ABC):
|
||||||
return {k: self.clean_json(v) for k, v in value.items() if v is not None}
|
return {k: self.clean_json(v) for k, v in value.items() if v is not None}
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def middleware(
|
||||||
|
self, middleware: RequestMiddlewareType[TelegramObject]
|
||||||
|
) -> RequestMiddlewareType[TelegramObject]:
|
||||||
|
self.middlewares.append(middleware)
|
||||||
|
return middleware
|
||||||
|
|
||||||
|
async def __call__(
|
||||||
|
self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET
|
||||||
|
) -> TelegramType:
|
||||||
|
middleware = partial(self.make_request, timeout=timeout)
|
||||||
|
for m in reversed(self.middlewares):
|
||||||
|
middleware = partial(m, make_request=middleware) # type: ignore
|
||||||
|
return await middleware(bot, method)
|
||||||
|
|
||||||
async def __aenter__(self) -> BaseSession:
|
async def __aenter__(self) -> BaseSession:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ from typing import Any, AsyncGenerator, Dict, Optional, Union, cast
|
||||||
|
|
||||||
from .. import loggers
|
from .. import loggers
|
||||||
from ..client.bot import Bot
|
from ..client.bot import Bot
|
||||||
from ..methods import TelegramMethod
|
from ..methods import GetUpdates, TelegramMethod
|
||||||
from ..types import TelegramObject, Update, User
|
from ..types import TelegramObject, Update, User
|
||||||
from ..utils.exceptions import TelegramAPIError
|
from ..utils.exceptions.base import TelegramAPIError
|
||||||
from .event.bases import UNHANDLED, SkipHandler
|
from .event.bases import UNHANDLED, SkipHandler
|
||||||
from .event.telegram import TelegramEventObserver
|
from .event.telegram import TelegramEventObserver
|
||||||
from .fsm.context import FSMContext
|
from .fsm.context import FSMContext
|
||||||
|
|
@ -119,16 +119,22 @@ class Dispatcher(Router):
|
||||||
return await self.feed_update(bot=bot, update=parsed_update, **kwargs)
|
return await self.feed_update(bot=bot, update=parsed_update, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _listen_updates(cls, bot: Bot) -> AsyncGenerator[Update, None]:
|
async def _listen_updates(
|
||||||
|
cls, bot: Bot, polling_timeout: int = 30
|
||||||
|
) -> AsyncGenerator[Update, None]:
|
||||||
"""
|
"""
|
||||||
Infinity updates reader
|
Infinity updates reader
|
||||||
"""
|
"""
|
||||||
update_id: Optional[int] = None
|
get_updates = GetUpdates(timeout=polling_timeout)
|
||||||
|
kwargs = {}
|
||||||
|
if bot.session.timeout:
|
||||||
|
kwargs["request_timeout"] = int(bot.session.timeout + polling_timeout)
|
||||||
while True:
|
while True:
|
||||||
# TODO: Skip restarting telegram error
|
# TODO: Skip restarting telegram error
|
||||||
for update in await bot.get_updates(offset=update_id):
|
updates = await bot(get_updates, **kwargs)
|
||||||
|
for update in updates:
|
||||||
yield update
|
yield update
|
||||||
update_id = update.update_id + 1
|
get_updates.offset = update.update_id + 1
|
||||||
|
|
||||||
async def _listen_update(self, update: Update, **kwargs: Any) -> Any:
|
async def _listen_update(self, update: Update, **kwargs: Any) -> Any:
|
||||||
"""
|
"""
|
||||||
|
|
@ -249,7 +255,7 @@ class Dispatcher(Router):
|
||||||
)
|
)
|
||||||
return True # because update was processed but unsuccessful
|
return True # because update was processed but unsuccessful
|
||||||
|
|
||||||
async def _polling(self, bot: Bot, **kwargs: Any) -> None:
|
async def _polling(self, bot: Bot, polling_timeout: int = 30, **kwargs: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Internal polling process
|
Internal polling process
|
||||||
|
|
||||||
|
|
@ -257,7 +263,7 @@ class Dispatcher(Router):
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
async for update in self._listen_updates(bot):
|
async for update in self._listen_updates(bot, polling_timeout=polling_timeout):
|
||||||
await self._process_update(bot=bot, update=update, **kwargs)
|
await self._process_update(bot=bot, update=update, **kwargs)
|
||||||
|
|
||||||
async def _feed_webhook_update(self, bot: Bot, update: Update, **kwargs: Any) -> Any:
|
async def _feed_webhook_update(self, bot: Bot, update: Update, **kwargs: Any) -> Any:
|
||||||
|
|
@ -336,7 +342,7 @@ class Dispatcher(Router):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def start_polling(self, *bots: Bot, **kwargs: Any) -> None:
|
async def start_polling(self, *bots: Bot, polling_timeout: int = 10, **kwargs: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Polling runner
|
Polling runner
|
||||||
|
|
||||||
|
|
@ -356,7 +362,9 @@ class Dispatcher(Router):
|
||||||
loggers.dispatcher.info(
|
loggers.dispatcher.info(
|
||||||
"Run polling for bot @%s id=%d - %r", user.username, bot.id, user.full_name
|
"Run polling for bot @%s id=%d - %r", user.username, bot.id, user.full_name
|
||||||
)
|
)
|
||||||
coro_list.append(self._polling(bot=bot, **kwargs))
|
coro_list.append(
|
||||||
|
self._polling(bot=bot, polling_timeout=polling_timeout, **kwargs)
|
||||||
|
)
|
||||||
await asyncio.gather(*coro_list)
|
await asyncio.gather(*coro_list)
|
||||||
finally:
|
finally:
|
||||||
for bot in bots: # Close sessions
|
for bot in bots: # Close sessions
|
||||||
|
|
@ -364,16 +372,19 @@ class Dispatcher(Router):
|
||||||
loggers.dispatcher.info("Polling stopped")
|
loggers.dispatcher.info("Polling stopped")
|
||||||
await self.emit_shutdown(**workflow_data)
|
await self.emit_shutdown(**workflow_data)
|
||||||
|
|
||||||
def run_polling(self, *bots: Bot, **kwargs: Any) -> None:
|
def run_polling(self, *bots: Bot, polling_timeout: int = 30, **kwargs: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Run many bots with polling
|
Run many bots with polling
|
||||||
|
|
||||||
:param bots:
|
:param bots: Bot instances
|
||||||
:param kwargs:
|
:param polling_timeout: Poling timeout
|
||||||
|
:param kwargs: contextual data
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return asyncio.run(self.start_polling(*bots, **kwargs))
|
return asyncio.run(
|
||||||
|
self.start_polling(*bots, **kwargs, polling_timeout=polling_timeout)
|
||||||
|
)
|
||||||
except (KeyboardInterrupt, SystemExit): # pragma: no cover
|
except (KeyboardInterrupt, SystemExit): # pragma: no cover
|
||||||
# Allow to graceful shutdown
|
# Allow to graceful shutdown
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ class TelegramEventObserver:
|
||||||
return UNHANDLED
|
return UNHANDLED
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self, *args: FilterType, **bound_filters: BaseFilter
|
self, *args: FilterType, **bound_filters: Any
|
||||||
) -> Callable[[CallbackType], CallbackType]:
|
) -> Callable[[CallbackType], CallbackType]:
|
||||||
"""
|
"""
|
||||||
Decorator for registering event handlers
|
Decorator for registering event handlers
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ class FSMContextMiddleware(BaseMiddleware[Update]):
|
||||||
data["fsm_storage"] = self.storage
|
data["fsm_storage"] = self.storage
|
||||||
if context:
|
if context:
|
||||||
data.update({"state": context, "raw_state": await context.get_state()})
|
data.update({"state": context, "raw_state": await context.get_state()})
|
||||||
if self.isolate_events:
|
if self.isolate_events:
|
||||||
async with self.storage.lock():
|
async with self.storage.lock(chat_id=context.chat_id, user_id=context.user_id):
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
|
|
||||||
def resolve_event_context(self, data: Dict[str, Any]) -> Optional[FSMContext]:
|
def resolve_event_context(self, data: Dict[str, Any]) -> Optional[FSMContext]:
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ StateType = Optional[Union[str, State]]
|
||||||
class BaseStorage(ABC):
|
class BaseStorage(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lock(self) -> AsyncGenerator[None, None]: # pragma: no cover
|
async def lock(
|
||||||
|
self, chat_id: int, user_id: int
|
||||||
|
) -> AsyncGenerator[None, None]: # pragma: no cover
|
||||||
yield None
|
yield None
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ from aiogram.dispatcher.fsm.storage.base import BaseStorage, StateType
|
||||||
class MemoryStorageRecord:
|
class MemoryStorageRecord:
|
||||||
data: Dict[str, Any] = field(default_factory=dict)
|
data: Dict[str, Any] = field(default_factory=dict)
|
||||||
state: Optional[str] = None
|
state: Optional[str] = None
|
||||||
|
lock: Lock = field(default_factory=Lock)
|
||||||
|
|
||||||
|
|
||||||
class MemoryStorage(BaseStorage):
|
class MemoryStorage(BaseStorage):
|
||||||
|
|
@ -19,11 +20,10 @@ class MemoryStorage(BaseStorage):
|
||||||
self.storage: DefaultDict[int, DefaultDict[int, MemoryStorageRecord]] = defaultdict(
|
self.storage: DefaultDict[int, DefaultDict[int, MemoryStorageRecord]] = defaultdict(
|
||||||
lambda: defaultdict(MemoryStorageRecord)
|
lambda: defaultdict(MemoryStorageRecord)
|
||||||
)
|
)
|
||||||
self._lock = Lock()
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lock(self) -> AsyncGenerator[None, None]:
|
async def lock(self, chat_id: int, user_id: int) -> AsyncGenerator[None, None]:
|
||||||
async with self._lock:
|
async with self.storage[chat_id][user_id].lock:
|
||||||
yield None
|
yield None
|
||||||
|
|
||||||
async def set_state(self, chat_id: int, user_id: int, state: StateType = None) -> None:
|
async def set_state(self, chat_id: int, user_id: int, state: StateType = None) -> None:
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from ..types import UNSET, InputFile, ResponseParameters
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from ..client.bot import Bot
|
from ..client.bot import Bot
|
||||||
|
|
||||||
T = TypeVar("T")
|
TelegramType = TypeVar("TelegramType", bound=Any)
|
||||||
|
|
||||||
|
|
||||||
class Request(BaseModel):
|
class Request(BaseModel):
|
||||||
|
|
@ -31,14 +31,15 @@ class Request(BaseModel):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Response(ResponseParameters, GenericModel, Generic[T]):
|
class Response(GenericModel, Generic[TelegramType]):
|
||||||
ok: bool
|
ok: bool
|
||||||
result: Optional[T] = None
|
result: Optional[TelegramType] = None
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
error_code: Optional[int] = None
|
error_code: Optional[int] = None
|
||||||
|
parameters: Optional[ResponseParameters] = None
|
||||||
|
|
||||||
|
|
||||||
class TelegramMethod(abc.ABC, BaseModel, Generic[T]):
|
class TelegramMethod(abc.ABC, BaseModel, Generic[TelegramType]):
|
||||||
class Config(BaseConfig):
|
class Config(BaseConfig):
|
||||||
# use_enum_values = True
|
# use_enum_values = True
|
||||||
extra = Extra.allow
|
extra = Extra.allow
|
||||||
|
|
@ -76,14 +77,14 @@ class TelegramMethod(abc.ABC, BaseModel, Generic[T]):
|
||||||
|
|
||||||
return super().dict(exclude=exclude, **kwargs)
|
return super().dict(exclude=exclude, **kwargs)
|
||||||
|
|
||||||
def build_response(self, data: Dict[str, Any]) -> Response[T]:
|
def build_response(self, data: Dict[str, Any]) -> Response[TelegramType]:
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
return Response[self.__returning__](**data) # type: ignore
|
return Response[self.__returning__](**data) # type: ignore
|
||||||
|
|
||||||
async def emit(self, bot: Bot) -> T:
|
async def emit(self, bot: Bot) -> TelegramType:
|
||||||
return await bot(self)
|
return await bot(self)
|
||||||
|
|
||||||
def __await__(self) -> Generator[Any, None, T]:
|
def __await__(self) -> Generator[Any, None, TelegramType]:
|
||||||
from aiogram.client.bot import Bot
|
from aiogram.client.bot import Bot
|
||||||
|
|
||||||
bot = Bot.get_current(no_error=False)
|
bot = Bot.get_current(no_error=False)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ from .base import UNSET, TelegramObject
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from ..methods import (
|
from ..methods import (
|
||||||
CopyMessage,
|
CopyMessage,
|
||||||
|
DeleteMessage,
|
||||||
|
EditMessageCaption,
|
||||||
|
EditMessageText,
|
||||||
SendAnimation,
|
SendAnimation,
|
||||||
SendAudio,
|
SendAudio,
|
||||||
SendContact,
|
SendContact,
|
||||||
|
|
@ -1714,6 +1717,49 @@ class Message(TelegramObject):
|
||||||
reply_markup=reply_markup,
|
reply_markup=reply_markup,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def edit_text(
|
||||||
|
self,
|
||||||
|
text: str,
|
||||||
|
parse_mode: Optional[str] = UNSET,
|
||||||
|
entities: Optional[List[MessageEntity]] = None,
|
||||||
|
disable_web_page_preview: Optional[bool] = None,
|
||||||
|
reply_markup: Optional[InlineKeyboardMarkup] = None,
|
||||||
|
) -> EditMessageText:
|
||||||
|
from ..methods import EditMessageText
|
||||||
|
|
||||||
|
return EditMessageText(
|
||||||
|
chat_id=self.chat.id,
|
||||||
|
message_id=self.message_id,
|
||||||
|
text=text,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
entities=entities,
|
||||||
|
disable_web_page_preview=disable_web_page_preview,
|
||||||
|
reply_markup=reply_markup,
|
||||||
|
)
|
||||||
|
|
||||||
|
def edit_caption(
|
||||||
|
self,
|
||||||
|
caption: str,
|
||||||
|
parse_mode: Optional[str] = UNSET,
|
||||||
|
caption_entities: Optional[List[MessageEntity]] = None,
|
||||||
|
reply_markup: Optional[InlineKeyboardMarkup] = None,
|
||||||
|
) -> EditMessageCaption:
|
||||||
|
from ..methods import EditMessageCaption
|
||||||
|
|
||||||
|
return EditMessageCaption(
|
||||||
|
chat_id=self.chat.id,
|
||||||
|
message_id=self.message_id,
|
||||||
|
caption=caption,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
caption_entities=caption_entities,
|
||||||
|
reply_markup=reply_markup,
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self) -> DeleteMessage:
|
||||||
|
from ..methods import DeleteMessage
|
||||||
|
|
||||||
|
return DeleteMessage(chat_id=self.chat.id, message_id=self.message_id)
|
||||||
|
|
||||||
|
|
||||||
class ContentType(helper.Helper):
|
class ContentType(helper.Helper):
|
||||||
mode = helper.HelperMode.snake_case
|
mode = helper.HelperMode.snake_case
|
||||||
|
|
|
||||||
|
|
@ -1,563 +0,0 @@
|
||||||
"""
|
|
||||||
- TelegramAPIError
|
|
||||||
- ValidationError
|
|
||||||
- Throttled
|
|
||||||
- BadRequest
|
|
||||||
- MessageError
|
|
||||||
- MessageNotModified
|
|
||||||
- MessageToForwardNotFound
|
|
||||||
- MessageToDeleteNotFound
|
|
||||||
- MessageIdentifierNotSpecified
|
|
||||||
- MessageTextIsEmpty
|
|
||||||
- MessageCantBeEdited
|
|
||||||
- MessageCantBeDeleted
|
|
||||||
- MessageToEditNotFound
|
|
||||||
- MessageToReplyNotFound
|
|
||||||
- ToMuchMessages
|
|
||||||
- PollError
|
|
||||||
- PollCantBeStopped
|
|
||||||
- PollHasAlreadyClosed
|
|
||||||
- PollsCantBeSentToPrivateChats
|
|
||||||
- PollSizeError
|
|
||||||
- PollMustHaveMoreOptions
|
|
||||||
- PollCantHaveMoreOptions
|
|
||||||
- PollsOptionsLengthTooLong
|
|
||||||
- PollOptionsMustBeNonEmpty
|
|
||||||
- PollQuestionMustBeNonEmpty
|
|
||||||
- MessageWithPollNotFound (with MessageError)
|
|
||||||
- MessageIsNotAPoll (with MessageError)
|
|
||||||
- ObjectExpectedAsReplyMarkup
|
|
||||||
- InlineKeyboardExpected
|
|
||||||
- ChatNotFound
|
|
||||||
- ChatDescriptionIsNotModified
|
|
||||||
- InvalidQueryID
|
|
||||||
- InvalidPeerID
|
|
||||||
- InvalidHTTPUrlContent
|
|
||||||
- ButtonURLInvalid
|
|
||||||
- URLHostIsEmpty
|
|
||||||
- StartParamInvalid
|
|
||||||
- ButtonDataInvalid
|
|
||||||
- WrongFileIdentifier
|
|
||||||
- GroupDeactivated
|
|
||||||
- BadWebhook
|
|
||||||
- WebhookRequireHTTPS
|
|
||||||
- BadWebhookPort
|
|
||||||
- BadWebhookAddrInfo
|
|
||||||
- BadWebhookNoAddressAssociatedWithHostname
|
|
||||||
- NotFound
|
|
||||||
- MethodNotKnown
|
|
||||||
- PhotoAsInputFileRequired
|
|
||||||
- InvalidStickersSet
|
|
||||||
- NoStickerInRequest
|
|
||||||
- ChatAdminRequired
|
|
||||||
- NeedAdministratorRightsInTheChannel
|
|
||||||
- MethodNotAvailableInPrivateChats
|
|
||||||
- CantDemoteChatCreator
|
|
||||||
- CantRestrictSelf
|
|
||||||
- NotEnoughRightsToRestrict
|
|
||||||
- PhotoDimensions
|
|
||||||
- UnavailableMembers
|
|
||||||
- TypeOfFileMismatch
|
|
||||||
- WrongRemoteFileIdSpecified
|
|
||||||
- PaymentProviderInvalid
|
|
||||||
- CurrencyTotalAmountInvalid
|
|
||||||
- CantParseUrl
|
|
||||||
- UnsupportedUrlProtocol
|
|
||||||
- CantParseEntities
|
|
||||||
- ResultIdDuplicate
|
|
||||||
- ConflictError
|
|
||||||
- TerminatedByOtherGetUpdates
|
|
||||||
- CantGetUpdates
|
|
||||||
- Unauthorized
|
|
||||||
- BotKicked
|
|
||||||
- BotBlocked
|
|
||||||
- UserDeactivated
|
|
||||||
- CantInitiateConversation
|
|
||||||
- CantTalkWithBots
|
|
||||||
- NetworkError
|
|
||||||
- RetryAfter
|
|
||||||
- MigrateToChat
|
|
||||||
- RestartingTelegram
|
|
||||||
|
|
||||||
- AIOGramWarning
|
|
||||||
- TimeoutWarning
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class TelegramAPIError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# _PREFIXES = ["error: ", "[error]: ", "bad request: ", "conflict: ", "not found: "]
|
|
||||||
#
|
|
||||||
|
|
||||||
# def _clean_message(text):
|
|
||||||
# for prefix in _PREFIXES:
|
|
||||||
# if text.startswith(prefix):
|
|
||||||
# text = text[len(prefix) :]
|
|
||||||
# return (text[0].upper() + text[1:]).strip()
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class _MatchErrorMixin:
|
|
||||||
# match = ""
|
|
||||||
# text = None
|
|
||||||
#
|
|
||||||
# __subclasses = []
|
|
||||||
#
|
|
||||||
# def __init_subclass__(cls, **kwargs):
|
|
||||||
# super(_MatchErrorMixin, cls).__init_subclass__(**kwargs)
|
|
||||||
# # cls.match = cls.match.lower() if cls.match else ''
|
|
||||||
# if not hasattr(cls, f"_{cls.__name__}__group"):
|
|
||||||
# cls.__subclasses.append(cls)
|
|
||||||
#
|
|
||||||
# @classmethod
|
|
||||||
# def check(cls, message) -> bool:
|
|
||||||
# """
|
|
||||||
# Compare pattern with message
|
|
||||||
#
|
|
||||||
# :param message: always must be in lowercase
|
|
||||||
# :return: bool
|
|
||||||
# """
|
|
||||||
# return cls.match.lower() in message
|
|
||||||
#
|
|
||||||
# @classmethod
|
|
||||||
# def detect(cls, description):
|
|
||||||
# description = description.lower()
|
|
||||||
# for err in cls.__subclasses:
|
|
||||||
# if err is cls:
|
|
||||||
# continue
|
|
||||||
# if err.check(description):
|
|
||||||
# raise err(cls.text or description)
|
|
||||||
# raise cls(description)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class AIOGramWarning(Warning):
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class TimeoutWarning(AIOGramWarning):
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class FSMStorageWarning(AIOGramWarning):
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ValidationError(TelegramAPIError):
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class BadRequest(TelegramAPIError, _MatchErrorMixin):
|
|
||||||
# __group = True
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageError(BadRequest):
|
|
||||||
# __group = True
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageNotModified(MessageError):
|
|
||||||
# """
|
|
||||||
# Will be raised when you try to set new text is equals to current text.
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# match = "message is not modified"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageToForwardNotFound(MessageError):
|
|
||||||
# """
|
|
||||||
# Will be raised when you try to forward very old or deleted or unknown message.
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# match = "message to forward not found"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageToDeleteNotFound(MessageError):
|
|
||||||
# """
|
|
||||||
# Will be raised when you try to delete very old or deleted or unknown message.
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# match = "message to delete not found"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageToReplyNotFound(MessageError):
|
|
||||||
# """
|
|
||||||
# Will be raised when you try to reply to very old or deleted or unknown message.
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# match = "message to reply not found"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageIdentifierNotSpecified(MessageError):
|
|
||||||
# match = "message identifier is not specified"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageTextIsEmpty(MessageError):
|
|
||||||
# match = "Message text is empty"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageCantBeEdited(MessageError):
|
|
||||||
# match = "message can't be edited"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageCantBeDeleted(MessageError):
|
|
||||||
# match = "message can't be deleted"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageToEditNotFound(MessageError):
|
|
||||||
# match = "message to edit not found"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageIsTooLong(MessageError):
|
|
||||||
# match = "message is too long"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ToMuchMessages(MessageError):
|
|
||||||
# """
|
|
||||||
# Will be raised when you try to send media group with more than 10 items.
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# match = "Too much messages to send as an album"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ObjectExpectedAsReplyMarkup(BadRequest):
|
|
||||||
# match = "object expected as reply markup"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class InlineKeyboardExpected(BadRequest):
|
|
||||||
# match = "inline keyboard expected"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollError(BadRequest):
|
|
||||||
# __group = True
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollCantBeStopped(PollError):
|
|
||||||
# match = "poll can't be stopped"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollHasAlreadyBeenClosed(PollError):
|
|
||||||
# match = "poll has already been closed"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollsCantBeSentToPrivateChats(PollError):
|
|
||||||
# match = "polls can't be sent to private chats"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollSizeError(PollError):
|
|
||||||
# __group = True
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollMustHaveMoreOptions(PollSizeError):
|
|
||||||
# match = "poll must have at least 2 option"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollCantHaveMoreOptions(PollSizeError):
|
|
||||||
# match = "poll can't have more than 10 options"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollOptionsMustBeNonEmpty(PollSizeError):
|
|
||||||
# match = "poll options must be non-empty"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollQuestionMustBeNonEmpty(PollSizeError):
|
|
||||||
# match = "poll question must be non-empty"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollOptionsLengthTooLong(PollSizeError):
|
|
||||||
# match = "poll options length must not exceed 100"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PollQuestionLengthTooLong(PollSizeError):
|
|
||||||
# match = "poll question length must not exceed 255"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageWithPollNotFound(PollError, MessageError):
|
|
||||||
# """
|
|
||||||
# Will be raised when you try to stop poll with message without poll
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# match = "message with poll to stop not found"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MessageIsNotAPoll(PollError, MessageError):
|
|
||||||
# """
|
|
||||||
# Will be raised when you try to stop poll with message without poll
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# match = "message is not a poll"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ChatNotFound(BadRequest):
|
|
||||||
# match = "chat not found"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ChatIdIsEmpty(BadRequest):
|
|
||||||
# match = "chat_id is empty"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class InvalidUserId(BadRequest):
|
|
||||||
# match = "user_id_invalid"
|
|
||||||
# text = "Invalid user id"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ChatDescriptionIsNotModified(BadRequest):
|
|
||||||
# match = "chat description is not modified"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class InvalidQueryID(BadRequest):
|
|
||||||
# match = "query is too old and response timeout expired or query id is invalid"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class InvalidPeerID(BadRequest):
|
|
||||||
# match = "PEER_ID_INVALID"
|
|
||||||
# text = "Invalid peer ID"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class InvalidHTTPUrlContent(BadRequest):
|
|
||||||
# match = "Failed to get HTTP URL content"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ButtonURLInvalid(BadRequest):
|
|
||||||
# match = "BUTTON_URL_INVALID"
|
|
||||||
# text = "Button URL invalid"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class URLHostIsEmpty(BadRequest):
|
|
||||||
# match = "URL host is empty"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class StartParamInvalid(BadRequest):
|
|
||||||
# match = "START_PARAM_INVALID"
|
|
||||||
# text = "Start param invalid"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ButtonDataInvalid(BadRequest):
|
|
||||||
# match = "BUTTON_DATA_INVALID"
|
|
||||||
# text = "Button data invalid"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class WrongFileIdentifier(BadRequest):
|
|
||||||
# match = "wrong file identifier/HTTP URL specified"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class GroupDeactivated(BadRequest):
|
|
||||||
# match = "group is deactivated"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PhotoAsInputFileRequired(BadRequest):
|
|
||||||
# """
|
|
||||||
# Will be raised when you try to set chat photo from file ID.
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# match = "Photo should be uploaded as an InputFile"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class InvalidStickersSet(BadRequest):
|
|
||||||
# match = "STICKERSET_INVALID"
|
|
||||||
# text = "Stickers set is invalid"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class NoStickerInRequest(BadRequest):
|
|
||||||
# match = "there is no sticker in the request"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ChatAdminRequired(BadRequest):
|
|
||||||
# match = "CHAT_ADMIN_REQUIRED"
|
|
||||||
# text = "Admin permissions is required!"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class NeedAdministratorRightsInTheChannel(BadRequest):
|
|
||||||
# match = "need administrator rights in the channel chat"
|
|
||||||
# text = "Admin permissions is required!"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class NotEnoughRightsToPinMessage(BadRequest):
|
|
||||||
# match = "not enough rights to pin a message"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MethodNotAvailableInPrivateChats(BadRequest):
|
|
||||||
# match = "method is available only for supergroups and channel"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class CantDemoteChatCreator(BadRequest):
|
|
||||||
# match = "can't demote chat creator"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class CantRestrictSelf(BadRequest):
|
|
||||||
# match = "can't restrict self"
|
|
||||||
# text = "Admin can't restrict self."
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class NotEnoughRightsToRestrict(BadRequest):
|
|
||||||
# match = "not enough rights to restrict/unrestrict chat member"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PhotoDimensions(BadRequest):
|
|
||||||
# match = "PHOTO_INVALID_DIMENSIONS"
|
|
||||||
# text = "Invalid photo dimensions"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class UnavailableMembers(BadRequest):
|
|
||||||
# match = "supergroup members are unavailable"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class TypeOfFileMismatch(BadRequest):
|
|
||||||
# match = "type of file mismatch"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class WrongRemoteFileIdSpecified(BadRequest):
|
|
||||||
# match = "wrong remote file id specified"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class PaymentProviderInvalid(BadRequest):
|
|
||||||
# match = "PAYMENT_PROVIDER_INVALID"
|
|
||||||
# text = "payment provider invalid"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class CurrencyTotalAmountInvalid(BadRequest):
|
|
||||||
# match = "currency_total_amount_invalid"
|
|
||||||
# text = "currency total amount invalid"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class BadWebhook(BadRequest):
|
|
||||||
# __group = True
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class WebhookRequireHTTPS(BadWebhook):
|
|
||||||
# match = "HTTPS url must be provided for webhook"
|
|
||||||
# text = "bad webhook: " + match
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class BadWebhookPort(BadWebhook):
|
|
||||||
# match = "Webhook can be set up only on ports 80, 88, 443 or 8443"
|
|
||||||
# text = "bad webhook: " + match
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class BadWebhookAddrInfo(BadWebhook):
|
|
||||||
# match = "getaddrinfo: Temporary failure in name resolution"
|
|
||||||
# text = "bad webhook: " + match
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class BadWebhookNoAddressAssociatedWithHostname(BadWebhook):
|
|
||||||
# match = "failed to resolve host: no address associated with hostname"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class CantParseUrl(BadRequest):
|
|
||||||
# match = "can't parse URL"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class UnsupportedUrlProtocol(BadRequest):
|
|
||||||
# match = "unsupported URL protocol"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class CantParseEntities(BadRequest):
|
|
||||||
# match = "can't parse entities"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ResultIdDuplicate(BadRequest):
|
|
||||||
# match = "result_id_duplicate"
|
|
||||||
# text = "Result ID duplicate"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class BotDomainInvalid(BadRequest):
|
|
||||||
# match = "bot_domain_invalid"
|
|
||||||
# text = "Invalid bot domain"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class NotFound(TelegramAPIError, _MatchErrorMixin):
|
|
||||||
# __group = True
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MethodNotKnown(NotFound):
|
|
||||||
# match = "method not found"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class ConflictError(TelegramAPIError, _MatchErrorMixin):
|
|
||||||
# __group = True
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class TerminatedByOtherGetUpdates(ConflictError):
|
|
||||||
# match = "terminated by other getUpdates request"
|
|
||||||
# text = (
|
|
||||||
# "Terminated by other getUpdates request; "
|
|
||||||
# "Make sure that only one bot instance is running"
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class CantGetUpdates(ConflictError):
|
|
||||||
# match = "can't use getUpdates method while webhook is active"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class Unauthorized(TelegramAPIError, _MatchErrorMixin):
|
|
||||||
# __group = True
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class BotKicked(Unauthorized):
|
|
||||||
# match = "bot was kicked from a chat"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class BotBlocked(Unauthorized):
|
|
||||||
# match = "bot was blocked by the user"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class UserDeactivated(Unauthorized):
|
|
||||||
# match = "user is deactivated"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class CantInitiateConversation(Unauthorized):
|
|
||||||
# match = "bot can't initiate conversation with a user"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class CantTalkWithBots(Unauthorized):
|
|
||||||
# match = "bot can't send messages to bots"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class NetworkError(TelegramAPIError):
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class RestartingTelegram(TelegramAPIError):
|
|
||||||
# def __init__(self):
|
|
||||||
# super(RestartingTelegram, self).__init__(
|
|
||||||
# "The Telegram Bot API service is restarting. Wait few second."
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class RetryAfter(TelegramAPIError):
|
|
||||||
# def __init__(self, retry_after):
|
|
||||||
# super(RetryAfter, self).__init__(
|
|
||||||
# f"Flood control exceeded. Retry in {retry_after} seconds."
|
|
||||||
# )
|
|
||||||
# self.timeout = retry_after
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class MigrateToChat(TelegramAPIError):
|
|
||||||
# def __init__(self, chat_id):
|
|
||||||
# super(MigrateToChat, self).__init__(
|
|
||||||
# f"The group has been migrated to a supergroup. New id: {chat_id}."
|
|
||||||
# )
|
|
||||||
# self.migrate_to_chat_id = chat_id
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# 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, "<None>")
|
|
||||||
# self.called_at = kwargs.pop(LAST_CALL, time.time())
|
|
||||||
# self.rate = kwargs.pop(RATE_LIMIT, None)
|
|
||||||
# self.result = kwargs.pop(RESULT, False)
|
|
||||||
# self.exceeded_count = kwargs.pop(EXCEEDED_COUNT, 0)
|
|
||||||
# self.delta = kwargs.pop(DELTA, 0)
|
|
||||||
# self.user = kwargs.pop("user", None)
|
|
||||||
# self.chat = kwargs.pop("chat", None)
|
|
||||||
#
|
|
||||||
# def __str__(self):
|
|
||||||
# return (
|
|
||||||
# f"Rate limit exceeded! (Limit: {self.rate} s, "
|
|
||||||
# f"exceeded: {self.exceeded_count}, "
|
|
||||||
# f"time delta: {round(self.delta, 3)} s)"
|
|
||||||
# )
|
|
||||||
93
aiogram/utils/exceptions/exceptions.py
Normal file
93
aiogram/utils/exceptions/exceptions.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
from textwrap import indent
|
||||||
|
from typing import Match
|
||||||
|
|
||||||
|
from aiogram.methods.base import TelegramMethod, TelegramType
|
||||||
|
from aiogram.utils.exceptions.base import DetailedTelegramAPIError
|
||||||
|
from aiogram.utils.exceptions.util import mark_line
|
||||||
|
|
||||||
|
|
||||||
|
class BadRequest(DetailedTelegramAPIError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CantParseEntities(BadRequest):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CantParseEntitiesStartTag(CantParseEntities):
|
||||||
|
patterns = [
|
||||||
|
"Bad Request: can't parse entities: Can't find end tag corresponding to start tag (?P<tag>.+)"
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
method: TelegramMethod[TelegramType],
|
||||||
|
message: str,
|
||||||
|
match: Match[str],
|
||||||
|
) -> None:
|
||||||
|
super().__init__(method=method, message=message, match=match)
|
||||||
|
self.tag: str = match.group("tag")
|
||||||
|
|
||||||
|
|
||||||
|
class CantParseEntitiesUnmatchedTags(CantParseEntities):
|
||||||
|
patterns = [
|
||||||
|
r'Bad Request: can\'t parse entities: Unmatched end tag at byte offset (?P<offset>\d), expected "</(?P<expected>\w+)>", found "</(?P<found>\w+)>"'
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
method: TelegramMethod[TelegramType],
|
||||||
|
message: str,
|
||||||
|
match: Match[str],
|
||||||
|
) -> None:
|
||||||
|
super().__init__(method=method, message=message, match=match)
|
||||||
|
self.offset: int = int(match.group("offset"))
|
||||||
|
self.expected: str = match.group("expected")
|
||||||
|
self.found: str = match.group("found")
|
||||||
|
|
||||||
|
|
||||||
|
class CantParseEntitiesUnclosed(CantParseEntities):
|
||||||
|
patterns = [
|
||||||
|
"Bad Request: can't parse entities: Unclosed start tag at byte offset (?P<offset>.+)"
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
method: TelegramMethod[TelegramType],
|
||||||
|
message: str,
|
||||||
|
match: Match[str],
|
||||||
|
) -> None:
|
||||||
|
super().__init__(method=method, message=message, match=match)
|
||||||
|
self.offset: int = int(match.group("offset"))
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
message = [self.message]
|
||||||
|
text = getattr(self.method, "text", None) or getattr(self.method, "caption", None)
|
||||||
|
if text:
|
||||||
|
message.extend(["Example:", indent(mark_line(text, self.offset), prefix=" ")])
|
||||||
|
return "\n".join(message)
|
||||||
|
|
||||||
|
|
||||||
|
class CantParseEntitiesUnsupportedTag(CantParseEntities):
|
||||||
|
patterns = [
|
||||||
|
r'Bad Request: can\'t parse entities: Unsupported start tag "(?P<tag>.+)" at byte offset (?P<offset>\d+)'
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
method: TelegramMethod[TelegramType],
|
||||||
|
message: str,
|
||||||
|
match: Match[str],
|
||||||
|
) -> None:
|
||||||
|
super().__init__(method=method, message=message, match=match)
|
||||||
|
self.offset = int(match.group("offset"))
|
||||||
|
self.tag = match.group("tag")
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
message = [self.message]
|
||||||
|
text = getattr(self.method, "text", None) or getattr(self.method, "caption", None)
|
||||||
|
if text:
|
||||||
|
message.extend(
|
||||||
|
["Example:", indent(mark_line(text, self.offset, len(self.tag)), prefix=" ")]
|
||||||
|
)
|
||||||
|
return "\n".join(message)
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from itertools import cycle as repeat_all
|
from itertools import cycle as repeat_all
|
||||||
from typing import Any, Generator, Generic, Iterable, List, Optional, Type, TypeVar
|
from typing import Any, Generator, Generic, Iterable, List, Optional, Type, TypeVar, Union
|
||||||
|
|
||||||
from aiogram.types import InlineKeyboardButton, KeyboardButton
|
from aiogram.dispatcher.filters.callback_data import CallbackData
|
||||||
|
from aiogram.types import (
|
||||||
|
InlineKeyboardButton,
|
||||||
|
InlineKeyboardMarkup,
|
||||||
|
KeyboardButton,
|
||||||
|
ReplyKeyboardMarkup,
|
||||||
|
)
|
||||||
|
|
||||||
ButtonType = TypeVar("ButtonType", InlineKeyboardButton, KeyboardButton)
|
ButtonType = TypeVar("ButtonType", InlineKeyboardButton, KeyboardButton)
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
@ -11,7 +19,7 @@ MIN_WIDTH = 1
|
||||||
MAX_BUTTONS = 100
|
MAX_BUTTONS = 100
|
||||||
|
|
||||||
|
|
||||||
class MarkupConstructor(Generic[ButtonType]):
|
class KeyboardConstructor(Generic[ButtonType]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, button_type: Type[ButtonType], markup: Optional[List[List[ButtonType]]] = None
|
self, button_type: Type[ButtonType], markup: Optional[List[List[ButtonType]]] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
@ -106,7 +114,7 @@ class MarkupConstructor(Generic[ButtonType]):
|
||||||
raise ValueError(f"Row size {size} are not allowed")
|
raise ValueError(f"Row size {size} are not allowed")
|
||||||
return size
|
return size
|
||||||
|
|
||||||
def copy(self: "MarkupConstructor[ButtonType]") -> "MarkupConstructor[ButtonType]":
|
def copy(self: "KeyboardConstructor[ButtonType]") -> "KeyboardConstructor[ButtonType]":
|
||||||
"""
|
"""
|
||||||
Make full copy of current constructor with markup
|
Make full copy of current constructor with markup
|
||||||
|
|
||||||
|
|
@ -120,7 +128,7 @@ class MarkupConstructor(Generic[ButtonType]):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
>>> constructor = MarkupConstructor(button_type=InlineKeyboardButton)
|
>>> constructor = KeyboardConstructor(button_type=InlineKeyboardButton)
|
||||||
>>> ... # Add buttons to constructor
|
>>> ... # Add buttons to constructor
|
||||||
>>> markup = InlineKeyboardMarkup(inline_keyboard=constructor.export())
|
>>> markup = InlineKeyboardMarkup(inline_keyboard=constructor.export())
|
||||||
|
|
||||||
|
|
@ -128,7 +136,7 @@ class MarkupConstructor(Generic[ButtonType]):
|
||||||
"""
|
"""
|
||||||
return self._markup.copy()
|
return self._markup.copy()
|
||||||
|
|
||||||
def add(self, *buttons: ButtonType) -> "MarkupConstructor[ButtonType]":
|
def add(self, *buttons: ButtonType) -> "KeyboardConstructor[ButtonType]":
|
||||||
"""
|
"""
|
||||||
Add one or many buttons to markup.
|
Add one or many buttons to markup.
|
||||||
|
|
||||||
|
|
@ -153,7 +161,9 @@ class MarkupConstructor(Generic[ButtonType]):
|
||||||
self._markup = markup
|
self._markup = markup
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def row(self, *buttons: ButtonType, width: int = MAX_WIDTH) -> "MarkupConstructor[ButtonType]":
|
def row(
|
||||||
|
self, *buttons: ButtonType, width: int = MAX_WIDTH
|
||||||
|
) -> "KeyboardConstructor[ButtonType]":
|
||||||
"""
|
"""
|
||||||
Add row to markup
|
Add row to markup
|
||||||
|
|
||||||
|
|
@ -170,7 +180,7 @@ class MarkupConstructor(Generic[ButtonType]):
|
||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def adjust(self, *sizes: int, repeat: bool = False) -> "MarkupConstructor[ButtonType]":
|
def adjust(self, *sizes: int, repeat: bool = False) -> "KeyboardConstructor[ButtonType]":
|
||||||
"""
|
"""
|
||||||
Adjust previously added buttons to specific row sizes.
|
Adjust previously added buttons to specific row sizes.
|
||||||
|
|
||||||
|
|
@ -202,10 +212,17 @@ class MarkupConstructor(Generic[ButtonType]):
|
||||||
self._markup = markup
|
self._markup = markup
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def button(self, **kwargs: Any) -> "MarkupConstructor[ButtonType]":
|
def button(self, **kwargs: Any) -> "KeyboardConstructor[ButtonType]":
|
||||||
|
if isinstance(callback_data := kwargs.get("callback_data", None), CallbackData):
|
||||||
|
kwargs["callback_data"] = callback_data.pack()
|
||||||
button = self._button_type(**kwargs)
|
button = self._button_type(**kwargs)
|
||||||
return self.add(button)
|
return self.add(button)
|
||||||
|
|
||||||
|
def as_markup(self, **kwargs: Any) -> Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]:
|
||||||
|
if self._button_type is ReplyKeyboardMarkup:
|
||||||
|
return ReplyKeyboardMarkup(keyboard=self.export(), **kwargs)
|
||||||
|
return InlineKeyboardMarkup(inline_keyboard=self.export())
|
||||||
|
|
||||||
|
|
||||||
def repeat_last(items: Iterable[T]) -> Generator[T, None, None]:
|
def repeat_last(items: Iterable[T]) -> Generator[T, None, None]:
|
||||||
items_iter = iter(items)
|
items_iter = iter(items)
|
||||||
|
|
@ -1,39 +1,46 @@
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
from aiogram.dispatcher.handler import MessageHandler
|
from aiogram.types import Message
|
||||||
|
|
||||||
TOKEN = "42:TOKEN"
|
TOKEN = "42:TOKEN"
|
||||||
dp = Dispatcher()
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@dp.message(commands=["start"])
|
|
||||||
class MyHandler(MessageHandler):
|
@dp.message(commands={"start"})
|
||||||
|
async def command_start_handler(message: Message) -> None:
|
||||||
"""
|
"""
|
||||||
This handler receive messages with `/start` command
|
This handler receive messages with `/start` command
|
||||||
|
|
||||||
Usage of Class-based handlers
|
|
||||||
"""
|
"""
|
||||||
|
# Most of event objects has an aliases for API methods to be called in event context
|
||||||
async def handle(self) -> Any:
|
# For example if you want to answer to incoming message you can use `message.answer(...)` alias
|
||||||
await self.event.answer(f"<b>Hello, {self.from_user.full_name}!</b>")
|
# and the target chat will be passed to :ref:`aiogram.methods.send_message.SendMessage` method automatically
|
||||||
|
# or call API method directly via Bot instance: `bot.send_message(chat_id=message.chat.id, ...)`
|
||||||
|
await message.answer(f"Hello, <b>{message.from_user.full_name}!</b>")
|
||||||
|
|
||||||
|
|
||||||
@dp.message(content_types=[types.ContentType.ANY])
|
@dp.message()
|
||||||
async def echo_handler(message: types.Message, bot: Bot) -> Any:
|
async def echo_handler(message: types.Message) -> Any:
|
||||||
"""
|
"""
|
||||||
Handler will forward received message back to the sender
|
Handler will forward received message back to the sender
|
||||||
|
|
||||||
Usage of Function-based handlers
|
By default message handler will handle all message types (like text, photo, sticker and etc.)
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
await bot.forward_message(
|
# Send copy of the received message
|
||||||
from_chat_id=message.chat.id, chat_id=message.chat.id, message_id=message.message_id
|
await message.send_copy(chat_id=message.chat.id)
|
||||||
)
|
except TypeError:
|
||||||
|
# But not all the types is supported to be copied so need to handle it
|
||||||
|
await message.answer("Nice try!")
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
# Initialize Bot instance with an default parse mode which will be passed to all API calls
|
||||||
bot = Bot(TOKEN, parse_mode="HTML")
|
bot = Bot(TOKEN, parse_mode="HTML")
|
||||||
|
# And the run events dispatching
|
||||||
dp.run_polling(bot)
|
dp.run_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@ from typing import TYPE_CHECKING, AsyncGenerator, Deque, Optional, Type
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
from aiogram.client.session.base import BaseSession
|
from aiogram.client.session.base import BaseSession
|
||||||
from aiogram.methods import TelegramMethod
|
from aiogram.methods import TelegramMethod
|
||||||
from aiogram.methods.base import Request, Response, T
|
from aiogram.methods.base import Request, Response, TelegramType
|
||||||
from aiogram.types import UNSET
|
from aiogram.types import UNSET, ResponseParameters
|
||||||
|
|
||||||
|
|
||||||
class MockedSession(BaseSession):
|
class MockedSession(BaseSession):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(MockedSession, self).__init__()
|
super(MockedSession, self).__init__()
|
||||||
self.responses: Deque[Response[T]] = deque()
|
self.responses: Deque[Response[TelegramType]] = deque()
|
||||||
self.requests: Deque[Request] = deque()
|
self.requests: Deque[Request] = deque()
|
||||||
|
|
||||||
def add_result(self, response: Response[T]) -> Response[T]:
|
def add_result(self, response: Response[TelegramType]) -> Response[TelegramType]:
|
||||||
self.responses.append(response)
|
self.responses.append(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
@ -25,11 +25,13 @@ class MockedSession(BaseSession):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def make_request(
|
async def make_request(
|
||||||
self, bot: Bot, method: TelegramMethod[T], timeout: Optional[int] = UNSET
|
self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET
|
||||||
) -> T:
|
) -> TelegramType:
|
||||||
self.requests.append(method.build_request(bot))
|
self.requests.append(method.build_request(bot))
|
||||||
response: Response[T] = self.responses.pop()
|
response: Response[TelegramType] = self.responses.pop()
|
||||||
self.raise_for_status(response)
|
self.check_response(
|
||||||
|
method=method, status_code=response.error_code, content=response.json()
|
||||||
|
)
|
||||||
return response.result # type: ignore
|
return response.result # type: ignore
|
||||||
|
|
||||||
async def stream_content(
|
async def stream_content(
|
||||||
|
|
@ -47,21 +49,23 @@ class MockedBot(Bot):
|
||||||
|
|
||||||
def add_result_for(
|
def add_result_for(
|
||||||
self,
|
self,
|
||||||
method: Type[TelegramMethod[T]],
|
method: Type[TelegramMethod[TelegramType]],
|
||||||
ok: bool,
|
ok: bool,
|
||||||
result: T = None,
|
result: TelegramType = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
error_code: Optional[int] = None,
|
error_code: Optional[int] = None,
|
||||||
migrate_to_chat_id: Optional[int] = None,
|
migrate_to_chat_id: Optional[int] = None,
|
||||||
retry_after: Optional[int] = None,
|
retry_after: Optional[int] = None,
|
||||||
) -> Response[T]:
|
) -> Response[TelegramType]:
|
||||||
response = Response[method.__returning__]( # type: ignore
|
response = Response[method.__returning__]( # type: ignore
|
||||||
ok=ok,
|
ok=ok,
|
||||||
result=result,
|
result=result,
|
||||||
description=description,
|
description=description,
|
||||||
error_code=error_code,
|
error_code=error_code,
|
||||||
migrate_to_chat_id=migrate_to_chat_id,
|
parameters=ResponseParameters(
|
||||||
retry_after=retry_after,
|
migrate_to_chat_id=migrate_to_chat_id,
|
||||||
|
retry_after=retry_after,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
self.session.add_result(response)
|
self.session.add_result(response)
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
|
|
@ -172,14 +172,10 @@ class TestAiohttpSession:
|
||||||
return Request(method="method", data={})
|
return Request(method="method", data={})
|
||||||
|
|
||||||
call = TestMethod()
|
call = TestMethod()
|
||||||
with patch(
|
|
||||||
"aiogram.client.session.base.BaseSession.raise_for_status"
|
|
||||||
) as patched_raise_for_status:
|
|
||||||
result = await session.make_request(bot, call)
|
|
||||||
assert isinstance(result, int)
|
|
||||||
assert result == 42
|
|
||||||
|
|
||||||
assert patched_raise_for_status.called_once()
|
result = await session.make_request(bot, call)
|
||||||
|
assert isinstance(result, int)
|
||||||
|
assert result == 42
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_stream_content(self, aresponses: ResponsesMockServer):
|
async def test_stream_content(self, aresponses: ResponsesMockServer):
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ from typing import AsyncContextManager, AsyncGenerator, Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aiogram.client.session.base import BaseSession, T
|
from aiogram.client.session.base import BaseSession, TelegramType
|
||||||
from aiogram.client.telegram import PRODUCTION, TelegramAPIServer
|
from aiogram.client.telegram import PRODUCTION, TelegramAPIServer
|
||||||
from aiogram.methods import GetMe, Response, TelegramMethod
|
from aiogram.methods import DeleteMessage, GetMe, Response, TelegramMethod
|
||||||
from aiogram.types import UNSET
|
from aiogram.types import UNSET
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -20,7 +20,7 @@ class CustomSession(BaseSession):
|
||||||
async def close(self):
|
async def close(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def make_request(self, token: str, method: TelegramMethod[T], timeout: Optional[int] = UNSET) -> None: # type: ignore
|
async def make_request(self, token: str, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET) -> None: # type: ignore
|
||||||
assert isinstance(token, str)
|
assert isinstance(token, str)
|
||||||
assert isinstance(method, TelegramMethod)
|
assert isinstance(method, TelegramMethod)
|
||||||
|
|
||||||
|
|
@ -135,12 +135,20 @@ class TestBaseSession:
|
||||||
|
|
||||||
assert session.clean_json(42) == 42
|
assert session.clean_json(42) == 42
|
||||||
|
|
||||||
def test_raise_for_status(self):
|
def check_response(self):
|
||||||
session = CustomSession()
|
session = CustomSession()
|
||||||
|
|
||||||
session.raise_for_status(Response[bool](ok=True, result=True))
|
session.check_response(
|
||||||
|
method=DeleteMessage(chat_id=42, message_id=42),
|
||||||
|
status_code=200,
|
||||||
|
content='{"ok":true,"result":true}',
|
||||||
|
)
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
session.raise_for_status(Response[bool](ok=False, description="Error", error_code=400))
|
session.check_response(
|
||||||
|
method=DeleteMessage(chat_id=42, message_id=42),
|
||||||
|
status_code=400,
|
||||||
|
content='{"ok":false,"description":"test"}',
|
||||||
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_make_request(self):
|
async def test_make_request(self):
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ import pytest
|
||||||
|
|
||||||
from aiogram.methods import (
|
from aiogram.methods import (
|
||||||
CopyMessage,
|
CopyMessage,
|
||||||
|
DeleteMessage,
|
||||||
|
EditMessageCaption,
|
||||||
|
EditMessageText,
|
||||||
SendAnimation,
|
SendAnimation,
|
||||||
SendAudio,
|
SendAudio,
|
||||||
SendContact,
|
SendContact,
|
||||||
|
|
@ -549,3 +552,28 @@ class TestMessage:
|
||||||
if method:
|
if method:
|
||||||
assert isinstance(method, expected_method)
|
assert isinstance(method, expected_method)
|
||||||
# TODO: Check additional fields
|
# TODO: Check additional fields
|
||||||
|
|
||||||
|
def test_edit_text(self):
|
||||||
|
message = Message(
|
||||||
|
message_id=42, chat=Chat(id=42, type="private"), date=datetime.datetime.now()
|
||||||
|
)
|
||||||
|
method = message.edit_text(text="test")
|
||||||
|
assert isinstance(method, EditMessageText)
|
||||||
|
assert method.chat_id == message.chat.id
|
||||||
|
|
||||||
|
def test_edit_caption(self):
|
||||||
|
message = Message(
|
||||||
|
message_id=42, chat=Chat(id=42, type="private"), date=datetime.datetime.now()
|
||||||
|
)
|
||||||
|
method = message.edit_caption(caption="test")
|
||||||
|
assert isinstance(method, EditMessageCaption)
|
||||||
|
assert method.chat_id == message.chat.id
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
message = Message(
|
||||||
|
message_id=42, chat=Chat(id=42, type="private"), date=datetime.datetime.now()
|
||||||
|
)
|
||||||
|
method = message.delete()
|
||||||
|
assert isinstance(method, DeleteMessage)
|
||||||
|
assert method.chat_id == message.chat.id
|
||||||
|
assert method.message_id == message.message_id
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue