Третий коммит, добавление share, share_kb, а также ADMIN_ID
This commit is contained in:
41
myenv/Lib/site-packages/aiogram/__init__.py
Normal file
41
myenv/Lib/site-packages/aiogram/__init__.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import asyncio as _asyncio
|
||||
from contextlib import suppress
|
||||
|
||||
from aiogram.dispatcher.flags import FlagGenerator
|
||||
|
||||
from . import enums, methods, types
|
||||
from .__meta__ import __api_version__, __version__
|
||||
from .client import session
|
||||
from .client.bot import Bot
|
||||
from .dispatcher.dispatcher import Dispatcher
|
||||
from .dispatcher.middlewares.base import BaseMiddleware
|
||||
from .dispatcher.router import Router
|
||||
from .utils.magic_filter import MagicFilter
|
||||
from .utils.text_decorations import html_decoration as html
|
||||
from .utils.text_decorations import markdown_decoration as md
|
||||
|
||||
with suppress(ImportError):
|
||||
import uvloop as _uvloop
|
||||
|
||||
_asyncio.set_event_loop_policy(_uvloop.EventLoopPolicy())
|
||||
|
||||
|
||||
F = MagicFilter()
|
||||
flags = FlagGenerator()
|
||||
|
||||
__all__ = (
|
||||
"__api_version__",
|
||||
"__version__",
|
||||
"types",
|
||||
"methods",
|
||||
"enums",
|
||||
"Bot",
|
||||
"session",
|
||||
"Dispatcher",
|
||||
"Router",
|
||||
"BaseMiddleware",
|
||||
"F",
|
||||
"html",
|
||||
"md",
|
||||
"flags",
|
||||
)
|
2
myenv/Lib/site-packages/aiogram/__meta__.py
Normal file
2
myenv/Lib/site-packages/aiogram/__meta__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
__version__ = "3.20.0.post0"
|
||||
__api_version__ = "9.0"
|
0
myenv/Lib/site-packages/aiogram/client/__init__.py
Normal file
0
myenv/Lib/site-packages/aiogram/client/__init__.py
Normal file
5418
myenv/Lib/site-packages/aiogram/client/bot.py
Normal file
5418
myenv/Lib/site-packages/aiogram/client/bot.py
Normal file
File diff suppressed because it is too large
Load Diff
33
myenv/Lib/site-packages/aiogram/client/context_controller.py
Normal file
33
myenv/Lib/site-packages/aiogram/client/context_controller.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import BaseModel, PrivateAttr
|
||||
from typing_extensions import Self
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.client.bot import Bot
|
||||
|
||||
|
||||
class BotContextController(BaseModel):
|
||||
_bot: Optional["Bot"] = PrivateAttr()
|
||||
|
||||
def model_post_init(self, __context: Any) -> None:
|
||||
self._bot = __context.get("bot") if __context else None
|
||||
|
||||
def as_(self, bot: Optional["Bot"]) -> Self:
|
||||
"""
|
||||
Bind object to a bot instance.
|
||||
|
||||
:param bot: Bot instance
|
||||
:return: self
|
||||
"""
|
||||
self._bot = bot
|
||||
return self
|
||||
|
||||
@property
|
||||
def bot(self) -> Optional["Bot"]:
|
||||
"""
|
||||
Get bot instance.
|
||||
|
||||
:return: Bot instance
|
||||
"""
|
||||
return self._bot
|
80
myenv/Lib/site-packages/aiogram/client/default.py
Normal file
80
myenv/Lib/site-packages/aiogram/client/default.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from aiogram.utils.dataclass import dataclass_kwargs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.types import LinkPreviewOptions
|
||||
|
||||
|
||||
# @dataclass ??
|
||||
class Default:
|
||||
# Is not a dataclass because of JSON serialization.
|
||||
|
||||
__slots__ = ("_name",)
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Default({self._name!r})"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self}>"
|
||||
|
||||
|
||||
@dataclass(**dataclass_kwargs(slots=True, kw_only=True))
|
||||
class DefaultBotProperties:
|
||||
"""
|
||||
Default bot properties.
|
||||
"""
|
||||
|
||||
parse_mode: Optional[str] = None
|
||||
"""Default parse mode for messages."""
|
||||
disable_notification: Optional[bool] = None
|
||||
"""Sends the message silently. Users will receive a notification with no sound."""
|
||||
protect_content: Optional[bool] = None
|
||||
"""Protects content from copying."""
|
||||
allow_sending_without_reply: Optional[bool] = None
|
||||
"""Allows to send messages without reply."""
|
||||
link_preview: Optional[LinkPreviewOptions] = None
|
||||
"""Link preview settings."""
|
||||
link_preview_is_disabled: Optional[bool] = None
|
||||
"""Disables link preview."""
|
||||
link_preview_prefer_small_media: Optional[bool] = None
|
||||
"""Prefer small media in link preview."""
|
||||
link_preview_prefer_large_media: Optional[bool] = None
|
||||
"""Prefer large media in link preview."""
|
||||
link_preview_show_above_text: Optional[bool] = None
|
||||
"""Show link preview above text."""
|
||||
show_caption_above_media: Optional[bool] = None
|
||||
"""Show caption above media."""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
has_any_link_preview_option = any(
|
||||
(
|
||||
self.link_preview_is_disabled,
|
||||
self.link_preview_prefer_small_media,
|
||||
self.link_preview_prefer_large_media,
|
||||
self.link_preview_show_above_text,
|
||||
)
|
||||
)
|
||||
|
||||
if has_any_link_preview_option and self.link_preview is None:
|
||||
from ..types import LinkPreviewOptions
|
||||
|
||||
self.link_preview = LinkPreviewOptions(
|
||||
is_disabled=self.link_preview_is_disabled,
|
||||
prefer_small_media=self.link_preview_prefer_small_media,
|
||||
prefer_large_media=self.link_preview_prefer_large_media,
|
||||
show_above_text=self.link_preview_show_above_text,
|
||||
)
|
||||
|
||||
def __getitem__(self, item: str) -> Any:
|
||||
return getattr(self, item, None)
|
211
myenv/Lib/site-packages/aiogram/client/session/aiohttp.py
Normal file
211
myenv/Lib/site-packages/aiogram/client/session/aiohttp.py
Normal file
@@ -0,0 +1,211 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import ssl
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AsyncGenerator,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
import certifi
|
||||
from aiohttp import BasicAuth, ClientError, ClientSession, FormData, TCPConnector
|
||||
from aiohttp.hdrs import USER_AGENT
|
||||
from aiohttp.http import SERVER_SOFTWARE
|
||||
|
||||
from aiogram.__meta__ import __version__
|
||||
from aiogram.methods import TelegramMethod
|
||||
|
||||
from ...exceptions import TelegramNetworkError
|
||||
from ...methods.base import TelegramType
|
||||
from ...types import InputFile
|
||||
from .base import BaseSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..bot import Bot
|
||||
|
||||
_ProxyBasic = Union[str, Tuple[str, BasicAuth]]
|
||||
_ProxyChain = Iterable[_ProxyBasic]
|
||||
_ProxyType = Union[_ProxyChain, _ProxyBasic]
|
||||
|
||||
|
||||
def _retrieve_basic(basic: _ProxyBasic) -> Dict[str, Any]:
|
||||
from aiohttp_socks.utils import parse_proxy_url
|
||||
|
||||
proxy_auth: Optional[BasicAuth] = None
|
||||
|
||||
if isinstance(basic, str):
|
||||
proxy_url = basic
|
||||
else:
|
||||
proxy_url, proxy_auth = basic
|
||||
|
||||
proxy_type, host, port, username, password = parse_proxy_url(proxy_url)
|
||||
if isinstance(proxy_auth, BasicAuth):
|
||||
username = proxy_auth.login
|
||||
password = proxy_auth.password
|
||||
|
||||
return {
|
||||
"proxy_type": proxy_type,
|
||||
"host": host,
|
||||
"port": port,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"rdns": True,
|
||||
}
|
||||
|
||||
|
||||
def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"], Dict[str, Any]]:
|
||||
from aiohttp_socks import ChainProxyConnector, ProxyConnector, ProxyInfo
|
||||
|
||||
# since tuple is Iterable(compatible with _ProxyChain) object, we assume that
|
||||
# user wants chained proxies if tuple is a pair of string(url) and BasicAuth
|
||||
if isinstance(chain_or_plain, str) or (
|
||||
isinstance(chain_or_plain, tuple) and len(chain_or_plain) == 2
|
||||
):
|
||||
chain_or_plain = cast(_ProxyBasic, chain_or_plain)
|
||||
return ProxyConnector, _retrieve_basic(chain_or_plain)
|
||||
|
||||
chain_or_plain = cast(_ProxyChain, chain_or_plain)
|
||||
infos: List[ProxyInfo] = []
|
||||
for basic in chain_or_plain:
|
||||
infos.append(ProxyInfo(**_retrieve_basic(basic)))
|
||||
|
||||
return ChainProxyConnector, {"proxy_infos": infos}
|
||||
|
||||
|
||||
class AiohttpSession(BaseSession):
|
||||
def __init__(
|
||||
self, proxy: Optional[_ProxyType] = None, limit: int = 100, **kwargs: Any
|
||||
) -> None:
|
||||
"""
|
||||
Client session based on aiohttp.
|
||||
|
||||
:param proxy: The proxy to be used for requests. Default is None.
|
||||
:param limit: The total number of simultaneous connections. Default is 100.
|
||||
:param kwargs: Additional keyword arguments.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._session: Optional[ClientSession] = None
|
||||
self._connector_type: Type[TCPConnector] = TCPConnector
|
||||
self._connector_init: Dict[str, Any] = {
|
||||
"ssl": ssl.create_default_context(cafile=certifi.where()),
|
||||
"limit": limit,
|
||||
"ttl_dns_cache": 3600, # Workaround for https://github.com/aiogram/aiogram/issues/1500
|
||||
}
|
||||
self._should_reset_connector = True # flag determines connector state
|
||||
self._proxy: Optional[_ProxyType] = None
|
||||
|
||||
if proxy is not None:
|
||||
try:
|
||||
self._setup_proxy_connector(proxy)
|
||||
except ImportError as exc: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
"In order to use aiohttp client for proxy requests, install "
|
||||
"https://pypi.org/project/aiohttp-socks/"
|
||||
) from exc
|
||||
|
||||
def _setup_proxy_connector(self, proxy: _ProxyType) -> None:
|
||||
self._connector_type, self._connector_init = _prepare_connector(proxy)
|
||||
self._proxy = proxy
|
||||
|
||||
@property
|
||||
def proxy(self) -> Optional[_ProxyType]:
|
||||
return self._proxy
|
||||
|
||||
@proxy.setter
|
||||
def proxy(self, proxy: _ProxyType) -> None:
|
||||
self._setup_proxy_connector(proxy)
|
||||
self._should_reset_connector = True
|
||||
|
||||
async def create_session(self) -> ClientSession:
|
||||
if self._should_reset_connector:
|
||||
await self.close()
|
||||
|
||||
if self._session is None or self._session.closed:
|
||||
self._session = ClientSession(
|
||||
connector=self._connector_type(**self._connector_init),
|
||||
headers={
|
||||
USER_AGENT: f"{SERVER_SOFTWARE} aiogram/{__version__}",
|
||||
},
|
||||
)
|
||||
self._should_reset_connector = False
|
||||
|
||||
return self._session
|
||||
|
||||
async def close(self) -> None:
|
||||
if self._session is not None and not self._session.closed:
|
||||
await self._session.close()
|
||||
|
||||
# Wait 250 ms for the underlying SSL connections to close
|
||||
# https://docs.aiohttp.org/en/stable/client_advanced.html#graceful-shutdown
|
||||
await asyncio.sleep(0.25)
|
||||
|
||||
def build_form_data(self, bot: Bot, method: TelegramMethod[TelegramType]) -> FormData:
|
||||
form = FormData(quote_fields=False)
|
||||
files: Dict[str, InputFile] = {}
|
||||
for key, value in method.model_dump(warnings=False).items():
|
||||
value = self.prepare_value(value, bot=bot, files=files)
|
||||
if not value:
|
||||
continue
|
||||
form.add_field(key, value)
|
||||
for key, value in files.items():
|
||||
form.add_field(
|
||||
key,
|
||||
value.read(bot),
|
||||
filename=value.filename or key,
|
||||
)
|
||||
return form
|
||||
|
||||
async def make_request(
|
||||
self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = None
|
||||
) -> TelegramType:
|
||||
session = await self.create_session()
|
||||
|
||||
url = self.api.api_url(token=bot.token, method=method.__api_method__)
|
||||
form = self.build_form_data(bot=bot, method=method)
|
||||
|
||||
try:
|
||||
async with session.post(
|
||||
url, data=form, timeout=self.timeout if timeout is None else timeout
|
||||
) as resp:
|
||||
raw_result = await resp.text()
|
||||
except asyncio.TimeoutError:
|
||||
raise TelegramNetworkError(method=method, message="Request timeout error")
|
||||
except ClientError as e:
|
||||
raise TelegramNetworkError(method=method, message=f"{type(e).__name__}: {e}")
|
||||
response = self.check_response(
|
||||
bot=bot, method=method, status_code=resp.status, content=raw_result
|
||||
)
|
||||
return cast(TelegramType, response.result)
|
||||
|
||||
async def stream_content(
|
||||
self,
|
||||
url: str,
|
||||
headers: Optional[Dict[str, Any]] = None,
|
||||
timeout: int = 30,
|
||||
chunk_size: int = 65536,
|
||||
raise_for_status: bool = True,
|
||||
) -> AsyncGenerator[bytes, None]:
|
||||
if headers is None:
|
||||
headers = {}
|
||||
|
||||
session = await self.create_session()
|
||||
|
||||
async with session.get(
|
||||
url, timeout=timeout, headers=headers, raise_for_status=raise_for_status
|
||||
) as resp:
|
||||
async for chunk in resp.content.iter_chunked(chunk_size):
|
||||
yield chunk
|
||||
|
||||
async def __aenter__(self) -> AiohttpSession:
|
||||
await self.create_session()
|
||||
return self
|
265
myenv/Lib/site-packages/aiogram/client/session/base.py
Normal file
265
myenv/Lib/site-packages/aiogram/client/session/base.py
Normal file
@@ -0,0 +1,265 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import datetime
|
||||
import json
|
||||
import secrets
|
||||
from enum import Enum
|
||||
from http import HTTPStatus
|
||||
from types import TracebackType
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AsyncGenerator,
|
||||
Callable,
|
||||
Dict,
|
||||
Final,
|
||||
Optional,
|
||||
Type,
|
||||
cast,
|
||||
)
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from aiogram.exceptions import (
|
||||
ClientDecodeError,
|
||||
RestartingTelegram,
|
||||
TelegramAPIError,
|
||||
TelegramBadRequest,
|
||||
TelegramConflictError,
|
||||
TelegramEntityTooLarge,
|
||||
TelegramForbiddenError,
|
||||
TelegramMigrateToChat,
|
||||
TelegramNotFound,
|
||||
TelegramRetryAfter,
|
||||
TelegramServerError,
|
||||
TelegramUnauthorizedError,
|
||||
)
|
||||
|
||||
from ...methods import Response, TelegramMethod
|
||||
from ...methods.base import TelegramType
|
||||
from ...types import InputFile, TelegramObject
|
||||
from ..default import Default
|
||||
from ..telegram import PRODUCTION, TelegramAPIServer
|
||||
from .middlewares.manager import RequestMiddlewareManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..bot import Bot
|
||||
|
||||
_JsonLoads = Callable[..., Any]
|
||||
_JsonDumps = Callable[..., str]
|
||||
|
||||
DEFAULT_TIMEOUT: Final[float] = 60.0
|
||||
|
||||
|
||||
class BaseSession(abc.ABC):
|
||||
"""
|
||||
This is base class for all HTTP sessions in aiogram.
|
||||
|
||||
If you want to create your own session, you must inherit from this class.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api: TelegramAPIServer = PRODUCTION,
|
||||
json_loads: _JsonLoads = json.loads,
|
||||
json_dumps: _JsonDumps = json.dumps,
|
||||
timeout: float = DEFAULT_TIMEOUT,
|
||||
) -> None:
|
||||
"""
|
||||
|
||||
:param api: Telegram Bot API URL patterns
|
||||
:param json_loads: JSON loader
|
||||
:param json_dumps: JSON dumper
|
||||
:param timeout: Session scope request timeout
|
||||
"""
|
||||
self.api = api
|
||||
self.json_loads = json_loads
|
||||
self.json_dumps = json_dumps
|
||||
self.timeout = timeout
|
||||
|
||||
self.middleware = RequestMiddlewareManager()
|
||||
|
||||
def check_response(
|
||||
self, bot: Bot, method: TelegramMethod[TelegramType], status_code: int, content: str
|
||||
) -> Response[TelegramType]:
|
||||
"""
|
||||
Check response status
|
||||
"""
|
||||
try:
|
||||
json_data = self.json_loads(content)
|
||||
except Exception as e:
|
||||
# Handled error type can't be classified as specific error
|
||||
# in due to decoder can be customized and raise any exception
|
||||
|
||||
raise ClientDecodeError("Failed to decode object", e, content)
|
||||
|
||||
try:
|
||||
response_type = Response[method.__returning__] # type: ignore
|
||||
response = response_type.model_validate(json_data, context={"bot": bot})
|
||||
except ValidationError as e:
|
||||
raise ClientDecodeError("Failed to deserialize object", e, json_data)
|
||||
|
||||
if HTTPStatus.OK <= status_code <= HTTPStatus.IM_USED and response.ok:
|
||||
return response
|
||||
|
||||
description = cast(str, response.description)
|
||||
|
||||
if parameters := response.parameters:
|
||||
if parameters.retry_after:
|
||||
raise TelegramRetryAfter(
|
||||
method=method, message=description, retry_after=parameters.retry_after
|
||||
)
|
||||
if parameters.migrate_to_chat_id:
|
||||
raise TelegramMigrateToChat(
|
||||
method=method,
|
||||
message=description,
|
||||
migrate_to_chat_id=parameters.migrate_to_chat_id,
|
||||
)
|
||||
if status_code == HTTPStatus.BAD_REQUEST:
|
||||
raise TelegramBadRequest(method=method, message=description)
|
||||
if status_code == HTTPStatus.NOT_FOUND:
|
||||
raise TelegramNotFound(method=method, message=description)
|
||||
if status_code == HTTPStatus.CONFLICT:
|
||||
raise TelegramConflictError(method=method, message=description)
|
||||
if status_code == HTTPStatus.UNAUTHORIZED:
|
||||
raise TelegramUnauthorizedError(method=method, message=description)
|
||||
if status_code == HTTPStatus.FORBIDDEN:
|
||||
raise TelegramForbiddenError(method=method, message=description)
|
||||
if status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
|
||||
raise TelegramEntityTooLarge(method=method, message=description)
|
||||
if status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
|
||||
if "restart" in description:
|
||||
raise RestartingTelegram(method=method, message=description)
|
||||
raise TelegramServerError(method=method, message=description)
|
||||
|
||||
raise TelegramAPIError(
|
||||
method=method,
|
||||
message=description,
|
||||
)
|
||||
|
||||
@abc.abstractmethod
|
||||
async def close(self) -> None: # pragma: no cover
|
||||
"""
|
||||
Close client session
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
async def make_request(
|
||||
self,
|
||||
bot: Bot,
|
||||
method: TelegramMethod[TelegramType],
|
||||
timeout: Optional[int] = None,
|
||||
) -> TelegramType: # pragma: no cover
|
||||
"""
|
||||
Make request to Telegram Bot API
|
||||
|
||||
:param bot: Bot instance
|
||||
:param method: Method instance
|
||||
:param timeout: Request timeout
|
||||
:return:
|
||||
:raise TelegramApiError:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
async def stream_content(
|
||||
self,
|
||||
url: str,
|
||||
headers: Optional[Dict[str, Any]] = None,
|
||||
timeout: int = 30,
|
||||
chunk_size: int = 65536,
|
||||
raise_for_status: bool = True,
|
||||
) -> AsyncGenerator[bytes, None]: # pragma: no cover
|
||||
"""
|
||||
Stream reader
|
||||
"""
|
||||
yield b""
|
||||
|
||||
def prepare_value(
|
||||
self,
|
||||
value: Any,
|
||||
bot: Bot,
|
||||
files: Dict[str, Any],
|
||||
_dumps_json: bool = True,
|
||||
) -> Any:
|
||||
"""
|
||||
Prepare value before send
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
if isinstance(value, Default):
|
||||
default_value = bot.default[value.name]
|
||||
return self.prepare_value(default_value, bot=bot, files=files, _dumps_json=_dumps_json)
|
||||
if isinstance(value, InputFile):
|
||||
key = secrets.token_urlsafe(10)
|
||||
files[key] = value
|
||||
return f"attach://{key}"
|
||||
if isinstance(value, dict):
|
||||
value = {
|
||||
key: prepared_item
|
||||
for key, item in value.items()
|
||||
if (
|
||||
prepared_item := self.prepare_value(
|
||||
item, bot=bot, files=files, _dumps_json=False
|
||||
)
|
||||
)
|
||||
is not None
|
||||
}
|
||||
if _dumps_json:
|
||||
return self.json_dumps(value)
|
||||
return value
|
||||
if isinstance(value, list):
|
||||
value = [
|
||||
prepared_item
|
||||
for item in value
|
||||
if (
|
||||
prepared_item := self.prepare_value(
|
||||
item, bot=bot, files=files, _dumps_json=False
|
||||
)
|
||||
)
|
||||
is not None
|
||||
]
|
||||
if _dumps_json:
|
||||
return self.json_dumps(value)
|
||||
return value
|
||||
if isinstance(value, datetime.timedelta):
|
||||
now = datetime.datetime.now()
|
||||
return str(round((now + value).timestamp()))
|
||||
if isinstance(value, datetime.datetime):
|
||||
return str(round(value.timestamp()))
|
||||
if isinstance(value, Enum):
|
||||
return self.prepare_value(value.value, bot=bot, files=files)
|
||||
if isinstance(value, TelegramObject):
|
||||
return self.prepare_value(
|
||||
value.model_dump(warnings=False),
|
||||
bot=bot,
|
||||
files=files,
|
||||
_dumps_json=_dumps_json,
|
||||
)
|
||||
if _dumps_json:
|
||||
return self.json_dumps(value)
|
||||
return value
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
bot: Bot,
|
||||
method: TelegramMethod[TelegramType],
|
||||
timeout: Optional[int] = None,
|
||||
) -> TelegramType:
|
||||
middleware = self.middleware.wrap_middlewares(self.make_request, timeout=timeout)
|
||||
return cast(TelegramType, await middleware(bot, method))
|
||||
|
||||
async def __aenter__(self) -> BaseSession:
|
||||
return self
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_value: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
) -> None:
|
||||
await self.close()
|
@@ -0,0 +1,53 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Protocol
|
||||
|
||||
from aiogram.methods import Response, TelegramMethod
|
||||
from aiogram.methods.base import TelegramType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
|
||||
|
||||
class NextRequestMiddlewareType(Protocol[TelegramType]): # pragma: no cover
|
||||
async def __call__(
|
||||
self,
|
||||
bot: "Bot",
|
||||
method: TelegramMethod[TelegramType],
|
||||
) -> Response[TelegramType]:
|
||||
pass
|
||||
|
||||
|
||||
class RequestMiddlewareType(Protocol): # pragma: no cover
|
||||
async def __call__(
|
||||
self,
|
||||
make_request: NextRequestMiddlewareType[TelegramType],
|
||||
bot: "Bot",
|
||||
method: TelegramMethod[TelegramType],
|
||||
) -> Response[TelegramType]:
|
||||
pass
|
||||
|
||||
|
||||
class BaseRequestMiddleware(ABC):
|
||||
"""
|
||||
Generic middleware class
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def __call__(
|
||||
self,
|
||||
make_request: NextRequestMiddlewareType[TelegramType],
|
||||
bot: "Bot",
|
||||
method: TelegramMethod[TelegramType],
|
||||
) -> Response[TelegramType]:
|
||||
"""
|
||||
Execute middleware
|
||||
|
||||
:param make_request: Wrapped make_request in middlewares chain
|
||||
:param bot: bot for request making
|
||||
:param method: Request method (Subclass of :class:`aiogram.methods.base.TelegramMethod`)
|
||||
|
||||
:return: :class:`aiogram.methods.Response`
|
||||
"""
|
||||
pass
|
@@ -0,0 +1,62 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
from typing import Any, Callable, List, Optional, Sequence, Union, cast, overload
|
||||
|
||||
from aiogram.client.session.middlewares.base import (
|
||||
NextRequestMiddlewareType,
|
||||
RequestMiddlewareType,
|
||||
)
|
||||
from aiogram.methods.base import TelegramType
|
||||
|
||||
|
||||
class RequestMiddlewareManager(Sequence[RequestMiddlewareType]):
|
||||
def __init__(self) -> None:
|
||||
self._middlewares: List[RequestMiddlewareType] = []
|
||||
|
||||
def register(
|
||||
self,
|
||||
middleware: RequestMiddlewareType,
|
||||
) -> RequestMiddlewareType:
|
||||
self._middlewares.append(middleware)
|
||||
return middleware
|
||||
|
||||
def unregister(self, middleware: RequestMiddlewareType) -> None:
|
||||
self._middlewares.remove(middleware)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
middleware: Optional[RequestMiddlewareType] = None,
|
||||
) -> Union[
|
||||
Callable[[RequestMiddlewareType], RequestMiddlewareType],
|
||||
RequestMiddlewareType,
|
||||
]:
|
||||
if middleware is None:
|
||||
return self.register
|
||||
return self.register(middleware)
|
||||
|
||||
@overload
|
||||
def __getitem__(self, item: int) -> RequestMiddlewareType:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def __getitem__(self, item: slice) -> Sequence[RequestMiddlewareType]:
|
||||
pass
|
||||
|
||||
def __getitem__(
|
||||
self, item: Union[int, slice]
|
||||
) -> Union[RequestMiddlewareType, Sequence[RequestMiddlewareType]]:
|
||||
return self._middlewares[item]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._middlewares)
|
||||
|
||||
def wrap_middlewares(
|
||||
self,
|
||||
callback: NextRequestMiddlewareType[TelegramType],
|
||||
**kwargs: Any,
|
||||
) -> NextRequestMiddlewareType[TelegramType]:
|
||||
middleware = partial(callback, **kwargs)
|
||||
for m in reversed(self._middlewares):
|
||||
middleware = partial(m, middleware)
|
||||
return cast(NextRequestMiddlewareType[TelegramType], middleware)
|
@@ -0,0 +1,37 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Type
|
||||
|
||||
from aiogram import loggers
|
||||
from aiogram.methods import TelegramMethod
|
||||
from aiogram.methods.base import Response, TelegramType
|
||||
|
||||
from .base import BaseRequestMiddleware, NextRequestMiddlewareType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RequestLogging(BaseRequestMiddleware):
|
||||
def __init__(self, ignore_methods: Optional[List[Type[TelegramMethod[Any]]]] = None):
|
||||
"""
|
||||
Middleware for logging outgoing requests
|
||||
|
||||
:param ignore_methods: methods to ignore in logging middleware
|
||||
"""
|
||||
self.ignore_methods = ignore_methods if ignore_methods else []
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
make_request: NextRequestMiddlewareType[TelegramType],
|
||||
bot: "Bot",
|
||||
method: TelegramMethod[TelegramType],
|
||||
) -> Response[TelegramType]:
|
||||
if type(method) not in self.ignore_methods:
|
||||
loggers.middlewares.info(
|
||||
"Make request with method=%r by bot id=%d",
|
||||
type(method).__name__,
|
||||
bot.id,
|
||||
)
|
||||
return await make_request(bot, method)
|
103
myenv/Lib/site-packages/aiogram/client/telegram.py
Normal file
103
myenv/Lib/site-packages/aiogram/client/telegram.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Union
|
||||
|
||||
|
||||
class FilesPathWrapper(ABC):
|
||||
@abstractmethod
|
||||
def to_local(self, path: Union[Path, str]) -> Union[Path, str]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def to_server(self, path: Union[Path, str]) -> Union[Path, str]:
|
||||
pass
|
||||
|
||||
|
||||
class BareFilesPathWrapper(FilesPathWrapper):
|
||||
def to_local(self, path: Union[Path, str]) -> Union[Path, str]:
|
||||
return path
|
||||
|
||||
def to_server(self, path: Union[Path, str]) -> Union[Path, str]:
|
||||
return path
|
||||
|
||||
|
||||
class SimpleFilesPathWrapper(FilesPathWrapper):
|
||||
def __init__(self, server_path: Path, local_path: Path) -> None:
|
||||
self.server_path = server_path
|
||||
self.local_path = local_path
|
||||
|
||||
@classmethod
|
||||
def _resolve(
|
||||
cls, base1: Union[Path, str], base2: Union[Path, str], value: Union[Path, str]
|
||||
) -> Path:
|
||||
relative = Path(value).relative_to(base1)
|
||||
return base2 / relative
|
||||
|
||||
def to_local(self, path: Union[Path, str]) -> Union[Path, str]:
|
||||
return self._resolve(base1=self.server_path, base2=self.local_path, value=path)
|
||||
|
||||
def to_server(self, path: Union[Path, str]) -> Union[Path, str]:
|
||||
return self._resolve(base1=self.local_path, base2=self.server_path, value=path)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TelegramAPIServer:
|
||||
"""
|
||||
Base config for API Endpoints
|
||||
"""
|
||||
|
||||
base: str
|
||||
"""Base URL"""
|
||||
file: str
|
||||
"""Files URL"""
|
||||
is_local: bool = False
|
||||
"""Mark this server is
|
||||
in `local mode <https://core.telegram.org/bots/api#using-a-local-bot-api-server>`_."""
|
||||
wrap_local_file: FilesPathWrapper = BareFilesPathWrapper()
|
||||
"""Callback to wrap files path in local mode"""
|
||||
|
||||
def api_url(self, token: str, method: str) -> str:
|
||||
"""
|
||||
Generate URL for API methods
|
||||
|
||||
:param token: Bot token
|
||||
:param method: API method name (case insensitive)
|
||||
:return: URL
|
||||
"""
|
||||
return self.base.format(token=token, method=method)
|
||||
|
||||
def file_url(self, token: str, path: Union[str, Path]) -> str:
|
||||
"""
|
||||
Generate URL for downloading files
|
||||
|
||||
:param token: Bot token
|
||||
:param path: file path
|
||||
:return: URL
|
||||
"""
|
||||
return self.file.format(token=token, path=path)
|
||||
|
||||
@classmethod
|
||||
def from_base(cls, base: str, **kwargs: Any) -> "TelegramAPIServer":
|
||||
"""
|
||||
Use this method to auto-generate TelegramAPIServer instance from base URL
|
||||
|
||||
:param base: Base URL
|
||||
:return: instance of :class:`TelegramAPIServer`
|
||||
"""
|
||||
base = base.rstrip("/")
|
||||
return cls(
|
||||
base=f"{base}/bot{{token}}/{{method}}",
|
||||
file=f"{base}/file/bot{{token}}/{{path}}",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
PRODUCTION = TelegramAPIServer(
|
||||
base="https://api.telegram.org/bot{token}/{method}",
|
||||
file="https://api.telegram.org/file/bot{token}/{path}",
|
||||
)
|
||||
TEST = TelegramAPIServer(
|
||||
base="https://api.telegram.org/bot{token}/test/{method}",
|
||||
file="https://api.telegram.org/file/bot{token}/test/{path}",
|
||||
)
|
642
myenv/Lib/site-packages/aiogram/dispatcher/dispatcher.py
Normal file
642
myenv/Lib/site-packages/aiogram/dispatcher/dispatcher.py
Normal file
@@ -0,0 +1,642 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextvars
|
||||
import signal
|
||||
import warnings
|
||||
from asyncio import CancelledError, Event, Future, Lock
|
||||
from contextlib import suppress
|
||||
from typing import Any, AsyncGenerator, Awaitable, Dict, List, Optional, Set, Union
|
||||
|
||||
from .. import loggers
|
||||
from ..client.bot import Bot
|
||||
from ..exceptions import TelegramAPIError
|
||||
from ..fsm.middleware import FSMContextMiddleware
|
||||
from ..fsm.storage.base import BaseEventIsolation, BaseStorage
|
||||
from ..fsm.storage.memory import DisabledEventIsolation, MemoryStorage
|
||||
from ..fsm.strategy import FSMStrategy
|
||||
from ..methods import GetUpdates, TelegramMethod
|
||||
from ..methods.base import TelegramType
|
||||
from ..types import Update, User
|
||||
from ..types.base import UNSET, UNSET_TYPE
|
||||
from ..types.update import UpdateTypeLookupError
|
||||
from ..utils.backoff import Backoff, BackoffConfig
|
||||
from .event.bases import UNHANDLED, SkipHandler
|
||||
from .event.telegram import TelegramEventObserver
|
||||
from .middlewares.error import ErrorsMiddleware
|
||||
from .middlewares.user_context import UserContextMiddleware
|
||||
from .router import Router
|
||||
|
||||
DEFAULT_BACKOFF_CONFIG = BackoffConfig(min_delay=1.0, max_delay=5.0, factor=1.3, jitter=0.1)
|
||||
|
||||
|
||||
class Dispatcher(Router):
|
||||
"""
|
||||
Root router
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*, # * - Preventing to pass instance of Bot to the FSM storage
|
||||
storage: Optional[BaseStorage] = None,
|
||||
fsm_strategy: FSMStrategy = FSMStrategy.USER_IN_CHAT,
|
||||
events_isolation: Optional[BaseEventIsolation] = None,
|
||||
disable_fsm: bool = False,
|
||||
name: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
Root router
|
||||
|
||||
:param storage: Storage for FSM
|
||||
:param fsm_strategy: FSM strategy
|
||||
:param events_isolation: Events isolation
|
||||
:param disable_fsm: Disable FSM, note that if you disable FSM
|
||||
then you should not use storage and events isolation
|
||||
:param kwargs: Other arguments, will be passed as keyword arguments to handlers
|
||||
"""
|
||||
super(Dispatcher, self).__init__(name=name)
|
||||
|
||||
if storage and not isinstance(storage, BaseStorage):
|
||||
raise TypeError(
|
||||
f"FSM storage should be instance of 'BaseStorage' not {type(storage).__name__}"
|
||||
)
|
||||
|
||||
# Telegram API provides originally only one event type - Update
|
||||
# For making easily interactions with events here is registered handler which helps
|
||||
# to separate Update to different event types like Message, CallbackQuery etc.
|
||||
self.update = self.observers["update"] = TelegramEventObserver(
|
||||
router=self, event_name="update"
|
||||
)
|
||||
self.update.register(self._listen_update)
|
||||
|
||||
# Error handlers should work is out of all other functions
|
||||
# and should be registered before all others middlewares
|
||||
self.update.outer_middleware(ErrorsMiddleware(self))
|
||||
|
||||
# User context middleware makes small optimization for all other builtin
|
||||
# middlewares via caching the user and chat instances in the event context
|
||||
self.update.outer_middleware(UserContextMiddleware())
|
||||
|
||||
# FSM middleware should always be registered after User context middleware
|
||||
# because here is used context from previous step
|
||||
self.fsm = FSMContextMiddleware(
|
||||
storage=storage or MemoryStorage(),
|
||||
strategy=fsm_strategy,
|
||||
events_isolation=events_isolation or DisabledEventIsolation(),
|
||||
)
|
||||
if not disable_fsm:
|
||||
# Note that when FSM middleware is disabled, the event isolation is also disabled
|
||||
# Because the isolation mechanism is a part of the FSM
|
||||
self.update.outer_middleware(self.fsm)
|
||||
self.shutdown.register(self.fsm.close)
|
||||
|
||||
self.workflow_data: Dict[str, Any] = kwargs
|
||||
self._running_lock = Lock()
|
||||
self._stop_signal: Optional[Event] = None
|
||||
self._stopped_signal: Optional[Event] = None
|
||||
self._handle_update_tasks: Set[asyncio.Task[Any]] = set()
|
||||
|
||||
def __getitem__(self, item: str) -> Any:
|
||||
return self.workflow_data[item]
|
||||
|
||||
def __setitem__(self, key: str, value: Any) -> None:
|
||||
self.workflow_data[key] = value
|
||||
|
||||
def __delitem__(self, key: str) -> None:
|
||||
del self.workflow_data[key]
|
||||
|
||||
def get(self, key: str, /, default: Optional[Any] = None) -> Optional[Any]:
|
||||
return self.workflow_data.get(key, default)
|
||||
|
||||
@property
|
||||
def storage(self) -> BaseStorage:
|
||||
return self.fsm.storage
|
||||
|
||||
@property
|
||||
def parent_router(self) -> Optional[Router]:
|
||||
"""
|
||||
Dispatcher has no parent router and can't be included to any other routers or dispatchers
|
||||
|
||||
:return:
|
||||
"""
|
||||
return None # noqa: RET501
|
||||
|
||||
@parent_router.setter
|
||||
def parent_router(self, value: Router) -> None:
|
||||
"""
|
||||
Dispatcher is root Router then configuring parent router is not allowed
|
||||
|
||||
:param value:
|
||||
:return:
|
||||
"""
|
||||
raise RuntimeError("Dispatcher can not be attached to another Router.")
|
||||
|
||||
async def feed_update(self, bot: Bot, update: Update, **kwargs: Any) -> Any:
|
||||
"""
|
||||
Main entry point for incoming updates
|
||||
Response of this method can be used as Webhook response
|
||||
|
||||
:param bot:
|
||||
:param update:
|
||||
"""
|
||||
loop = asyncio.get_running_loop()
|
||||
handled = False
|
||||
start_time = loop.time()
|
||||
|
||||
if update.bot != bot:
|
||||
# Re-mounting update to the current bot instance for making possible to
|
||||
# use it in shortcuts.
|
||||
# Here is update is re-created because we need to propagate context to
|
||||
# all nested objects and attributes of the Update, but it
|
||||
# is impossible without roundtrip to JSON :(
|
||||
# The preferred way is that pass already mounted Bot instance to this update
|
||||
# before call feed_update method
|
||||
update = Update.model_validate(update.model_dump(), context={"bot": bot})
|
||||
|
||||
try:
|
||||
response = await self.update.wrap_outer_middleware(
|
||||
self.update.trigger,
|
||||
update,
|
||||
{
|
||||
**self.workflow_data,
|
||||
**kwargs,
|
||||
"bot": bot,
|
||||
},
|
||||
)
|
||||
handled = response is not UNHANDLED
|
||||
return response
|
||||
finally:
|
||||
finish_time = loop.time()
|
||||
duration = (finish_time - start_time) * 1000
|
||||
loggers.event.info(
|
||||
"Update id=%s is %s. Duration %d ms by bot id=%d",
|
||||
update.update_id,
|
||||
"handled" if handled else "not handled",
|
||||
duration,
|
||||
bot.id,
|
||||
)
|
||||
|
||||
async def feed_raw_update(self, bot: Bot, update: Dict[str, Any], **kwargs: Any) -> Any:
|
||||
"""
|
||||
Main entry point for incoming updates with automatic Dict->Update serializer
|
||||
|
||||
:param bot:
|
||||
:param update:
|
||||
:param kwargs:
|
||||
"""
|
||||
parsed_update = Update.model_validate(update, context={"bot": bot})
|
||||
return await self._feed_webhook_update(bot=bot, update=parsed_update, **kwargs)
|
||||
|
||||
@classmethod
|
||||
async def _listen_updates(
|
||||
cls,
|
||||
bot: Bot,
|
||||
polling_timeout: int = 30,
|
||||
backoff_config: BackoffConfig = DEFAULT_BACKOFF_CONFIG,
|
||||
allowed_updates: Optional[List[str]] = None,
|
||||
) -> AsyncGenerator[Update, None]:
|
||||
"""
|
||||
Endless updates reader with correctly handling any server-side or connection errors.
|
||||
|
||||
So you may not worry that the polling will stop working.
|
||||
"""
|
||||
backoff = Backoff(config=backoff_config)
|
||||
get_updates = GetUpdates(timeout=polling_timeout, allowed_updates=allowed_updates)
|
||||
kwargs = {}
|
||||
if bot.session.timeout:
|
||||
# Request timeout can be lower than session timeout and that's OK.
|
||||
# To prevent false-positive TimeoutError we should wait longer than polling timeout
|
||||
kwargs["request_timeout"] = int(bot.session.timeout + polling_timeout)
|
||||
failed = False
|
||||
while True:
|
||||
try:
|
||||
updates = await bot(get_updates, **kwargs)
|
||||
except Exception as e:
|
||||
failed = True
|
||||
# In cases when Telegram Bot API was inaccessible don't need to stop polling
|
||||
# process because some developers can't make auto-restarting of the script
|
||||
loggers.dispatcher.error("Failed to fetch updates - %s: %s", type(e).__name__, e)
|
||||
# And also backoff timeout is best practice to retry any network activity
|
||||
loggers.dispatcher.warning(
|
||||
"Sleep for %f seconds and try again... (tryings = %d, bot id = %d)",
|
||||
backoff.next_delay,
|
||||
backoff.counter,
|
||||
bot.id,
|
||||
)
|
||||
await backoff.asleep()
|
||||
continue
|
||||
|
||||
# In case when network connection was fixed let's reset the backoff
|
||||
# to initial value and then process updates
|
||||
if failed:
|
||||
loggers.dispatcher.info(
|
||||
"Connection established (tryings = %d, bot id = %d)",
|
||||
backoff.counter,
|
||||
bot.id,
|
||||
)
|
||||
backoff.reset()
|
||||
failed = False
|
||||
|
||||
for update in updates:
|
||||
yield update
|
||||
# The getUpdates method returns the earliest 100 unconfirmed updates.
|
||||
# To confirm an update, use the offset parameter when calling getUpdates
|
||||
# All updates with update_id less than or equal to offset will be marked
|
||||
# as confirmed on the server and will no longer be returned.
|
||||
get_updates.offset = update.update_id + 1
|
||||
|
||||
async def _listen_update(self, update: Update, **kwargs: Any) -> Any:
|
||||
"""
|
||||
Main updates listener
|
||||
|
||||
Workflow:
|
||||
- Detect content type and propagate to observers in current router
|
||||
- If no one filter is pass - propagate update to child routers as Update
|
||||
|
||||
:param update:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
update_type = update.event_type
|
||||
event = update.event
|
||||
except UpdateTypeLookupError as e:
|
||||
warnings.warn(
|
||||
"Detected unknown update type.\n"
|
||||
"Seems like Telegram Bot API was updated and you have "
|
||||
"installed not latest version of aiogram framework"
|
||||
f"\nUpdate: {update.model_dump_json(exclude_unset=True)}",
|
||||
RuntimeWarning,
|
||||
)
|
||||
raise SkipHandler() from e
|
||||
|
||||
kwargs.update(event_update=update)
|
||||
|
||||
return await self.propagate_event(update_type=update_type, event=event, **kwargs)
|
||||
|
||||
@classmethod
|
||||
async def silent_call_request(cls, bot: Bot, result: TelegramMethod[Any]) -> None:
|
||||
"""
|
||||
Simulate answer into WebHook
|
||||
|
||||
:param bot:
|
||||
:param result:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
await bot(result)
|
||||
except TelegramAPIError as e:
|
||||
# In due to WebHook mechanism doesn't allow getting response for
|
||||
# requests called in answer to WebHook request.
|
||||
# Need to skip unsuccessful responses.
|
||||
# For debugging here is added logging.
|
||||
loggers.event.error("Failed to make answer: %s: %s", e.__class__.__name__, e)
|
||||
|
||||
async def _process_update(
|
||||
self, bot: Bot, update: Update, call_answer: bool = True, **kwargs: Any
|
||||
) -> bool:
|
||||
"""
|
||||
Propagate update to event listeners
|
||||
|
||||
:param bot: instance of Bot
|
||||
:param update: instance of Update
|
||||
:param call_answer: need to execute response as Telegram method (like answer into webhook)
|
||||
:param kwargs: contextual data for middlewares, filters and handlers
|
||||
:return: status
|
||||
"""
|
||||
try:
|
||||
response = await self.feed_update(bot, update, **kwargs)
|
||||
if call_answer and isinstance(response, TelegramMethod):
|
||||
await self.silent_call_request(bot=bot, result=response)
|
||||
return response is not UNHANDLED
|
||||
|
||||
except Exception as e:
|
||||
loggers.event.exception(
|
||||
"Cause exception while process update id=%d by bot id=%d\n%s: %s",
|
||||
update.update_id,
|
||||
bot.id,
|
||||
e.__class__.__name__,
|
||||
e,
|
||||
)
|
||||
return True # because update was processed but unsuccessful
|
||||
|
||||
async def _process_with_semaphore(
|
||||
self, handle_update: Awaitable[bool], semaphore: asyncio.Semaphore
|
||||
) -> bool:
|
||||
"""
|
||||
Process update with semaphore to limit concurrent tasks
|
||||
|
||||
:param handle_update: Coroutine that processes the update
|
||||
:param semaphore: Semaphore to limit concurrent tasks
|
||||
:return: bool indicating the result of the update processing
|
||||
"""
|
||||
try:
|
||||
return await handle_update
|
||||
finally:
|
||||
semaphore.release()
|
||||
|
||||
async def _polling(
|
||||
self,
|
||||
bot: Bot,
|
||||
polling_timeout: int = 30,
|
||||
handle_as_tasks: bool = True,
|
||||
backoff_config: BackoffConfig = DEFAULT_BACKOFF_CONFIG,
|
||||
allowed_updates: Optional[List[str]] = None,
|
||||
tasks_concurrency_limit: Optional[int] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
Internal polling process
|
||||
|
||||
:param bot:
|
||||
:param polling_timeout: Long-polling wait time
|
||||
:param handle_as_tasks: Run task for each event and no wait result
|
||||
:param backoff_config: backoff-retry config
|
||||
:param allowed_updates: List of the update types you want your bot to receive
|
||||
:param tasks_concurrency_limit: Maximum number of concurrent updates to process
|
||||
(None = no limit), used only if handle_as_tasks is True
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
user: User = await bot.me()
|
||||
loggers.dispatcher.info(
|
||||
"Run polling for bot @%s id=%d - %r", user.username, bot.id, user.full_name
|
||||
)
|
||||
|
||||
# Create semaphore if tasks_concurrency_limit is specified
|
||||
semaphore = None
|
||||
if tasks_concurrency_limit is not None and handle_as_tasks:
|
||||
semaphore = asyncio.Semaphore(tasks_concurrency_limit)
|
||||
|
||||
try:
|
||||
async for update in self._listen_updates(
|
||||
bot,
|
||||
polling_timeout=polling_timeout,
|
||||
backoff_config=backoff_config,
|
||||
allowed_updates=allowed_updates,
|
||||
):
|
||||
handle_update = self._process_update(bot=bot, update=update, **kwargs)
|
||||
if handle_as_tasks:
|
||||
if semaphore:
|
||||
# Use semaphore to limit concurrent tasks
|
||||
await semaphore.acquire()
|
||||
handle_update_task = asyncio.create_task(
|
||||
self._process_with_semaphore(handle_update, semaphore)
|
||||
)
|
||||
else:
|
||||
handle_update_task = asyncio.create_task(handle_update)
|
||||
|
||||
self._handle_update_tasks.add(handle_update_task)
|
||||
handle_update_task.add_done_callback(self._handle_update_tasks.discard)
|
||||
else:
|
||||
await handle_update
|
||||
finally:
|
||||
loggers.dispatcher.info(
|
||||
"Polling stopped for bot @%s id=%d - %r", user.username, bot.id, user.full_name
|
||||
)
|
||||
|
||||
async def _feed_webhook_update(self, bot: Bot, update: Update, **kwargs: Any) -> Any:
|
||||
"""
|
||||
The same with `Dispatcher.process_update()` but returns real response instead of bool
|
||||
"""
|
||||
try:
|
||||
return await self.feed_update(bot, update, **kwargs)
|
||||
except Exception as e:
|
||||
loggers.event.exception(
|
||||
"Cause exception while process update id=%d by bot id=%d\n%s: %s",
|
||||
update.update_id,
|
||||
bot.id,
|
||||
e.__class__.__name__,
|
||||
e,
|
||||
)
|
||||
raise
|
||||
|
||||
async def feed_webhook_update(
|
||||
self, bot: Bot, update: Union[Update, Dict[str, Any]], _timeout: float = 55, **kwargs: Any
|
||||
) -> Optional[TelegramMethod[TelegramType]]:
|
||||
if not isinstance(update, Update): # Allow to use raw updates
|
||||
update = Update.model_validate(update, context={"bot": bot})
|
||||
|
||||
ctx = contextvars.copy_context()
|
||||
loop = asyncio.get_running_loop()
|
||||
waiter = loop.create_future()
|
||||
|
||||
def release_waiter(*_: Any) -> None:
|
||||
if not waiter.done():
|
||||
waiter.set_result(None)
|
||||
|
||||
timeout_handle = loop.call_later(_timeout, release_waiter)
|
||||
|
||||
process_updates: Future[Any] = asyncio.ensure_future(
|
||||
self._feed_webhook_update(bot=bot, update=update, **kwargs)
|
||||
)
|
||||
process_updates.add_done_callback(release_waiter, context=ctx)
|
||||
|
||||
def process_response(task: Future[Any]) -> None:
|
||||
warnings.warn(
|
||||
"Detected slow response into webhook.\n"
|
||||
"Telegram is waiting for response only first 60 seconds and then re-send update.\n"
|
||||
"For preventing this situation response into webhook returned immediately "
|
||||
"and handler is moved to background and still processing update.",
|
||||
RuntimeWarning,
|
||||
)
|
||||
try:
|
||||
result = task.result()
|
||||
except Exception as e:
|
||||
raise e
|
||||
if isinstance(result, TelegramMethod):
|
||||
asyncio.ensure_future(self.silent_call_request(bot=bot, result=result))
|
||||
|
||||
try:
|
||||
try:
|
||||
await waiter
|
||||
except CancelledError: # pragma: no cover
|
||||
process_updates.remove_done_callback(release_waiter)
|
||||
process_updates.cancel()
|
||||
raise
|
||||
|
||||
if process_updates.done():
|
||||
# TODO: handle exceptions
|
||||
response: Any = process_updates.result()
|
||||
if isinstance(response, TelegramMethod):
|
||||
return response
|
||||
|
||||
else:
|
||||
process_updates.remove_done_callback(release_waiter)
|
||||
process_updates.add_done_callback(process_response, context=ctx)
|
||||
|
||||
finally:
|
||||
timeout_handle.cancel()
|
||||
|
||||
return None
|
||||
|
||||
async def stop_polling(self) -> None:
|
||||
"""
|
||||
Execute this method if you want to stop polling programmatically
|
||||
|
||||
:return:
|
||||
"""
|
||||
if not self._running_lock.locked():
|
||||
raise RuntimeError("Polling is not started")
|
||||
if not self._stop_signal or not self._stopped_signal:
|
||||
return
|
||||
self._stop_signal.set()
|
||||
await self._stopped_signal.wait()
|
||||
|
||||
def _signal_stop_polling(self, sig: signal.Signals) -> None:
|
||||
if not self._running_lock.locked():
|
||||
return
|
||||
|
||||
loggers.dispatcher.warning("Received %s signal", sig.name)
|
||||
if not self._stop_signal:
|
||||
return
|
||||
self._stop_signal.set()
|
||||
|
||||
async def start_polling(
|
||||
self,
|
||||
*bots: Bot,
|
||||
polling_timeout: int = 10,
|
||||
handle_as_tasks: bool = True,
|
||||
backoff_config: BackoffConfig = DEFAULT_BACKOFF_CONFIG,
|
||||
allowed_updates: Optional[Union[List[str], UNSET_TYPE]] = UNSET,
|
||||
handle_signals: bool = True,
|
||||
close_bot_session: bool = True,
|
||||
tasks_concurrency_limit: Optional[int] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
Polling runner
|
||||
|
||||
:param bots: Bot instances (one or more)
|
||||
:param polling_timeout: Long-polling wait time
|
||||
:param handle_as_tasks: Run task for each event and no wait result
|
||||
:param backoff_config: backoff-retry config
|
||||
:param allowed_updates: List of the update types you want your bot to receive
|
||||
By default, all used update types are enabled (resolved from handlers)
|
||||
:param handle_signals: handle signals (SIGINT/SIGTERM)
|
||||
:param close_bot_session: close bot sessions on shutdown
|
||||
:param tasks_concurrency_limit: Maximum number of concurrent updates to process
|
||||
(None = no limit), used only if handle_as_tasks is True
|
||||
:param kwargs: contextual data
|
||||
:return:
|
||||
"""
|
||||
if not bots:
|
||||
raise ValueError("At least one bot instance is required to start polling")
|
||||
if "bot" in kwargs:
|
||||
raise ValueError(
|
||||
"Keyword argument 'bot' is not acceptable, "
|
||||
"the bot instance should be passed as positional argument"
|
||||
)
|
||||
|
||||
async with self._running_lock: # Prevent to run this method twice at a once
|
||||
if self._stop_signal is None:
|
||||
self._stop_signal = Event()
|
||||
if self._stopped_signal is None:
|
||||
self._stopped_signal = Event()
|
||||
|
||||
if allowed_updates is UNSET:
|
||||
allowed_updates = self.resolve_used_update_types()
|
||||
|
||||
self._stop_signal.clear()
|
||||
self._stopped_signal.clear()
|
||||
|
||||
if handle_signals:
|
||||
loop = asyncio.get_running_loop()
|
||||
with suppress(NotImplementedError): # pragma: no cover
|
||||
# Signals handling is not supported on Windows
|
||||
# It also can't be covered on Windows
|
||||
loop.add_signal_handler(
|
||||
signal.SIGTERM, self._signal_stop_polling, signal.SIGTERM
|
||||
)
|
||||
loop.add_signal_handler(
|
||||
signal.SIGINT, self._signal_stop_polling, signal.SIGINT
|
||||
)
|
||||
|
||||
workflow_data = {
|
||||
"dispatcher": self,
|
||||
"bots": bots,
|
||||
**self.workflow_data,
|
||||
**kwargs,
|
||||
}
|
||||
if "bot" in workflow_data:
|
||||
workflow_data.pop("bot")
|
||||
|
||||
await self.emit_startup(bot=bots[-1], **workflow_data)
|
||||
loggers.dispatcher.info("Start polling")
|
||||
try:
|
||||
tasks: List[asyncio.Task[Any]] = [
|
||||
asyncio.create_task(
|
||||
self._polling(
|
||||
bot=bot,
|
||||
handle_as_tasks=handle_as_tasks,
|
||||
polling_timeout=polling_timeout,
|
||||
backoff_config=backoff_config,
|
||||
allowed_updates=allowed_updates,
|
||||
tasks_concurrency_limit=tasks_concurrency_limit,
|
||||
**workflow_data,
|
||||
)
|
||||
)
|
||||
for bot in bots
|
||||
]
|
||||
tasks.append(asyncio.create_task(self._stop_signal.wait()))
|
||||
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
||||
|
||||
for task in pending:
|
||||
# (mostly) Graceful shutdown unfinished tasks
|
||||
task.cancel()
|
||||
with suppress(CancelledError):
|
||||
await task
|
||||
# Wait finished tasks to propagate unhandled exceptions
|
||||
await asyncio.gather(*done)
|
||||
|
||||
finally:
|
||||
loggers.dispatcher.info("Polling stopped")
|
||||
try:
|
||||
await self.emit_shutdown(bot=bots[-1], **workflow_data)
|
||||
finally:
|
||||
if close_bot_session:
|
||||
await asyncio.gather(*(bot.session.close() for bot in bots))
|
||||
self._stopped_signal.set()
|
||||
|
||||
def run_polling(
|
||||
self,
|
||||
*bots: Bot,
|
||||
polling_timeout: int = 10,
|
||||
handle_as_tasks: bool = True,
|
||||
backoff_config: BackoffConfig = DEFAULT_BACKOFF_CONFIG,
|
||||
allowed_updates: Optional[Union[List[str], UNSET_TYPE]] = UNSET,
|
||||
handle_signals: bool = True,
|
||||
close_bot_session: bool = True,
|
||||
tasks_concurrency_limit: Optional[int] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
Run many bots with polling
|
||||
|
||||
:param bots: Bot instances (one or more)
|
||||
:param polling_timeout: Long-polling wait time
|
||||
:param handle_as_tasks: Run task for each event and no wait result
|
||||
:param backoff_config: backoff-retry config
|
||||
:param allowed_updates: List of the update types you want your bot to receive
|
||||
:param handle_signals: handle signals (SIGINT/SIGTERM)
|
||||
:param close_bot_session: close bot sessions on shutdown
|
||||
:param tasks_concurrency_limit: Maximum number of concurrent updates to process
|
||||
(None = no limit), used only if handle_as_tasks is True
|
||||
:param kwargs: contextual data
|
||||
:return:
|
||||
"""
|
||||
with suppress(KeyboardInterrupt):
|
||||
return asyncio.run(
|
||||
self.start_polling(
|
||||
*bots,
|
||||
**kwargs,
|
||||
polling_timeout=polling_timeout,
|
||||
handle_as_tasks=handle_as_tasks,
|
||||
backoff_config=backoff_config,
|
||||
allowed_updates=allowed_updates,
|
||||
handle_signals=handle_signals,
|
||||
close_bot_session=close_bot_session,
|
||||
tasks_concurrency_limit=tasks_concurrency_limit,
|
||||
)
|
||||
)
|
35
myenv/Lib/site-packages/aiogram/dispatcher/event/bases.py
Normal file
35
myenv/Lib/site-packages/aiogram/dispatcher/event/bases.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Awaitable, Callable, Dict, NoReturn, Optional, TypeVar, Union
|
||||
from unittest.mock import sentinel
|
||||
|
||||
from ...types import TelegramObject
|
||||
from ..middlewares.base import BaseMiddleware
|
||||
|
||||
MiddlewareEventType = TypeVar("MiddlewareEventType", bound=TelegramObject)
|
||||
NextMiddlewareType = Callable[[MiddlewareEventType, Dict[str, Any]], Awaitable[Any]]
|
||||
MiddlewareType = Union[
|
||||
BaseMiddleware,
|
||||
Callable[
|
||||
[NextMiddlewareType[MiddlewareEventType], MiddlewareEventType, Dict[str, Any]],
|
||||
Awaitable[Any],
|
||||
],
|
||||
]
|
||||
|
||||
UNHANDLED = sentinel.UNHANDLED
|
||||
REJECTED = sentinel.REJECTED
|
||||
|
||||
|
||||
class SkipHandler(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class CancelHandler(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def skip(message: Optional[str] = None) -> NoReturn:
|
||||
"""
|
||||
Raise an SkipHandler
|
||||
"""
|
||||
raise SkipHandler(message or "Event skipped")
|
53
myenv/Lib/site-packages/aiogram/dispatcher/event/event.py
Normal file
53
myenv/Lib/site-packages/aiogram/dispatcher/event/event.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, List
|
||||
|
||||
from .handler import CallbackType, HandlerObject
|
||||
|
||||
|
||||
class EventObserver:
|
||||
"""
|
||||
Simple events observer
|
||||
|
||||
Is used for managing events is not related with Telegram
|
||||
(For example startup/shutdown processes)
|
||||
|
||||
Handlers can be registered via decorator or method
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
<observer>.register(my_handler)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@<observer>()
|
||||
async def my_handler(*args, **kwargs): ...
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.handlers: List[HandlerObject] = []
|
||||
|
||||
def register(self, callback: CallbackType) -> None:
|
||||
"""
|
||||
Register callback with filters
|
||||
"""
|
||||
self.handlers.append(HandlerObject(callback=callback))
|
||||
|
||||
async def trigger(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""
|
||||
Propagate event to handlers.
|
||||
Handler will be called when all its filters is pass.
|
||||
"""
|
||||
for handler in self.handlers:
|
||||
await handler.call(*args, **kwargs)
|
||||
|
||||
def __call__(self) -> Callable[[CallbackType], CallbackType]:
|
||||
"""
|
||||
Decorator for registering event handlers
|
||||
"""
|
||||
|
||||
def wrapper(callback: CallbackType) -> CallbackType:
|
||||
self.register(callback)
|
||||
return callback
|
||||
|
||||
return wrapper
|
95
myenv/Lib/site-packages/aiogram/dispatcher/event/handler.py
Normal file
95
myenv/Lib/site-packages/aiogram/dispatcher/event/handler.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import asyncio
|
||||
import contextvars
|
||||
import inspect
|
||||
import warnings
|
||||
from dataclasses import dataclass, field
|
||||
from functools import partial
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from magic_filter.magic import MagicFilter as OriginalMagicFilter
|
||||
|
||||
from aiogram.dispatcher.flags import extract_flags_from_object
|
||||
from aiogram.filters.base import Filter
|
||||
from aiogram.handlers import BaseHandler
|
||||
from aiogram.utils.magic_filter import MagicFilter
|
||||
from aiogram.utils.warnings import Recommendation
|
||||
|
||||
CallbackType = Callable[..., Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class CallableObject:
|
||||
callback: CallbackType
|
||||
awaitable: bool = field(init=False)
|
||||
params: Set[str] = field(init=False)
|
||||
varkw: bool = field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
callback = inspect.unwrap(self.callback)
|
||||
self.awaitable = inspect.isawaitable(callback) or inspect.iscoroutinefunction(callback)
|
||||
spec = inspect.getfullargspec(callback)
|
||||
self.params = {*spec.args, *spec.kwonlyargs}
|
||||
self.varkw = spec.varkw is not None
|
||||
|
||||
def _prepare_kwargs(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if self.varkw:
|
||||
return kwargs
|
||||
|
||||
return {k: kwargs[k] for k in self.params if k in kwargs}
|
||||
|
||||
async def call(self, *args: Any, **kwargs: Any) -> Any:
|
||||
wrapped = partial(self.callback, *args, **self._prepare_kwargs(kwargs))
|
||||
if self.awaitable:
|
||||
return await wrapped()
|
||||
return await asyncio.to_thread(wrapped)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FilterObject(CallableObject):
|
||||
magic: Optional[MagicFilter] = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.callback, OriginalMagicFilter):
|
||||
# MagicFilter instance is callable but generates
|
||||
# only "CallOperation" instead of applying the filter
|
||||
self.magic = self.callback
|
||||
self.callback = self.callback.resolve
|
||||
if not isinstance(self.magic, MagicFilter):
|
||||
# Issue: https://github.com/aiogram/aiogram/issues/990
|
||||
warnings.warn(
|
||||
category=Recommendation,
|
||||
message="You are using F provided by magic_filter package directly, "
|
||||
"but it lacks `.as_()` extension."
|
||||
"\n Please change the import statement: from `from magic_filter import F` "
|
||||
"to `from aiogram import F` to silence this warning.",
|
||||
stacklevel=6,
|
||||
)
|
||||
|
||||
super(FilterObject, self).__post_init__()
|
||||
|
||||
if isinstance(self.callback, Filter):
|
||||
self.awaitable = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class HandlerObject(CallableObject):
|
||||
filters: Optional[List[FilterObject]] = None
|
||||
flags: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
super(HandlerObject, self).__post_init__()
|
||||
callback = inspect.unwrap(self.callback)
|
||||
if inspect.isclass(callback) and issubclass(callback, BaseHandler):
|
||||
self.awaitable = True
|
||||
self.flags.update(extract_flags_from_object(callback))
|
||||
|
||||
async def check(self, *args: Any, **kwargs: Any) -> Tuple[bool, Dict[str, Any]]:
|
||||
if not self.filters:
|
||||
return True, kwargs
|
||||
for event_filter in self.filters:
|
||||
check = await event_filter.call(*args, **kwargs)
|
||||
if not check:
|
||||
return False, kwargs
|
||||
if isinstance(check, dict):
|
||||
kwargs.update(check)
|
||||
return True, kwargs
|
141
myenv/Lib/site-packages/aiogram/dispatcher/event/telegram.py
Normal file
141
myenv/Lib/site-packages/aiogram/dispatcher/event/telegram.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
|
||||
|
||||
from aiogram.dispatcher.middlewares.manager import MiddlewareManager
|
||||
|
||||
from ...exceptions import UnsupportedKeywordArgument
|
||||
from ...filters.base import Filter
|
||||
from ...types import TelegramObject
|
||||
from .bases import UNHANDLED, MiddlewareType, SkipHandler
|
||||
from .handler import CallbackType, FilterObject, HandlerObject
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.dispatcher.router import Router
|
||||
|
||||
|
||||
class TelegramEventObserver:
|
||||
"""
|
||||
Event observer for Telegram events
|
||||
|
||||
Here you can register handler with filter.
|
||||
This observer will stop event propagation when first handler is pass.
|
||||
"""
|
||||
|
||||
def __init__(self, router: Router, event_name: str) -> None:
|
||||
self.router: Router = router
|
||||
self.event_name: str = event_name
|
||||
|
||||
self.handlers: List[HandlerObject] = []
|
||||
|
||||
self.middleware = MiddlewareManager()
|
||||
self.outer_middleware = MiddlewareManager()
|
||||
|
||||
# Re-used filters check method from already implemented handler object
|
||||
# with dummy callback which never will be used
|
||||
self._handler = HandlerObject(callback=lambda: True, filters=[])
|
||||
|
||||
def filter(self, *filters: CallbackType) -> None:
|
||||
"""
|
||||
Register filter for all handlers of this event observer
|
||||
|
||||
:param filters: positional filters
|
||||
"""
|
||||
if self._handler.filters is None:
|
||||
self._handler.filters = []
|
||||
self._handler.filters.extend([FilterObject(filter_) for filter_ in filters])
|
||||
|
||||
def _resolve_middlewares(self) -> List[MiddlewareType[TelegramObject]]:
|
||||
middlewares: List[MiddlewareType[TelegramObject]] = []
|
||||
for router in reversed(tuple(self.router.chain_head)):
|
||||
observer = router.observers.get(self.event_name)
|
||||
if observer:
|
||||
middlewares.extend(observer.middleware)
|
||||
|
||||
return middlewares
|
||||
|
||||
def register(
|
||||
self,
|
||||
callback: CallbackType,
|
||||
*filters: CallbackType,
|
||||
flags: Optional[Dict[str, Any]] = None,
|
||||
**kwargs: Any,
|
||||
) -> CallbackType:
|
||||
"""
|
||||
Register event handler
|
||||
"""
|
||||
if kwargs:
|
||||
raise UnsupportedKeywordArgument(
|
||||
"Passing any additional keyword arguments to the registrar method "
|
||||
"is not supported.\n"
|
||||
"This error may be caused when you are trying to register filters like in 2.x "
|
||||
"version of this framework, if it's true just look at correspoding "
|
||||
"documentation pages.\n"
|
||||
f"Please remove the {set(kwargs.keys())} arguments from this call.\n"
|
||||
)
|
||||
|
||||
if flags is None:
|
||||
flags = {}
|
||||
|
||||
for item in filters:
|
||||
if isinstance(item, Filter):
|
||||
item.update_handler_flags(flags=flags)
|
||||
|
||||
self.handlers.append(
|
||||
HandlerObject(
|
||||
callback=callback,
|
||||
filters=[FilterObject(filter_) for filter_ in filters],
|
||||
flags=flags,
|
||||
)
|
||||
)
|
||||
|
||||
return callback
|
||||
|
||||
def wrap_outer_middleware(
|
||||
self, callback: Any, event: TelegramObject, data: Dict[str, Any]
|
||||
) -> Any:
|
||||
wrapped_outer = self.middleware.wrap_middlewares(
|
||||
self.outer_middleware,
|
||||
callback,
|
||||
)
|
||||
return wrapped_outer(event, data)
|
||||
|
||||
def check_root_filters(self, event: TelegramObject, **kwargs: Any) -> Any:
|
||||
return self._handler.check(event, **kwargs)
|
||||
|
||||
async def trigger(self, event: TelegramObject, **kwargs: Any) -> Any:
|
||||
"""
|
||||
Propagate event to handlers and stops propagation on first match.
|
||||
Handler will be called when all its filters are pass.
|
||||
"""
|
||||
for handler in self.handlers:
|
||||
kwargs["handler"] = handler
|
||||
result, data = await handler.check(event, **kwargs)
|
||||
if result:
|
||||
kwargs.update(data)
|
||||
try:
|
||||
wrapped_inner = self.outer_middleware.wrap_middlewares(
|
||||
self._resolve_middlewares(),
|
||||
handler.call,
|
||||
)
|
||||
return await wrapped_inner(event, kwargs)
|
||||
except SkipHandler:
|
||||
continue
|
||||
|
||||
return UNHANDLED
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
*filters: CallbackType,
|
||||
flags: Optional[Dict[str, Any]] = None,
|
||||
**kwargs: Any,
|
||||
) -> Callable[[CallbackType], CallbackType]:
|
||||
"""
|
||||
Decorator for registering event handlers
|
||||
"""
|
||||
|
||||
def wrapper(callback: CallbackType) -> CallbackType:
|
||||
self.register(callback, *filters, flags=flags, **kwargs)
|
||||
return callback
|
||||
|
||||
return wrapper
|
127
myenv/Lib/site-packages/aiogram/dispatcher/flags.py
Normal file
127
myenv/Lib/site-packages/aiogram/dispatcher/flags.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union, cast, overload
|
||||
|
||||
from magic_filter import AttrDict, MagicFilter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.dispatcher.event.handler import HandlerObject
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Flag:
|
||||
name: str
|
||||
value: Any
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FlagDecorator:
|
||||
flag: Flag
|
||||
|
||||
@classmethod
|
||||
def _with_flag(cls, flag: Flag) -> "FlagDecorator":
|
||||
return cls(flag)
|
||||
|
||||
def _with_value(self, value: Any) -> "FlagDecorator":
|
||||
new_flag = Flag(self.flag.name, value)
|
||||
return self._with_flag(new_flag)
|
||||
|
||||
@overload
|
||||
def __call__(self, value: Callable[..., Any], /) -> Callable[..., Any]: # type: ignore
|
||||
pass
|
||||
|
||||
@overload
|
||||
def __call__(self, value: Any, /) -> "FlagDecorator":
|
||||
pass
|
||||
|
||||
@overload
|
||||
def __call__(self, **kwargs: Any) -> "FlagDecorator":
|
||||
pass
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
value: Optional[Any] = None,
|
||||
**kwargs: Any,
|
||||
) -> Union[Callable[..., Any], "FlagDecorator"]:
|
||||
if value and kwargs:
|
||||
raise ValueError("The arguments `value` and **kwargs can not be used together")
|
||||
|
||||
if value is not None and callable(value):
|
||||
value.aiogram_flag = {
|
||||
**extract_flags_from_object(value),
|
||||
self.flag.name: self.flag.value,
|
||||
}
|
||||
return cast(Callable[..., Any], value)
|
||||
return self._with_value(AttrDict(kwargs) if value is None else value)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
class _ChatActionFlagProtocol(FlagDecorator):
|
||||
def __call__( # type: ignore[override]
|
||||
self,
|
||||
action: str = ...,
|
||||
interval: float = ...,
|
||||
initial_sleep: float = ...,
|
||||
**kwargs: Any,
|
||||
) -> FlagDecorator:
|
||||
pass
|
||||
|
||||
|
||||
class FlagGenerator:
|
||||
def __getattr__(self, name: str) -> FlagDecorator:
|
||||
if name[0] == "_":
|
||||
raise AttributeError("Flag name must NOT start with underscore")
|
||||
return FlagDecorator(Flag(name, True))
|
||||
|
||||
if TYPE_CHECKING:
|
||||
chat_action: _ChatActionFlagProtocol
|
||||
|
||||
|
||||
def extract_flags_from_object(obj: Any) -> Dict[str, Any]:
|
||||
if not hasattr(obj, "aiogram_flag"):
|
||||
return {}
|
||||
return cast(Dict[str, Any], obj.aiogram_flag)
|
||||
|
||||
|
||||
def extract_flags(handler: Union["HandlerObject", Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract flags from handler or middleware context data
|
||||
|
||||
:param handler: handler object or data
|
||||
:return: dictionary with all handler flags
|
||||
"""
|
||||
if isinstance(handler, dict) and "handler" in handler:
|
||||
handler = handler["handler"]
|
||||
if hasattr(handler, "flags"):
|
||||
return handler.flags
|
||||
return {}
|
||||
|
||||
|
||||
def get_flag(
|
||||
handler: Union["HandlerObject", Dict[str, Any]],
|
||||
name: str,
|
||||
*,
|
||||
default: Optional[Any] = None,
|
||||
) -> Any:
|
||||
"""
|
||||
Get flag by name
|
||||
|
||||
:param handler: handler object or data
|
||||
:param name: name of the flag
|
||||
:param default: default value (None)
|
||||
:return: value of the flag or default
|
||||
"""
|
||||
flags = extract_flags(handler)
|
||||
return flags.get(name, default)
|
||||
|
||||
|
||||
def check_flags(handler: Union["HandlerObject", Dict[str, Any]], magic: MagicFilter) -> Any:
|
||||
"""
|
||||
Check flags via magic filter
|
||||
|
||||
:param handler: handler object or data
|
||||
:param magic: instance of the magic
|
||||
:return: the result of magic filter check
|
||||
"""
|
||||
flags = extract_flags(handler)
|
||||
return magic.resolve(AttrDict(flags))
|
@@ -0,0 +1,29 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Awaitable, Callable, Dict, TypeVar
|
||||
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class BaseMiddleware(ABC):
|
||||
"""
|
||||
Generic middleware class
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any: # pragma: no cover
|
||||
"""
|
||||
Execute middleware
|
||||
|
||||
:param handler: Wrapped handler in middlewares chain
|
||||
:param event: Incoming event (Subclass of :class:`aiogram.types.base.TelegramObject`)
|
||||
:param data: Contextual data. Will be mapped to handler arguments
|
||||
:return: :class:`Any`
|
||||
"""
|
||||
pass
|
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, TypedDict
|
||||
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram import Bot, Dispatcher, Router
|
||||
from aiogram.dispatcher.event.handler import HandlerObject
|
||||
from aiogram.dispatcher.middlewares.user_context import EventContext
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.storage.base import BaseStorage
|
||||
from aiogram.types import Chat, Update, User
|
||||
from aiogram.utils.i18n import I18n, I18nMiddleware
|
||||
|
||||
|
||||
class DispatcherData(TypedDict, total=False):
|
||||
"""
|
||||
Dispatcher and bot related data.
|
||||
"""
|
||||
|
||||
dispatcher: Dispatcher
|
||||
"""Instance of the Dispatcher from which the handler was called."""
|
||||
bot: Bot
|
||||
"""Bot that received the update."""
|
||||
bots: NotRequired[list[Bot]]
|
||||
"""List of all bots in the Dispatcher. Used only in polling mode."""
|
||||
event_update: Update
|
||||
"""Update object that triggered the handler."""
|
||||
event_router: Router
|
||||
"""Router that was used to find the handler."""
|
||||
handler: NotRequired[HandlerObject]
|
||||
"""Handler object that was called.
|
||||
Available only in the handler itself and inner middlewares."""
|
||||
|
||||
|
||||
class UserContextData(TypedDict, total=False):
|
||||
"""
|
||||
Event context related data about user and chat.
|
||||
"""
|
||||
|
||||
event_context: EventContext
|
||||
"""Event context object that contains user and chat data."""
|
||||
event_from_user: NotRequired[User]
|
||||
"""User object that triggered the handler."""
|
||||
event_chat: NotRequired[Chat]
|
||||
"""Chat object that triggered the handler.
|
||||
.. deprecated:: 3.5.0
|
||||
Use :attr:`event_context.chat` instead."""
|
||||
event_thread_id: NotRequired[int]
|
||||
"""Thread ID of the chat that triggered the handler.
|
||||
.. deprecated:: 3.5.0
|
||||
Use :attr:`event_context.chat` instead."""
|
||||
event_business_connection_id: NotRequired[str]
|
||||
"""Business connection ID of the chat that triggered the handler.
|
||||
.. deprecated:: 3.5.0
|
||||
Use :attr:`event_context.business_connection_id` instead."""
|
||||
|
||||
|
||||
class FSMData(TypedDict, total=False):
|
||||
"""
|
||||
FSM related data.
|
||||
"""
|
||||
|
||||
fsm_storage: BaseStorage
|
||||
"""Storage used for FSM."""
|
||||
state: NotRequired[FSMContext]
|
||||
"""Current state of the FSM."""
|
||||
raw_state: NotRequired[str | None]
|
||||
"""Raw state of the FSM."""
|
||||
|
||||
|
||||
class I18nData(TypedDict, total=False):
|
||||
"""
|
||||
I18n related data.
|
||||
|
||||
Is not included by default, you need to add it to your own Data class if you need it.
|
||||
"""
|
||||
|
||||
i18n: I18n
|
||||
"""I18n object."""
|
||||
i18n_middleware: I18nMiddleware
|
||||
"""I18n middleware."""
|
||||
|
||||
|
||||
class MiddlewareData(
|
||||
DispatcherData,
|
||||
UserContextData,
|
||||
FSMData,
|
||||
# I18nData, # Disabled by default, add it if you need it to your own Data class.
|
||||
total=False,
|
||||
):
|
||||
"""
|
||||
Data passed to the handler by the middlewares.
|
||||
|
||||
You can add your own data by extending this class.
|
||||
"""
|
@@ -0,0 +1,36 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, cast
|
||||
|
||||
from ...types import TelegramObject, Update
|
||||
from ...types.error_event import ErrorEvent
|
||||
from ..event.bases import UNHANDLED, CancelHandler, SkipHandler
|
||||
from .base import BaseMiddleware
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..router import Router
|
||||
|
||||
|
||||
class ErrorsMiddleware(BaseMiddleware):
|
||||
def __init__(self, router: Router):
|
||||
self.router = router
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
try:
|
||||
return await handler(event, data)
|
||||
except (SkipHandler, CancelHandler): # pragma: no cover
|
||||
raise
|
||||
except Exception as e:
|
||||
response = await self.router.propagate_event(
|
||||
update_type="error",
|
||||
event=ErrorEvent(update=cast(Update, event), exception=e),
|
||||
**data,
|
||||
)
|
||||
if response is not UNHANDLED:
|
||||
return response
|
||||
raise
|
@@ -0,0 +1,65 @@
|
||||
import functools
|
||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Union, overload
|
||||
|
||||
from aiogram.dispatcher.event.bases import (
|
||||
MiddlewareEventType,
|
||||
MiddlewareType,
|
||||
NextMiddlewareType,
|
||||
)
|
||||
from aiogram.dispatcher.event.handler import CallbackType
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
||||
class MiddlewareManager(Sequence[MiddlewareType[TelegramObject]]):
|
||||
def __init__(self) -> None:
|
||||
self._middlewares: List[MiddlewareType[TelegramObject]] = []
|
||||
|
||||
def register(
|
||||
self,
|
||||
middleware: MiddlewareType[TelegramObject],
|
||||
) -> MiddlewareType[TelegramObject]:
|
||||
self._middlewares.append(middleware)
|
||||
return middleware
|
||||
|
||||
def unregister(self, middleware: MiddlewareType[TelegramObject]) -> None:
|
||||
self._middlewares.remove(middleware)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
middleware: Optional[MiddlewareType[TelegramObject]] = None,
|
||||
) -> Union[
|
||||
Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]],
|
||||
MiddlewareType[TelegramObject],
|
||||
]:
|
||||
if middleware is None:
|
||||
return self.register
|
||||
return self.register(middleware)
|
||||
|
||||
@overload
|
||||
def __getitem__(self, item: int) -> MiddlewareType[TelegramObject]:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def __getitem__(self, item: slice) -> Sequence[MiddlewareType[TelegramObject]]:
|
||||
pass
|
||||
|
||||
def __getitem__(
|
||||
self, item: Union[int, slice]
|
||||
) -> Union[MiddlewareType[TelegramObject], Sequence[MiddlewareType[TelegramObject]]]:
|
||||
return self._middlewares[item]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._middlewares)
|
||||
|
||||
@staticmethod
|
||||
def wrap_middlewares(
|
||||
middlewares: Sequence[MiddlewareType[MiddlewareEventType]], handler: CallbackType
|
||||
) -> NextMiddlewareType[MiddlewareEventType]:
|
||||
@functools.wraps(handler)
|
||||
def handler_wrapper(event: TelegramObject, kwargs: Dict[str, Any]) -> Any:
|
||||
return handler(event, **kwargs)
|
||||
|
||||
middleware = handler_wrapper
|
||||
for m in reversed(middlewares):
|
||||
middleware = functools.partial(m, middleware) # type: ignore[assignment]
|
||||
return middleware
|
@@ -0,0 +1,182 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Awaitable, Callable, Dict, Optional
|
||||
|
||||
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
||||
from aiogram.types import (
|
||||
Chat,
|
||||
ChatBoostSourcePremium,
|
||||
InaccessibleMessage,
|
||||
TelegramObject,
|
||||
Update,
|
||||
User,
|
||||
)
|
||||
|
||||
EVENT_CONTEXT_KEY = "event_context"
|
||||
|
||||
EVENT_FROM_USER_KEY = "event_from_user"
|
||||
EVENT_CHAT_KEY = "event_chat"
|
||||
EVENT_THREAD_ID_KEY = "event_thread_id"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EventContext:
|
||||
chat: Optional[Chat] = None
|
||||
user: Optional[User] = None
|
||||
thread_id: Optional[int] = None
|
||||
business_connection_id: Optional[str] = None
|
||||
|
||||
@property
|
||||
def user_id(self) -> Optional[int]:
|
||||
return self.user.id if self.user else None
|
||||
|
||||
@property
|
||||
def chat_id(self) -> Optional[int]:
|
||||
return self.chat.id if self.chat else None
|
||||
|
||||
|
||||
class UserContextMiddleware(BaseMiddleware):
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
if not isinstance(event, Update):
|
||||
raise RuntimeError("UserContextMiddleware got an unexpected event type!")
|
||||
event_context = data[EVENT_CONTEXT_KEY] = self.resolve_event_context(event=event)
|
||||
|
||||
# Backward compatibility
|
||||
if event_context.user is not None:
|
||||
data[EVENT_FROM_USER_KEY] = event_context.user
|
||||
if event_context.chat is not None:
|
||||
data[EVENT_CHAT_KEY] = event_context.chat
|
||||
if event_context.thread_id is not None:
|
||||
data[EVENT_THREAD_ID_KEY] = event_context.thread_id
|
||||
|
||||
return await handler(event, data)
|
||||
|
||||
@classmethod
|
||||
def resolve_event_context(cls, event: Update) -> EventContext:
|
||||
"""
|
||||
Resolve chat and user instance from Update object
|
||||
"""
|
||||
if event.message:
|
||||
return EventContext(
|
||||
chat=event.message.chat,
|
||||
user=event.message.from_user,
|
||||
thread_id=(
|
||||
event.message.message_thread_id if event.message.is_topic_message else None
|
||||
),
|
||||
)
|
||||
if event.edited_message:
|
||||
return EventContext(
|
||||
chat=event.edited_message.chat,
|
||||
user=event.edited_message.from_user,
|
||||
thread_id=(
|
||||
event.edited_message.message_thread_id
|
||||
if event.edited_message.is_topic_message
|
||||
else None
|
||||
),
|
||||
)
|
||||
if event.channel_post:
|
||||
return EventContext(chat=event.channel_post.chat)
|
||||
if event.edited_channel_post:
|
||||
return EventContext(chat=event.edited_channel_post.chat)
|
||||
if event.inline_query:
|
||||
return EventContext(user=event.inline_query.from_user)
|
||||
if event.chosen_inline_result:
|
||||
return EventContext(user=event.chosen_inline_result.from_user)
|
||||
if event.callback_query:
|
||||
callback_query_message = event.callback_query.message
|
||||
if callback_query_message:
|
||||
return EventContext(
|
||||
chat=callback_query_message.chat,
|
||||
user=event.callback_query.from_user,
|
||||
thread_id=(
|
||||
callback_query_message.message_thread_id
|
||||
if not isinstance(callback_query_message, InaccessibleMessage)
|
||||
and callback_query_message.is_topic_message
|
||||
else None
|
||||
),
|
||||
business_connection_id=(
|
||||
callback_query_message.business_connection_id
|
||||
if not isinstance(callback_query_message, InaccessibleMessage)
|
||||
else None
|
||||
),
|
||||
)
|
||||
return EventContext(user=event.callback_query.from_user)
|
||||
if event.shipping_query:
|
||||
return EventContext(user=event.shipping_query.from_user)
|
||||
if event.pre_checkout_query:
|
||||
return EventContext(user=event.pre_checkout_query.from_user)
|
||||
if event.poll_answer:
|
||||
return EventContext(
|
||||
chat=event.poll_answer.voter_chat,
|
||||
user=event.poll_answer.user,
|
||||
)
|
||||
if event.my_chat_member:
|
||||
return EventContext(
|
||||
chat=event.my_chat_member.chat, user=event.my_chat_member.from_user
|
||||
)
|
||||
if event.chat_member:
|
||||
return EventContext(chat=event.chat_member.chat, user=event.chat_member.from_user)
|
||||
if event.chat_join_request:
|
||||
return EventContext(
|
||||
chat=event.chat_join_request.chat, user=event.chat_join_request.from_user
|
||||
)
|
||||
if event.message_reaction:
|
||||
return EventContext(
|
||||
chat=event.message_reaction.chat,
|
||||
user=event.message_reaction.user,
|
||||
)
|
||||
if event.message_reaction_count:
|
||||
return EventContext(chat=event.message_reaction_count.chat)
|
||||
if event.chat_boost:
|
||||
# We only check the premium source, because only it has a sender user,
|
||||
# other sources have a user, but it is not the sender, but the recipient
|
||||
if isinstance(event.chat_boost.boost.source, ChatBoostSourcePremium):
|
||||
return EventContext(
|
||||
chat=event.chat_boost.chat,
|
||||
user=event.chat_boost.boost.source.user,
|
||||
)
|
||||
|
||||
return EventContext(chat=event.chat_boost.chat)
|
||||
if event.removed_chat_boost:
|
||||
return EventContext(chat=event.removed_chat_boost.chat)
|
||||
if event.deleted_business_messages:
|
||||
return EventContext(
|
||||
chat=event.deleted_business_messages.chat,
|
||||
business_connection_id=event.deleted_business_messages.business_connection_id,
|
||||
)
|
||||
if event.business_connection:
|
||||
return EventContext(
|
||||
user=event.business_connection.user,
|
||||
business_connection_id=event.business_connection.id,
|
||||
)
|
||||
if event.business_message:
|
||||
return EventContext(
|
||||
chat=event.business_message.chat,
|
||||
user=event.business_message.from_user,
|
||||
thread_id=(
|
||||
event.business_message.message_thread_id
|
||||
if event.business_message.is_topic_message
|
||||
else None
|
||||
),
|
||||
business_connection_id=event.business_message.business_connection_id,
|
||||
)
|
||||
if event.edited_business_message:
|
||||
return EventContext(
|
||||
chat=event.edited_business_message.chat,
|
||||
user=event.edited_business_message.from_user,
|
||||
thread_id=(
|
||||
event.edited_business_message.message_thread_id
|
||||
if event.edited_business_message.is_topic_message
|
||||
else None
|
||||
),
|
||||
business_connection_id=event.edited_business_message.business_connection_id,
|
||||
)
|
||||
if event.purchased_paid_media:
|
||||
return EventContext(
|
||||
user=event.purchased_paid_media.from_user,
|
||||
)
|
||||
return EventContext()
|
275
myenv/Lib/site-packages/aiogram/dispatcher/router.py
Normal file
275
myenv/Lib/site-packages/aiogram/dispatcher/router.py
Normal file
@@ -0,0 +1,275 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Final, Generator, List, Optional, Set
|
||||
|
||||
from ..types import TelegramObject
|
||||
from .event.bases import REJECTED, UNHANDLED
|
||||
from .event.event import EventObserver
|
||||
from .event.telegram import TelegramEventObserver
|
||||
|
||||
INTERNAL_UPDATE_TYPES: Final[frozenset[str]] = frozenset({"update", "error"})
|
||||
|
||||
|
||||
class Router:
|
||||
"""
|
||||
Router can route update, and it nested update types like messages, callback query,
|
||||
polls and all other event types.
|
||||
|
||||
Event handlers can be registered in observer by two ways:
|
||||
|
||||
- By observer method - :obj:`router.<event_type>.register(handler, <filters, ...>)`
|
||||
- By decorator - :obj:`@router.<event_type>(<filters, ...>)`
|
||||
"""
|
||||
|
||||
def __init__(self, *, name: Optional[str] = None) -> None:
|
||||
"""
|
||||
:param name: Optional router name, can be useful for debugging
|
||||
"""
|
||||
|
||||
self.name = name or hex(id(self))
|
||||
|
||||
self._parent_router: Optional[Router] = None
|
||||
self.sub_routers: List[Router] = []
|
||||
|
||||
# Observers
|
||||
self.message = TelegramEventObserver(router=self, event_name="message")
|
||||
self.edited_message = TelegramEventObserver(router=self, event_name="edited_message")
|
||||
self.channel_post = TelegramEventObserver(router=self, event_name="channel_post")
|
||||
self.edited_channel_post = TelegramEventObserver(
|
||||
router=self, event_name="edited_channel_post"
|
||||
)
|
||||
self.inline_query = TelegramEventObserver(router=self, event_name="inline_query")
|
||||
self.chosen_inline_result = TelegramEventObserver(
|
||||
router=self, event_name="chosen_inline_result"
|
||||
)
|
||||
self.callback_query = TelegramEventObserver(router=self, event_name="callback_query")
|
||||
self.shipping_query = TelegramEventObserver(router=self, event_name="shipping_query")
|
||||
self.pre_checkout_query = TelegramEventObserver(
|
||||
router=self, event_name="pre_checkout_query"
|
||||
)
|
||||
self.poll = TelegramEventObserver(router=self, event_name="poll")
|
||||
self.poll_answer = TelegramEventObserver(router=self, event_name="poll_answer")
|
||||
self.my_chat_member = TelegramEventObserver(router=self, event_name="my_chat_member")
|
||||
self.chat_member = TelegramEventObserver(router=self, event_name="chat_member")
|
||||
self.chat_join_request = TelegramEventObserver(router=self, event_name="chat_join_request")
|
||||
self.message_reaction = TelegramEventObserver(router=self, event_name="message_reaction")
|
||||
self.message_reaction_count = TelegramEventObserver(
|
||||
router=self, event_name="message_reaction_count"
|
||||
)
|
||||
self.chat_boost = TelegramEventObserver(router=self, event_name="chat_boost")
|
||||
self.removed_chat_boost = TelegramEventObserver(
|
||||
router=self, event_name="removed_chat_boost"
|
||||
)
|
||||
self.deleted_business_messages = TelegramEventObserver(
|
||||
router=self, event_name="deleted_business_messages"
|
||||
)
|
||||
self.business_connection = TelegramEventObserver(
|
||||
router=self, event_name="business_connection"
|
||||
)
|
||||
self.edited_business_message = TelegramEventObserver(
|
||||
router=self, event_name="edited_business_message"
|
||||
)
|
||||
self.business_message = TelegramEventObserver(router=self, event_name="business_message")
|
||||
self.purchased_paid_media = TelegramEventObserver(
|
||||
router=self, event_name="purchased_paid_media"
|
||||
)
|
||||
|
||||
self.errors = self.error = TelegramEventObserver(router=self, event_name="error")
|
||||
|
||||
self.startup = EventObserver()
|
||||
self.shutdown = EventObserver()
|
||||
|
||||
self.observers: Dict[str, TelegramEventObserver] = {
|
||||
"message": self.message,
|
||||
"edited_message": self.edited_message,
|
||||
"channel_post": self.channel_post,
|
||||
"edited_channel_post": self.edited_channel_post,
|
||||
"inline_query": self.inline_query,
|
||||
"chosen_inline_result": self.chosen_inline_result,
|
||||
"callback_query": self.callback_query,
|
||||
"shipping_query": self.shipping_query,
|
||||
"pre_checkout_query": self.pre_checkout_query,
|
||||
"poll": self.poll,
|
||||
"poll_answer": self.poll_answer,
|
||||
"my_chat_member": self.my_chat_member,
|
||||
"chat_member": self.chat_member,
|
||||
"chat_join_request": self.chat_join_request,
|
||||
"message_reaction": self.message_reaction,
|
||||
"message_reaction_count": self.message_reaction_count,
|
||||
"chat_boost": self.chat_boost,
|
||||
"removed_chat_boost": self.removed_chat_boost,
|
||||
"deleted_business_messages": self.deleted_business_messages,
|
||||
"business_connection": self.business_connection,
|
||||
"edited_business_message": self.edited_business_message,
|
||||
"business_message": self.business_message,
|
||||
"purchased_paid_media": self.purchased_paid_media,
|
||||
"error": self.errors,
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{type(self).__name__} {self.name!r}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self}>"
|
||||
|
||||
def resolve_used_update_types(self, skip_events: Optional[Set[str]] = None) -> List[str]:
|
||||
"""
|
||||
Resolve registered event names
|
||||
|
||||
Is useful for getting updates only for registered event types.
|
||||
|
||||
:param skip_events: skip specified event names
|
||||
:return: set of registered names
|
||||
"""
|
||||
handlers_in_use: Set[str] = set()
|
||||
if skip_events is None:
|
||||
skip_events = set()
|
||||
skip_events = {*skip_events, *INTERNAL_UPDATE_TYPES}
|
||||
|
||||
for router in self.chain_tail:
|
||||
for update_name, observer in router.observers.items():
|
||||
if observer.handlers and update_name not in skip_events:
|
||||
handlers_in_use.add(update_name)
|
||||
|
||||
return list(sorted(handlers_in_use)) # NOQA: C413
|
||||
|
||||
async def propagate_event(self, update_type: str, event: TelegramObject, **kwargs: Any) -> Any:
|
||||
kwargs.update(event_router=self)
|
||||
observer = self.observers.get(update_type)
|
||||
|
||||
async def _wrapped(telegram_event: TelegramObject, **data: Any) -> Any:
|
||||
return await self._propagate_event(
|
||||
observer=observer, update_type=update_type, event=telegram_event, **data
|
||||
)
|
||||
|
||||
if observer:
|
||||
return await observer.wrap_outer_middleware(_wrapped, event=event, data=kwargs)
|
||||
return await _wrapped(event, **kwargs)
|
||||
|
||||
async def _propagate_event(
|
||||
self,
|
||||
observer: Optional[TelegramEventObserver],
|
||||
update_type: str,
|
||||
event: TelegramObject,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
response = UNHANDLED
|
||||
if observer:
|
||||
# Check globally defined filters before any other handler will be checked.
|
||||
# This check is placed here instead of `trigger` method to add possibility
|
||||
# to pass context to handlers from global filters.
|
||||
result, data = await observer.check_root_filters(event, **kwargs)
|
||||
if not result:
|
||||
return UNHANDLED
|
||||
kwargs.update(data)
|
||||
|
||||
response = await observer.trigger(event, **kwargs)
|
||||
if response is REJECTED: # pragma: no cover
|
||||
# Possible only if some handler returns REJECTED
|
||||
return UNHANDLED
|
||||
if response is not UNHANDLED:
|
||||
return response
|
||||
|
||||
for router in self.sub_routers:
|
||||
response = await router.propagate_event(update_type=update_type, event=event, **kwargs)
|
||||
if response is not UNHANDLED:
|
||||
break
|
||||
|
||||
return response
|
||||
|
||||
@property
|
||||
def chain_head(self) -> Generator[Router, None, None]:
|
||||
router: Optional[Router] = self
|
||||
while router:
|
||||
yield router
|
||||
router = router.parent_router
|
||||
|
||||
@property
|
||||
def chain_tail(self) -> Generator[Router, None, None]:
|
||||
yield self
|
||||
for router in self.sub_routers:
|
||||
yield from router.chain_tail
|
||||
|
||||
@property
|
||||
def parent_router(self) -> Optional[Router]:
|
||||
return self._parent_router
|
||||
|
||||
@parent_router.setter
|
||||
def parent_router(self, router: Router) -> None:
|
||||
"""
|
||||
Internal property setter of parent router fot this router.
|
||||
Do not use this method in own code.
|
||||
All routers should be included via `include_router` method.
|
||||
|
||||
Self- and circular- referencing are not allowed here
|
||||
|
||||
:param router:
|
||||
"""
|
||||
if not isinstance(router, Router):
|
||||
raise ValueError(f"router should be instance of Router not {type(router).__name__!r}")
|
||||
if self._parent_router:
|
||||
raise RuntimeError(f"Router is already attached to {self._parent_router!r}")
|
||||
if self == router:
|
||||
raise RuntimeError("Self-referencing routers is not allowed")
|
||||
|
||||
parent: Optional[Router] = router
|
||||
while parent is not None:
|
||||
if parent == self:
|
||||
raise RuntimeError("Circular referencing of Router is not allowed")
|
||||
|
||||
parent = parent.parent_router
|
||||
|
||||
self._parent_router = router
|
||||
router.sub_routers.append(self)
|
||||
|
||||
def include_routers(self, *routers: Router) -> None:
|
||||
"""
|
||||
Attach multiple routers.
|
||||
|
||||
:param routers:
|
||||
:return:
|
||||
"""
|
||||
if not routers:
|
||||
raise ValueError("At least one router must be provided")
|
||||
for router in routers:
|
||||
self.include_router(router)
|
||||
|
||||
def include_router(self, router: Router) -> Router:
|
||||
"""
|
||||
Attach another router.
|
||||
|
||||
:param router:
|
||||
:return:
|
||||
"""
|
||||
if not isinstance(router, Router):
|
||||
raise ValueError(
|
||||
f"router should be instance of Router not {type(router).__class__.__name__}"
|
||||
)
|
||||
router.parent_router = self
|
||||
return router
|
||||
|
||||
async def emit_startup(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""
|
||||
Recursively call startup callbacks
|
||||
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
kwargs.update(router=self)
|
||||
await self.startup.trigger(*args, **kwargs)
|
||||
for router in self.sub_routers:
|
||||
await router.emit_startup(*args, **kwargs)
|
||||
|
||||
async def emit_shutdown(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""
|
||||
Recursively call shutdown callbacks to graceful shutdown
|
||||
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
kwargs.update(router=self)
|
||||
await self.shutdown.trigger(*args, **kwargs)
|
||||
for router in self.sub_routers:
|
||||
await router.emit_shutdown(*args, **kwargs)
|
71
myenv/Lib/site-packages/aiogram/enums/__init__.py
Normal file
71
myenv/Lib/site-packages/aiogram/enums/__init__.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from .bot_command_scope_type import BotCommandScopeType
|
||||
from .chat_action import ChatAction
|
||||
from .chat_boost_source_type import ChatBoostSourceType
|
||||
from .chat_member_status import ChatMemberStatus
|
||||
from .chat_type import ChatType
|
||||
from .content_type import ContentType
|
||||
from .currency import Currency
|
||||
from .dice_emoji import DiceEmoji
|
||||
from .encrypted_passport_element import EncryptedPassportElement
|
||||
from .inline_query_result_type import InlineQueryResultType
|
||||
from .input_media_type import InputMediaType
|
||||
from .input_paid_media_type import InputPaidMediaType
|
||||
from .input_profile_photo_type import InputProfilePhotoType
|
||||
from .input_story_content_type import InputStoryContentType
|
||||
from .keyboard_button_poll_type_type import KeyboardButtonPollTypeType
|
||||
from .mask_position_point import MaskPositionPoint
|
||||
from .menu_button_type import MenuButtonType
|
||||
from .message_entity_type import MessageEntityType
|
||||
from .message_origin_type import MessageOriginType
|
||||
from .owned_gift_type import OwnedGiftType
|
||||
from .paid_media_type import PaidMediaType
|
||||
from .parse_mode import ParseMode
|
||||
from .passport_element_error_type import PassportElementErrorType
|
||||
from .poll_type import PollType
|
||||
from .reaction_type_type import ReactionTypeType
|
||||
from .revenue_withdrawal_state_type import RevenueWithdrawalStateType
|
||||
from .sticker_format import StickerFormat
|
||||
from .sticker_type import StickerType
|
||||
from .story_area_type_type import StoryAreaTypeType
|
||||
from .topic_icon_color import TopicIconColor
|
||||
from .transaction_partner_type import TransactionPartnerType
|
||||
from .transaction_partner_user_transaction_type_enum import (
|
||||
TransactionPartnerUserTransactionTypeEnum,
|
||||
)
|
||||
from .update_type import UpdateType
|
||||
|
||||
__all__ = (
|
||||
"BotCommandScopeType",
|
||||
"ChatAction",
|
||||
"ChatBoostSourceType",
|
||||
"ChatMemberStatus",
|
||||
"ChatType",
|
||||
"ContentType",
|
||||
"Currency",
|
||||
"DiceEmoji",
|
||||
"EncryptedPassportElement",
|
||||
"InlineQueryResultType",
|
||||
"InputMediaType",
|
||||
"InputPaidMediaType",
|
||||
"InputProfilePhotoType",
|
||||
"InputStoryContentType",
|
||||
"KeyboardButtonPollTypeType",
|
||||
"MaskPositionPoint",
|
||||
"MenuButtonType",
|
||||
"MessageEntityType",
|
||||
"MessageOriginType",
|
||||
"OwnedGiftType",
|
||||
"PaidMediaType",
|
||||
"ParseMode",
|
||||
"PassportElementErrorType",
|
||||
"PollType",
|
||||
"ReactionTypeType",
|
||||
"RevenueWithdrawalStateType",
|
||||
"StickerFormat",
|
||||
"StickerType",
|
||||
"StoryAreaTypeType",
|
||||
"TopicIconColor",
|
||||
"TransactionPartnerType",
|
||||
"TransactionPartnerUserTransactionTypeEnum",
|
||||
"UpdateType",
|
||||
)
|
@@ -0,0 +1,17 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class BotCommandScopeType(str, Enum):
|
||||
"""
|
||||
This object represents the scope to which bot commands are applied.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#botcommandscope
|
||||
"""
|
||||
|
||||
DEFAULT = "default"
|
||||
ALL_PRIVATE_CHATS = "all_private_chats"
|
||||
ALL_GROUP_CHATS = "all_group_chats"
|
||||
ALL_CHAT_ADMINISTRATORS = "all_chat_administrators"
|
||||
CHAT = "chat"
|
||||
CHAT_ADMINISTRATORS = "chat_administrators"
|
||||
CHAT_MEMBER = "chat_member"
|
32
myenv/Lib/site-packages/aiogram/enums/chat_action.py
Normal file
32
myenv/Lib/site-packages/aiogram/enums/chat_action.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ChatAction(str, Enum):
|
||||
"""
|
||||
This object represents bot actions.
|
||||
|
||||
Choose one, depending on what the user is about to receive:
|
||||
|
||||
- typing for text messages,
|
||||
- upload_photo for photos,
|
||||
- record_video or upload_video for videos,
|
||||
- record_voice or upload_voice for voice notes,
|
||||
- upload_document for general files,
|
||||
- choose_sticker for stickers,
|
||||
- find_location for location data,
|
||||
- record_video_note or upload_video_note for video notes.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#sendchataction
|
||||
"""
|
||||
|
||||
TYPING = "typing"
|
||||
UPLOAD_PHOTO = "upload_photo"
|
||||
RECORD_VIDEO = "record_video"
|
||||
UPLOAD_VIDEO = "upload_video"
|
||||
RECORD_VOICE = "record_voice"
|
||||
UPLOAD_VOICE = "upload_voice"
|
||||
UPLOAD_DOCUMENT = "upload_document"
|
||||
CHOOSE_STICKER = "choose_sticker"
|
||||
FIND_LOCATION = "find_location"
|
||||
RECORD_VIDEO_NOTE = "record_video_note"
|
||||
UPLOAD_VIDEO_NOTE = "upload_video_note"
|
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ChatBoostSourceType(str, Enum):
|
||||
"""
|
||||
This object represents a type of chat boost source.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#chatboostsource
|
||||
"""
|
||||
|
||||
PREMIUM = "premium"
|
||||
GIFT_CODE = "gift_code"
|
||||
GIVEAWAY = "giveaway"
|
16
myenv/Lib/site-packages/aiogram/enums/chat_member_status.py
Normal file
16
myenv/Lib/site-packages/aiogram/enums/chat_member_status.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ChatMemberStatus(str, Enum):
|
||||
"""
|
||||
This object represents chat member status.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#chatmember
|
||||
"""
|
||||
|
||||
CREATOR = "creator"
|
||||
ADMINISTRATOR = "administrator"
|
||||
MEMBER = "member"
|
||||
RESTRICTED = "restricted"
|
||||
LEFT = "left"
|
||||
KICKED = "kicked"
|
15
myenv/Lib/site-packages/aiogram/enums/chat_type.py
Normal file
15
myenv/Lib/site-packages/aiogram/enums/chat_type.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ChatType(str, Enum):
|
||||
"""
|
||||
This object represents a chat type
|
||||
|
||||
Source: https://core.telegram.org/bots/api#chat
|
||||
"""
|
||||
|
||||
SENDER = "sender"
|
||||
PRIVATE = "private"
|
||||
GROUP = "group"
|
||||
SUPERGROUP = "supergroup"
|
||||
CHANNEL = "channel"
|
69
myenv/Lib/site-packages/aiogram/enums/content_type.py
Normal file
69
myenv/Lib/site-packages/aiogram/enums/content_type.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ContentType(str, Enum):
|
||||
"""
|
||||
This object represents a type of content in message
|
||||
"""
|
||||
|
||||
UNKNOWN = "unknown"
|
||||
ANY = "any"
|
||||
TEXT = "text"
|
||||
ANIMATION = "animation"
|
||||
AUDIO = "audio"
|
||||
DOCUMENT = "document"
|
||||
PAID_MEDIA = "paid_media"
|
||||
PHOTO = "photo"
|
||||
STICKER = "sticker"
|
||||
STORY = "story"
|
||||
VIDEO = "video"
|
||||
VIDEO_NOTE = "video_note"
|
||||
VOICE = "voice"
|
||||
CONTACT = "contact"
|
||||
DICE = "dice"
|
||||
GAME = "game"
|
||||
POLL = "poll"
|
||||
VENUE = "venue"
|
||||
LOCATION = "location"
|
||||
NEW_CHAT_MEMBERS = "new_chat_members"
|
||||
LEFT_CHAT_MEMBER = "left_chat_member"
|
||||
NEW_CHAT_TITLE = "new_chat_title"
|
||||
NEW_CHAT_PHOTO = "new_chat_photo"
|
||||
DELETE_CHAT_PHOTO = "delete_chat_photo"
|
||||
GROUP_CHAT_CREATED = "group_chat_created"
|
||||
SUPERGROUP_CHAT_CREATED = "supergroup_chat_created"
|
||||
CHANNEL_CHAT_CREATED = "channel_chat_created"
|
||||
MESSAGE_AUTO_DELETE_TIMER_CHANGED = "message_auto_delete_timer_changed"
|
||||
MIGRATE_TO_CHAT_ID = "migrate_to_chat_id"
|
||||
MIGRATE_FROM_CHAT_ID = "migrate_from_chat_id"
|
||||
PINNED_MESSAGE = "pinned_message"
|
||||
INVOICE = "invoice"
|
||||
SUCCESSFUL_PAYMENT = "successful_payment"
|
||||
REFUNDED_PAYMENT = "refunded_payment"
|
||||
USERS_SHARED = "users_shared"
|
||||
CHAT_SHARED = "chat_shared"
|
||||
GIFT = "gift"
|
||||
UNIQUE_GIFT = "unique_gift"
|
||||
CONNECTED_WEBSITE = "connected_website"
|
||||
WRITE_ACCESS_ALLOWED = "write_access_allowed"
|
||||
PASSPORT_DATA = "passport_data"
|
||||
PROXIMITY_ALERT_TRIGGERED = "proximity_alert_triggered"
|
||||
BOOST_ADDED = "boost_added"
|
||||
CHAT_BACKGROUND_SET = "chat_background_set"
|
||||
FORUM_TOPIC_CREATED = "forum_topic_created"
|
||||
FORUM_TOPIC_EDITED = "forum_topic_edited"
|
||||
FORUM_TOPIC_CLOSED = "forum_topic_closed"
|
||||
FORUM_TOPIC_REOPENED = "forum_topic_reopened"
|
||||
GENERAL_FORUM_TOPIC_HIDDEN = "general_forum_topic_hidden"
|
||||
GENERAL_FORUM_TOPIC_UNHIDDEN = "general_forum_topic_unhidden"
|
||||
GIVEAWAY_CREATED = "giveaway_created"
|
||||
GIVEAWAY = "giveaway"
|
||||
GIVEAWAY_WINNERS = "giveaway_winners"
|
||||
GIVEAWAY_COMPLETED = "giveaway_completed"
|
||||
PAID_MESSAGE_PRICE_CHANGED = "paid_message_price_changed"
|
||||
VIDEO_CHAT_SCHEDULED = "video_chat_scheduled"
|
||||
VIDEO_CHAT_STARTED = "video_chat_started"
|
||||
VIDEO_CHAT_ENDED = "video_chat_ended"
|
||||
VIDEO_CHAT_PARTICIPANTS_INVITED = "video_chat_participants_invited"
|
||||
WEB_APP_DATA = "web_app_data"
|
||||
USER_SHARED = "user_shared"
|
96
myenv/Lib/site-packages/aiogram/enums/currency.py
Normal file
96
myenv/Lib/site-packages/aiogram/enums/currency.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Currency(str, Enum):
|
||||
"""
|
||||
Currencies supported by Telegram Bot API
|
||||
|
||||
Source: https://core.telegram.org/bots/payments#supported-currencies
|
||||
"""
|
||||
|
||||
AED = "AED"
|
||||
AFN = "AFN"
|
||||
ALL = "ALL"
|
||||
AMD = "AMD"
|
||||
ARS = "ARS"
|
||||
AUD = "AUD"
|
||||
AZN = "AZN"
|
||||
BAM = "BAM"
|
||||
BDT = "BDT"
|
||||
BGN = "BGN"
|
||||
BND = "BND"
|
||||
BOB = "BOB"
|
||||
BRL = "BRL"
|
||||
BYN = "BYN"
|
||||
CAD = "CAD"
|
||||
CHF = "CHF"
|
||||
CLP = "CLP"
|
||||
CNY = "CNY"
|
||||
COP = "COP"
|
||||
CRC = "CRC"
|
||||
CZK = "CZK"
|
||||
DKK = "DKK"
|
||||
DOP = "DOP"
|
||||
DZD = "DZD"
|
||||
EGP = "EGP"
|
||||
ETB = "ETB"
|
||||
EUR = "EUR"
|
||||
GBP = "GBP"
|
||||
GEL = "GEL"
|
||||
GTQ = "GTQ"
|
||||
HKD = "HKD"
|
||||
HNL = "HNL"
|
||||
HRK = "HRK"
|
||||
HUF = "HUF"
|
||||
IDR = "IDR"
|
||||
ILS = "ILS"
|
||||
INR = "INR"
|
||||
ISK = "ISK"
|
||||
JMD = "JMD"
|
||||
JPY = "JPY"
|
||||
KES = "KES"
|
||||
KGS = "KGS"
|
||||
KRW = "KRW"
|
||||
KZT = "KZT"
|
||||
LBP = "LBP"
|
||||
LKR = "LKR"
|
||||
MAD = "MAD"
|
||||
MDL = "MDL"
|
||||
MNT = "MNT"
|
||||
MUR = "MUR"
|
||||
MVR = "MVR"
|
||||
MXN = "MXN"
|
||||
MYR = "MYR"
|
||||
MZN = "MZN"
|
||||
NGN = "NGN"
|
||||
NIO = "NIO"
|
||||
NOK = "NOK"
|
||||
NPR = "NPR"
|
||||
NZD = "NZD"
|
||||
PAB = "PAB"
|
||||
PEN = "PEN"
|
||||
PHP = "PHP"
|
||||
PKR = "PKR"
|
||||
PLN = "PLN"
|
||||
PYG = "PYG"
|
||||
QAR = "QAR"
|
||||
RON = "RON"
|
||||
RSD = "RSD"
|
||||
RUB = "RUB"
|
||||
SAR = "SAR"
|
||||
SEK = "SEK"
|
||||
SGD = "SGD"
|
||||
THB = "THB"
|
||||
TJS = "TJS"
|
||||
TRY = "TRY"
|
||||
TTD = "TTD"
|
||||
TWD = "TWD"
|
||||
TZS = "TZS"
|
||||
UAH = "UAH"
|
||||
UGX = "UGX"
|
||||
USD = "USD"
|
||||
UYU = "UYU"
|
||||
UZS = "UZS"
|
||||
VND = "VND"
|
||||
YER = "YER"
|
||||
ZAR = "ZAR"
|
16
myenv/Lib/site-packages/aiogram/enums/dice_emoji.py
Normal file
16
myenv/Lib/site-packages/aiogram/enums/dice_emoji.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class DiceEmoji(str, Enum):
|
||||
"""
|
||||
Emoji on which the dice throw animation is based
|
||||
|
||||
Source: https://core.telegram.org/bots/api#dice
|
||||
"""
|
||||
|
||||
DICE = "🎲"
|
||||
DART = "🎯"
|
||||
BASKETBALL = "🏀"
|
||||
FOOTBALL = "⚽"
|
||||
SLOT_MACHINE = "🎰"
|
||||
BOWLING = "🎳"
|
@@ -0,0 +1,23 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class EncryptedPassportElement(str, Enum):
|
||||
"""
|
||||
This object represents type of encrypted passport element.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#encryptedpassportelement
|
||||
"""
|
||||
|
||||
PERSONAL_DETAILS = "personal_details"
|
||||
PASSPORT = "passport"
|
||||
DRIVER_LICENSE = "driver_license"
|
||||
IDENTITY_CARD = "identity_card"
|
||||
INTERNAL_PASSPORT = "internal_passport"
|
||||
ADDRESS = "address"
|
||||
UTILITY_BILL = "utility_bill"
|
||||
BANK_STATEMENT = "bank_statement"
|
||||
RENTAL_AGREEMENT = "rental_agreement"
|
||||
PASSPORT_REGISTRATION = "passport_registration"
|
||||
TEMPORARY_REGISTRATION = "temporary_registration"
|
||||
PHONE_NUMBER = "phone_number"
|
||||
EMAIL = "email"
|
@@ -0,0 +1,23 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class InlineQueryResultType(str, Enum):
|
||||
"""
|
||||
Type of inline query result
|
||||
|
||||
Source: https://core.telegram.org/bots/api#inlinequeryresult
|
||||
"""
|
||||
|
||||
AUDIO = "audio"
|
||||
DOCUMENT = "document"
|
||||
GIF = "gif"
|
||||
MPEG4_GIF = "mpeg4_gif"
|
||||
PHOTO = "photo"
|
||||
STICKER = "sticker"
|
||||
VIDEO = "video"
|
||||
VOICE = "voice"
|
||||
ARTICLE = "article"
|
||||
CONTACT = "contact"
|
||||
GAME = "game"
|
||||
LOCATION = "location"
|
||||
VENUE = "venue"
|
15
myenv/Lib/site-packages/aiogram/enums/input_media_type.py
Normal file
15
myenv/Lib/site-packages/aiogram/enums/input_media_type.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class InputMediaType(str, Enum):
|
||||
"""
|
||||
This object represents input media type
|
||||
|
||||
Source: https://core.telegram.org/bots/api#inputmedia
|
||||
"""
|
||||
|
||||
ANIMATION = "animation"
|
||||
AUDIO = "audio"
|
||||
DOCUMENT = "document"
|
||||
PHOTO = "photo"
|
||||
VIDEO = "video"
|
@@ -0,0 +1,12 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class InputPaidMediaType(str, Enum):
|
||||
"""
|
||||
This object represents the type of a media in a paid message.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#inputpaidmedia
|
||||
"""
|
||||
|
||||
PHOTO = "photo"
|
||||
VIDEO = "video"
|
@@ -0,0 +1,12 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class InputProfilePhotoType(str, Enum):
|
||||
"""
|
||||
This object represents input profile photo type
|
||||
|
||||
Source: https://core.telegram.org/bots/api#inputprofilephoto
|
||||
"""
|
||||
|
||||
STATIC = "static"
|
||||
ANIMATED = "animated"
|
@@ -0,0 +1,12 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class InputStoryContentType(str, Enum):
|
||||
"""
|
||||
This object represents input story content photo type.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#inputstorycontentphoto
|
||||
"""
|
||||
|
||||
PHOTO = "photo"
|
||||
VIDEO = "video"
|
@@ -0,0 +1,12 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class KeyboardButtonPollTypeType(str, Enum):
|
||||
"""
|
||||
This object represents type of a poll, which is allowed to be created and sent when the corresponding button is pressed.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#keyboardbuttonpolltype
|
||||
"""
|
||||
|
||||
QUIZ = "quiz"
|
||||
REGULAR = "regular"
|
14
myenv/Lib/site-packages/aiogram/enums/mask_position_point.py
Normal file
14
myenv/Lib/site-packages/aiogram/enums/mask_position_point.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class MaskPositionPoint(str, Enum):
|
||||
"""
|
||||
The part of the face relative to which the mask should be placed.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#maskposition
|
||||
"""
|
||||
|
||||
FOREHEAD = "forehead"
|
||||
EYES = "eyes"
|
||||
MOUTH = "mouth"
|
||||
CHIN = "chin"
|
13
myenv/Lib/site-packages/aiogram/enums/menu_button_type.py
Normal file
13
myenv/Lib/site-packages/aiogram/enums/menu_button_type.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class MenuButtonType(str, Enum):
|
||||
"""
|
||||
This object represents an type of Menu button
|
||||
|
||||
Source: https://core.telegram.org/bots/api#menubuttondefault
|
||||
"""
|
||||
|
||||
DEFAULT = "default"
|
||||
COMMANDS = "commands"
|
||||
WEB_APP = "web_app"
|
29
myenv/Lib/site-packages/aiogram/enums/message_entity_type.py
Normal file
29
myenv/Lib/site-packages/aiogram/enums/message_entity_type.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class MessageEntityType(str, Enum):
|
||||
"""
|
||||
This object represents type of message entity
|
||||
|
||||
Source: https://core.telegram.org/bots/api#messageentity
|
||||
"""
|
||||
|
||||
MENTION = "mention"
|
||||
HASHTAG = "hashtag"
|
||||
CASHTAG = "cashtag"
|
||||
BOT_COMMAND = "bot_command"
|
||||
URL = "url"
|
||||
EMAIL = "email"
|
||||
PHONE_NUMBER = "phone_number"
|
||||
BOLD = "bold"
|
||||
ITALIC = "italic"
|
||||
UNDERLINE = "underline"
|
||||
STRIKETHROUGH = "strikethrough"
|
||||
SPOILER = "spoiler"
|
||||
BLOCKQUOTE = "blockquote"
|
||||
EXPANDABLE_BLOCKQUOTE = "expandable_blockquote"
|
||||
CODE = "code"
|
||||
PRE = "pre"
|
||||
TEXT_LINK = "text_link"
|
||||
TEXT_MENTION = "text_mention"
|
||||
CUSTOM_EMOJI = "custom_emoji"
|
14
myenv/Lib/site-packages/aiogram/enums/message_origin_type.py
Normal file
14
myenv/Lib/site-packages/aiogram/enums/message_origin_type.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class MessageOriginType(str, Enum):
|
||||
"""
|
||||
This object represents origin of a message.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#messageorigin
|
||||
"""
|
||||
|
||||
USER = "user"
|
||||
HIDDEN_USER = "hidden_user"
|
||||
CHAT = "chat"
|
||||
CHANNEL = "channel"
|
12
myenv/Lib/site-packages/aiogram/enums/owned_gift_type.py
Normal file
12
myenv/Lib/site-packages/aiogram/enums/owned_gift_type.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class OwnedGiftType(str, Enum):
|
||||
"""
|
||||
This object represents owned gift type
|
||||
|
||||
Source: https://core.telegram.org/bots/api#ownedgift
|
||||
"""
|
||||
|
||||
REGULAR = "regular"
|
||||
UNIQUE = "unique"
|
13
myenv/Lib/site-packages/aiogram/enums/paid_media_type.py
Normal file
13
myenv/Lib/site-packages/aiogram/enums/paid_media_type.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class PaidMediaType(str, Enum):
|
||||
"""
|
||||
This object represents the type of a media in a paid message.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#paidmedia
|
||||
"""
|
||||
|
||||
PHOTO = "photo"
|
||||
PREVIEW = "preview"
|
||||
VIDEO = "video"
|
13
myenv/Lib/site-packages/aiogram/enums/parse_mode.py
Normal file
13
myenv/Lib/site-packages/aiogram/enums/parse_mode.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ParseMode(str, Enum):
|
||||
"""
|
||||
Formatting options
|
||||
|
||||
Source: https://core.telegram.org/bots/api#formatting-options
|
||||
"""
|
||||
|
||||
MARKDOWN_V2 = "MarkdownV2"
|
||||
MARKDOWN = "Markdown"
|
||||
HTML = "HTML"
|
@@ -0,0 +1,19 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class PassportElementErrorType(str, Enum):
|
||||
"""
|
||||
This object represents a passport element error type.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#passportelementerror
|
||||
"""
|
||||
|
||||
DATA = "data"
|
||||
FRONT_SIDE = "front_side"
|
||||
REVERSE_SIDE = "reverse_side"
|
||||
SELFIE = "selfie"
|
||||
FILE = "file"
|
||||
FILES = "files"
|
||||
TRANSLATION_FILE = "translation_file"
|
||||
TRANSLATION_FILES = "translation_files"
|
||||
UNSPECIFIED = "unspecified"
|
12
myenv/Lib/site-packages/aiogram/enums/poll_type.py
Normal file
12
myenv/Lib/site-packages/aiogram/enums/poll_type.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class PollType(str, Enum):
|
||||
"""
|
||||
This object represents poll type
|
||||
|
||||
Source: https://core.telegram.org/bots/api#poll
|
||||
"""
|
||||
|
||||
REGULAR = "regular"
|
||||
QUIZ = "quiz"
|
13
myenv/Lib/site-packages/aiogram/enums/reaction_type_type.py
Normal file
13
myenv/Lib/site-packages/aiogram/enums/reaction_type_type.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ReactionTypeType(str, Enum):
|
||||
"""
|
||||
This object represents reaction type.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#reactiontype
|
||||
"""
|
||||
|
||||
EMOJI = "emoji"
|
||||
CUSTOM_EMOJI = "custom_emoji"
|
||||
PAID = "paid"
|
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class RevenueWithdrawalStateType(str, Enum):
|
||||
"""
|
||||
This object represents a revenue withdrawal state type
|
||||
|
||||
Source: https://core.telegram.org/bots/api#revenuewithdrawalstate
|
||||
"""
|
||||
|
||||
FAILED = "failed"
|
||||
PENDING = "pending"
|
||||
SUCCEEDED = "succeeded"
|
13
myenv/Lib/site-packages/aiogram/enums/sticker_format.py
Normal file
13
myenv/Lib/site-packages/aiogram/enums/sticker_format.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class StickerFormat(str, Enum):
|
||||
"""
|
||||
Format of the sticker
|
||||
|
||||
Source: https://core.telegram.org/bots/api#createnewstickerset
|
||||
"""
|
||||
|
||||
STATIC = "static"
|
||||
ANIMATED = "animated"
|
||||
VIDEO = "video"
|
13
myenv/Lib/site-packages/aiogram/enums/sticker_type.py
Normal file
13
myenv/Lib/site-packages/aiogram/enums/sticker_type.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class StickerType(str, Enum):
|
||||
"""
|
||||
The part of the face relative to which the mask should be placed.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#maskposition
|
||||
"""
|
||||
|
||||
REGULAR = "regular"
|
||||
MASK = "mask"
|
||||
CUSTOM_EMOJI = "custom_emoji"
|
@@ -0,0 +1,15 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class StoryAreaTypeType(str, Enum):
|
||||
"""
|
||||
This object represents input profile photo type
|
||||
|
||||
Source: https://core.telegram.org/bots/api#storyareatype
|
||||
"""
|
||||
|
||||
LOCATION = "location"
|
||||
SUGGESTED_REACTION = "suggested_reaction"
|
||||
LINK = "link"
|
||||
WEATHER = "weather"
|
||||
UNIQUE_GIFT = "unique_gift"
|
16
myenv/Lib/site-packages/aiogram/enums/topic_icon_color.py
Normal file
16
myenv/Lib/site-packages/aiogram/enums/topic_icon_color.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TopicIconColor(int, Enum):
|
||||
"""
|
||||
Color of the topic icon in RGB format.
|
||||
|
||||
Source: https://github.com/telegramdesktop/tdesktop/blob/991fe491c5ae62705d77aa8fdd44a79caf639c45/Telegram/SourceFiles/data/data_forum_topic.cpp#L51-L56
|
||||
"""
|
||||
|
||||
BLUE = 0x6FB9F0
|
||||
YELLOW = 0xFFD67E
|
||||
VIOLET = 0xCB86DB
|
||||
GREEN = 0x8EEE98
|
||||
ROSE = 0xFF93B2
|
||||
RED = 0xFB6F5F
|
@@ -0,0 +1,17 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TransactionPartnerType(str, Enum):
|
||||
"""
|
||||
This object represents a type of transaction partner.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#transactionpartner
|
||||
"""
|
||||
|
||||
FRAGMENT = "fragment"
|
||||
OTHER = "other"
|
||||
USER = "user"
|
||||
TELEGRAM_ADS = "telegram_ads"
|
||||
TELEGRAM_API = "telegram_api"
|
||||
AFFILIATE_PROGRAM = "affiliate_program"
|
||||
CHAT = "chat"
|
@@ -0,0 +1,15 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TransactionPartnerUserTransactionTypeEnum(str, Enum):
|
||||
"""
|
||||
This object represents type of the transaction that were made by partner user.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#transactionpartneruser
|
||||
"""
|
||||
|
||||
INVOICE_PAYMENT = "invoice_payment"
|
||||
PAID_MEDIA_PAYMENT = "paid_media_payment"
|
||||
GIFT_PURCHASE = "gift_purchase"
|
||||
PREMIUM_PURCHASE = "premium_purchase"
|
||||
BUSINESS_ACCOUNT_TRANSFER = "business_account_transfer"
|
33
myenv/Lib/site-packages/aiogram/enums/update_type.py
Normal file
33
myenv/Lib/site-packages/aiogram/enums/update_type.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class UpdateType(str, Enum):
|
||||
"""
|
||||
This object represents the complete list of allowed update types
|
||||
|
||||
Source: https://core.telegram.org/bots/api#update
|
||||
"""
|
||||
|
||||
MESSAGE = "message"
|
||||
EDITED_MESSAGE = "edited_message"
|
||||
CHANNEL_POST = "channel_post"
|
||||
EDITED_CHANNEL_POST = "edited_channel_post"
|
||||
BUSINESS_CONNECTION = "business_connection"
|
||||
BUSINESS_MESSAGE = "business_message"
|
||||
EDITED_BUSINESS_MESSAGE = "edited_business_message"
|
||||
DELETED_BUSINESS_MESSAGES = "deleted_business_messages"
|
||||
MESSAGE_REACTION = "message_reaction"
|
||||
MESSAGE_REACTION_COUNT = "message_reaction_count"
|
||||
INLINE_QUERY = "inline_query"
|
||||
CHOSEN_INLINE_RESULT = "chosen_inline_result"
|
||||
CALLBACK_QUERY = "callback_query"
|
||||
SHIPPING_QUERY = "shipping_query"
|
||||
PRE_CHECKOUT_QUERY = "pre_checkout_query"
|
||||
PURCHASED_PAID_MEDIA = "purchased_paid_media"
|
||||
POLL = "poll"
|
||||
POLL_ANSWER = "poll_answer"
|
||||
MY_CHAT_MEMBER = "my_chat_member"
|
||||
CHAT_MEMBER = "chat_member"
|
||||
CHAT_JOIN_REQUEST = "chat_join_request"
|
||||
CHAT_BOOST = "chat_boost"
|
||||
REMOVED_CHAT_BOOST = "removed_chat_boost"
|
199
myenv/Lib/site-packages/aiogram/exceptions.py
Normal file
199
myenv/Lib/site-packages/aiogram/exceptions.py
Normal file
@@ -0,0 +1,199 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
from aiogram.methods import TelegramMethod
|
||||
from aiogram.methods.base import TelegramType
|
||||
from aiogram.utils.link import docs_url
|
||||
|
||||
|
||||
class AiogramError(Exception):
|
||||
"""
|
||||
Base exception for all aiogram errors.
|
||||
"""
|
||||
|
||||
|
||||
class DetailedAiogramError(AiogramError):
|
||||
"""
|
||||
Base exception for all aiogram errors with detailed message.
|
||||
"""
|
||||
|
||||
url: Optional[str] = None
|
||||
|
||||
def __init__(self, message: str) -> None:
|
||||
self.message = message
|
||||
|
||||
def __str__(self) -> str:
|
||||
message = self.message
|
||||
if self.url:
|
||||
message += f"\n(background on this error at: {self.url})"
|
||||
return message
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{type(self).__name__}('{self}')"
|
||||
|
||||
|
||||
class CallbackAnswerException(AiogramError):
|
||||
"""
|
||||
Exception for callback answer.
|
||||
"""
|
||||
|
||||
|
||||
class SceneException(AiogramError):
|
||||
"""
|
||||
Exception for scenes.
|
||||
"""
|
||||
|
||||
|
||||
class UnsupportedKeywordArgument(DetailedAiogramError):
|
||||
"""
|
||||
Exception raised when a keyword argument is passed as filter.
|
||||
"""
|
||||
|
||||
url = docs_url("migration_2_to_3.html", fragment_="filtering-events")
|
||||
|
||||
|
||||
class TelegramAPIError(DetailedAiogramError):
|
||||
"""
|
||||
Base exception for all Telegram API errors.
|
||||
"""
|
||||
|
||||
label: str = "Telegram server says"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
method: TelegramMethod[TelegramType],
|
||||
message: str,
|
||||
) -> None:
|
||||
super().__init__(message=message)
|
||||
self.method = method
|
||||
|
||||
def __str__(self) -> str:
|
||||
original_message = super().__str__()
|
||||
return f"{self.label} - {original_message}"
|
||||
|
||||
|
||||
class TelegramNetworkError(TelegramAPIError):
|
||||
"""
|
||||
Base exception for all Telegram network errors.
|
||||
"""
|
||||
|
||||
label = "HTTP Client says"
|
||||
|
||||
|
||||
class TelegramRetryAfter(TelegramAPIError):
|
||||
"""
|
||||
Exception raised when flood control exceeds.
|
||||
"""
|
||||
|
||||
url = "https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
method: TelegramMethod[TelegramType],
|
||||
message: str,
|
||||
retry_after: int,
|
||||
) -> None:
|
||||
description = f"Flood control exceeded on method {type(method).__name__!r}"
|
||||
if chat_id := getattr(method, "chat_id", None):
|
||||
description += f" in chat {chat_id}"
|
||||
description += f". Retry in {retry_after} seconds."
|
||||
description += f"\nOriginal description: {message}"
|
||||
|
||||
super().__init__(method=method, message=description)
|
||||
self.retry_after = retry_after
|
||||
|
||||
|
||||
class TelegramMigrateToChat(TelegramAPIError):
|
||||
"""
|
||||
Exception raised when chat has been migrated to a supergroup.
|
||||
"""
|
||||
|
||||
url = "https://core.telegram.org/bots/api#responseparameters"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
method: TelegramMethod[TelegramType],
|
||||
message: str,
|
||||
migrate_to_chat_id: int,
|
||||
) -> None:
|
||||
description = f"The group has been migrated to a supergroup with id {migrate_to_chat_id}"
|
||||
if chat_id := getattr(method, "chat_id", None):
|
||||
description += f" from {chat_id}"
|
||||
description += f"\nOriginal description: {message}"
|
||||
super().__init__(method=method, message=message)
|
||||
self.migrate_to_chat_id = migrate_to_chat_id
|
||||
|
||||
|
||||
class TelegramBadRequest(TelegramAPIError):
|
||||
"""
|
||||
Exception raised when request is malformed.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramNotFound(TelegramAPIError):
|
||||
"""
|
||||
Exception raised when chat, message, user, etc. not found.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramConflictError(TelegramAPIError):
|
||||
"""
|
||||
Exception raised when bot token is already used by another application in polling mode.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramUnauthorizedError(TelegramAPIError):
|
||||
"""
|
||||
Exception raised when bot token is invalid.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramForbiddenError(TelegramAPIError):
|
||||
"""
|
||||
Exception raised when bot is kicked from chat or etc.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramServerError(TelegramAPIError):
|
||||
"""
|
||||
Exception raised when Telegram server returns 5xx error.
|
||||
"""
|
||||
|
||||
|
||||
class RestartingTelegram(TelegramServerError):
|
||||
"""
|
||||
Exception raised when Telegram server is restarting.
|
||||
|
||||
It seems like this error is not used by Telegram anymore,
|
||||
but it's still here for backward compatibility.
|
||||
|
||||
Currently, you should expect that Telegram can raise RetryAfter (with timeout 5 seconds)
|
||||
error instead of this one.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramEntityTooLarge(TelegramNetworkError):
|
||||
"""
|
||||
Exception raised when you are trying to send a file that is too large.
|
||||
"""
|
||||
|
||||
url = "https://core.telegram.org/bots/api#sending-files"
|
||||
|
||||
|
||||
class ClientDecodeError(AiogramError):
|
||||
"""
|
||||
Exception raised when client can't decode response. (Malformed response, etc.)
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, original: Exception, data: Any) -> None:
|
||||
self.message = message
|
||||
self.original = original
|
||||
self.data = data
|
||||
|
||||
def __str__(self) -> str:
|
||||
original_type = type(self.original)
|
||||
return (
|
||||
f"{self.message}\n"
|
||||
f"Caused from error: "
|
||||
f"{original_type.__module__}.{original_type.__name__}: {self.original}\n"
|
||||
f"Content: {self.data}"
|
||||
)
|
51
myenv/Lib/site-packages/aiogram/filters/__init__.py
Normal file
51
myenv/Lib/site-packages/aiogram/filters/__init__.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from .base import Filter
|
||||
from .chat_member_updated import (
|
||||
ADMINISTRATOR,
|
||||
CREATOR,
|
||||
IS_ADMIN,
|
||||
IS_MEMBER,
|
||||
IS_NOT_MEMBER,
|
||||
JOIN_TRANSITION,
|
||||
KICKED,
|
||||
LEAVE_TRANSITION,
|
||||
LEFT,
|
||||
MEMBER,
|
||||
PROMOTED_TRANSITION,
|
||||
RESTRICTED,
|
||||
ChatMemberUpdatedFilter,
|
||||
)
|
||||
from .command import Command, CommandObject, CommandStart
|
||||
from .exception import ExceptionMessageFilter, ExceptionTypeFilter
|
||||
from .logic import and_f, invert_f, or_f
|
||||
from .magic_data import MagicData
|
||||
from .state import StateFilter
|
||||
|
||||
BaseFilter = Filter
|
||||
|
||||
__all__ = (
|
||||
"Filter",
|
||||
"BaseFilter",
|
||||
"Command",
|
||||
"CommandObject",
|
||||
"CommandStart",
|
||||
"ExceptionMessageFilter",
|
||||
"ExceptionTypeFilter",
|
||||
"StateFilter",
|
||||
"MagicData",
|
||||
"ChatMemberUpdatedFilter",
|
||||
"CREATOR",
|
||||
"ADMINISTRATOR",
|
||||
"MEMBER",
|
||||
"RESTRICTED",
|
||||
"LEFT",
|
||||
"KICKED",
|
||||
"IS_MEMBER",
|
||||
"IS_ADMIN",
|
||||
"PROMOTED_TRANSITION",
|
||||
"IS_NOT_MEMBER",
|
||||
"JOIN_TRANSITION",
|
||||
"LEAVE_TRANSITION",
|
||||
"and_f",
|
||||
"or_f",
|
||||
"invert_f",
|
||||
)
|
55
myenv/Lib/site-packages/aiogram/filters/base.py
Normal file
55
myenv/Lib/site-packages/aiogram/filters/base.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.filters.logic import _InvertFilter
|
||||
|
||||
|
||||
class Filter(ABC):
|
||||
"""
|
||||
If you want to register own filters like builtin filters you will need to write subclass
|
||||
of this class with overriding the :code:`__call__`
|
||||
method and adding filter attributes.
|
||||
"""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# This checking type-hint is needed because mypy checks validity of overrides and raises:
|
||||
# error: Signature of "__call__" incompatible with supertype "BaseFilter" [override]
|
||||
# https://mypy.readthedocs.io/en/latest/error_code_list.html#check-validity-of-overrides-override
|
||||
__call__: Callable[..., Awaitable[Union[bool, Dict[str, Any]]]]
|
||||
else: # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]:
|
||||
"""
|
||||
This method should be overridden.
|
||||
|
||||
Accepts incoming event and should return boolean or dict.
|
||||
|
||||
:return: :class:`bool` or :class:`Dict[str, Any]`
|
||||
"""
|
||||
pass
|
||||
|
||||
def __invert__(self) -> "_InvertFilter":
|
||||
from aiogram.filters.logic import invert_f
|
||||
|
||||
return invert_f(self)
|
||||
|
||||
def update_handler_flags(self, flags: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Also if you want to extend handler flags with using this filter
|
||||
you should implement this method
|
||||
|
||||
:param flags: existing flags, can be updated directly
|
||||
"""
|
||||
pass
|
||||
|
||||
def _signature_to_string(self, *args: Any, **kwargs: Any) -> str:
|
||||
items = [repr(arg) for arg in args]
|
||||
items.extend([f"{k}={v!r}" for k, v in kwargs.items() if v is not None])
|
||||
|
||||
return f"{type(self).__name__}({', '.join(items)})"
|
||||
|
||||
def __await__(self): # type: ignore # pragma: no cover
|
||||
# Is needed only for inspection and this method is never be called
|
||||
return self.__call__
|
208
myenv/Lib/site-packages/aiogram/filters/callback_data.py
Normal file
208
myenv/Lib/site-packages/aiogram/filters/callback_data.py
Normal file
@@ -0,0 +1,208 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import types
|
||||
import typing
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
from fractions import Fraction
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
ClassVar,
|
||||
Dict,
|
||||
Literal,
|
||||
Optional,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
from magic_filter import MagicFilter
|
||||
from pydantic import BaseModel
|
||||
from pydantic.fields import FieldInfo
|
||||
from pydantic_core import PydanticUndefined
|
||||
|
||||
from aiogram.filters.base import Filter
|
||||
from aiogram.types import CallbackQuery
|
||||
|
||||
T = TypeVar("T", bound="CallbackData")
|
||||
|
||||
MAX_CALLBACK_LENGTH: int = 64
|
||||
|
||||
|
||||
_UNION_TYPES = {typing.Union}
|
||||
if sys.version_info >= (3, 10): # pragma: no cover
|
||||
_UNION_TYPES.add(types.UnionType)
|
||||
|
||||
|
||||
class CallbackDataException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class CallbackData(BaseModel):
|
||||
"""
|
||||
Base class for callback data wrapper
|
||||
|
||||
This class should be used as super-class of user-defined callbacks.
|
||||
|
||||
The class-keyword :code:`prefix` is required to define prefix
|
||||
and also the argument :code:`sep` can be passed to define separator (default is :code:`:`).
|
||||
"""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
__separator__: ClassVar[str]
|
||||
"""Data separator (default is :code:`:`)"""
|
||||
__prefix__: ClassVar[str]
|
||||
"""Callback prefix"""
|
||||
|
||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||
if "prefix" not in kwargs:
|
||||
raise ValueError(
|
||||
f"prefix required, usage example: "
|
||||
f"`class {cls.__name__}(CallbackData, prefix='my_callback'): ...`"
|
||||
)
|
||||
cls.__separator__ = kwargs.pop("sep", ":")
|
||||
cls.__prefix__ = kwargs.pop("prefix")
|
||||
if cls.__separator__ in cls.__prefix__:
|
||||
raise ValueError(
|
||||
f"Separator symbol {cls.__separator__!r} can not be used "
|
||||
f"inside prefix {cls.__prefix__!r}"
|
||||
)
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
def _encode_value(self, key: str, value: Any) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
if isinstance(value, Enum):
|
||||
return str(value.value)
|
||||
if isinstance(value, UUID):
|
||||
return value.hex
|
||||
if isinstance(value, bool):
|
||||
return str(int(value))
|
||||
if isinstance(value, (int, str, float, Decimal, Fraction)):
|
||||
return str(value)
|
||||
raise ValueError(
|
||||
f"Attribute {key}={value!r} of type {type(value).__name__!r}"
|
||||
f" can not be packed to callback data"
|
||||
)
|
||||
|
||||
def pack(self) -> str:
|
||||
"""
|
||||
Generate callback data string
|
||||
|
||||
:return: valid callback data for Telegram Bot API
|
||||
"""
|
||||
result = [self.__prefix__]
|
||||
for key, value in self.model_dump(mode="python").items():
|
||||
encoded = self._encode_value(key, value)
|
||||
if self.__separator__ in encoded:
|
||||
raise ValueError(
|
||||
f"Separator symbol {self.__separator__!r} can not be used "
|
||||
f"in value {key}={encoded!r}"
|
||||
)
|
||||
result.append(encoded)
|
||||
callback_data = self.__separator__.join(result)
|
||||
if len(callback_data.encode()) > MAX_CALLBACK_LENGTH:
|
||||
raise ValueError(
|
||||
f"Resulted callback data is too long! "
|
||||
f"len({callback_data!r}.encode()) > {MAX_CALLBACK_LENGTH}"
|
||||
)
|
||||
return callback_data
|
||||
|
||||
@classmethod
|
||||
def unpack(cls: Type[T], value: str) -> T:
|
||||
"""
|
||||
Parse callback data string
|
||||
|
||||
:param value: value from Telegram
|
||||
:return: instance of CallbackData
|
||||
"""
|
||||
prefix, *parts = value.split(cls.__separator__)
|
||||
names = cls.model_fields.keys()
|
||||
if len(parts) != len(names):
|
||||
raise TypeError(
|
||||
f"Callback data {cls.__name__!r} takes {len(names)} arguments "
|
||||
f"but {len(parts)} were given"
|
||||
)
|
||||
if prefix != cls.__prefix__:
|
||||
raise ValueError(f"Bad prefix ({prefix!r} != {cls.__prefix__!r})")
|
||||
payload = {}
|
||||
for k, v in zip(names, parts): # type: str, Optional[str]
|
||||
if field := cls.model_fields.get(k):
|
||||
if v == "" and _check_field_is_nullable(field) and field.default != "":
|
||||
v = field.default if field.default is not PydanticUndefined else None
|
||||
payload[k] = v
|
||||
return cls(**payload)
|
||||
|
||||
@classmethod
|
||||
def filter(cls, rule: Optional[MagicFilter] = None) -> CallbackQueryFilter:
|
||||
"""
|
||||
Generates a filter for callback query with rule
|
||||
|
||||
:param rule: magic rule
|
||||
:return: instance of filter
|
||||
"""
|
||||
return CallbackQueryFilter(callback_data=cls, rule=rule)
|
||||
|
||||
|
||||
class CallbackQueryFilter(Filter):
|
||||
"""
|
||||
This filter helps to handle callback query.
|
||||
|
||||
Should not be used directly, you should create the instance of this filter
|
||||
via callback data instance
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"callback_data",
|
||||
"rule",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
callback_data: Type[CallbackData],
|
||||
rule: Optional[MagicFilter] = None,
|
||||
):
|
||||
"""
|
||||
:param callback_data: Expected type of callback data
|
||||
:param rule: Magic rule
|
||||
"""
|
||||
self.callback_data = callback_data
|
||||
self.rule = rule
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._signature_to_string(
|
||||
callback_data=self.callback_data,
|
||||
rule=self.rule,
|
||||
)
|
||||
|
||||
async def __call__(self, query: CallbackQuery) -> Union[Literal[False], Dict[str, Any]]:
|
||||
if not isinstance(query, CallbackQuery) or not query.data:
|
||||
return False
|
||||
try:
|
||||
callback_data = self.callback_data.unpack(query.data)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
if self.rule is None or self.rule.resolve(callback_data):
|
||||
return {"callback_data": callback_data}
|
||||
return False
|
||||
|
||||
|
||||
def _check_field_is_nullable(field: FieldInfo) -> bool:
|
||||
"""
|
||||
Check if the given field is nullable.
|
||||
|
||||
:param field: The FieldInfo object representing the field to check.
|
||||
:return: True if the field is nullable, False otherwise.
|
||||
|
||||
"""
|
||||
if not field.is_required():
|
||||
return True
|
||||
|
||||
return typing.get_origin(field.annotation) in _UNION_TYPES and type(None) in typing.get_args(
|
||||
field.annotation
|
||||
)
|
204
myenv/Lib/site-packages/aiogram/filters/chat_member_updated.py
Normal file
204
myenv/Lib/site-packages/aiogram/filters/chat_member_updated.py
Normal file
@@ -0,0 +1,204 @@
|
||||
from typing import Any, Dict, Optional, TypeVar, Union
|
||||
|
||||
from aiogram.filters.base import Filter
|
||||
from aiogram.types import ChatMember, ChatMemberUpdated
|
||||
|
||||
MarkerT = TypeVar("MarkerT", bound="_MemberStatusMarker")
|
||||
MarkerGroupT = TypeVar("MarkerGroupT", bound="_MemberStatusGroupMarker")
|
||||
TransitionT = TypeVar("TransitionT", bound="_MemberStatusTransition")
|
||||
|
||||
|
||||
class _MemberStatusMarker:
|
||||
__slots__ = (
|
||||
"name",
|
||||
"is_member",
|
||||
)
|
||||
|
||||
def __init__(self, name: str, *, is_member: Optional[bool] = None) -> None:
|
||||
self.name = name
|
||||
self.is_member = is_member
|
||||
|
||||
def __str__(self) -> str:
|
||||
result = self.name.upper()
|
||||
if self.is_member is not None:
|
||||
result = ("+" if self.is_member else "-") + result
|
||||
return result # noqa: RET504
|
||||
|
||||
def __pos__(self: MarkerT) -> MarkerT:
|
||||
return type(self)(name=self.name, is_member=True)
|
||||
|
||||
def __neg__(self: MarkerT) -> MarkerT:
|
||||
return type(self)(name=self.name, is_member=False)
|
||||
|
||||
def __or__(
|
||||
self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||
) -> "_MemberStatusGroupMarker":
|
||||
if isinstance(other, _MemberStatusMarker):
|
||||
return _MemberStatusGroupMarker(self, other)
|
||||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return other | self
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for |: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
__ror__ = __or__
|
||||
|
||||
def __rshift__(
|
||||
self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||
) -> "_MemberStatusTransition":
|
||||
old = _MemberStatusGroupMarker(self)
|
||||
if isinstance(other, _MemberStatusMarker):
|
||||
return _MemberStatusTransition(old=old, new=_MemberStatusGroupMarker(other))
|
||||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return _MemberStatusTransition(old=old, new=other)
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for >>: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
def __lshift__(
|
||||
self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||
) -> "_MemberStatusTransition":
|
||||
new = _MemberStatusGroupMarker(self)
|
||||
if isinstance(other, _MemberStatusMarker):
|
||||
return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=new)
|
||||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return _MemberStatusTransition(old=other, new=new)
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for <<: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.name, self.is_member))
|
||||
|
||||
def check(self, *, member: ChatMember) -> bool:
|
||||
# Not all member types have `is_member` attribute
|
||||
is_member = getattr(member, "is_member", None)
|
||||
status = getattr(member, "status", None)
|
||||
if self.is_member is not None and is_member != self.is_member:
|
||||
return False
|
||||
return self.name == status
|
||||
|
||||
|
||||
class _MemberStatusGroupMarker:
|
||||
__slots__ = ("statuses",)
|
||||
|
||||
def __init__(self, *statuses: _MemberStatusMarker) -> None:
|
||||
if not statuses:
|
||||
raise ValueError("Member status group should have at least one status included")
|
||||
self.statuses = frozenset(statuses)
|
||||
|
||||
def __or__(
|
||||
self: MarkerGroupT, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||
) -> MarkerGroupT:
|
||||
if isinstance(other, _MemberStatusMarker):
|
||||
return type(self)(*self.statuses, other)
|
||||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return type(self)(*self.statuses, *other.statuses)
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for |: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
def __rshift__(
|
||||
self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||
) -> "_MemberStatusTransition":
|
||||
if isinstance(other, _MemberStatusMarker):
|
||||
return _MemberStatusTransition(old=self, new=_MemberStatusGroupMarker(other))
|
||||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return _MemberStatusTransition(old=self, new=other)
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for >>: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
def __lshift__(
|
||||
self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"]
|
||||
) -> "_MemberStatusTransition":
|
||||
if isinstance(other, _MemberStatusMarker):
|
||||
return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=self)
|
||||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return _MemberStatusTransition(old=other, new=self)
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for <<: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
result = " | ".join(map(str, sorted(self.statuses, key=str)))
|
||||
if len(self.statuses) != 1:
|
||||
return f"({result})"
|
||||
return result
|
||||
|
||||
def check(self, *, member: ChatMember) -> bool:
|
||||
return any(status.check(member=member) for status in self.statuses)
|
||||
|
||||
|
||||
class _MemberStatusTransition:
|
||||
__slots__ = (
|
||||
"old",
|
||||
"new",
|
||||
)
|
||||
|
||||
def __init__(self, *, old: _MemberStatusGroupMarker, new: _MemberStatusGroupMarker) -> None:
|
||||
self.old = old
|
||||
self.new = new
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.old} >> {self.new}"
|
||||
|
||||
def __invert__(self: TransitionT) -> TransitionT:
|
||||
return type(self)(old=self.new, new=self.old)
|
||||
|
||||
def check(self, *, old: ChatMember, new: ChatMember) -> bool:
|
||||
return self.old.check(member=old) and self.new.check(member=new)
|
||||
|
||||
|
||||
CREATOR = _MemberStatusMarker("creator")
|
||||
ADMINISTRATOR = _MemberStatusMarker("administrator")
|
||||
MEMBER = _MemberStatusMarker("member")
|
||||
RESTRICTED = _MemberStatusMarker("restricted")
|
||||
LEFT = _MemberStatusMarker("left")
|
||||
KICKED = _MemberStatusMarker("kicked")
|
||||
|
||||
IS_MEMBER = CREATOR | ADMINISTRATOR | MEMBER | +RESTRICTED
|
||||
IS_ADMIN = CREATOR | ADMINISTRATOR
|
||||
IS_NOT_MEMBER = LEFT | KICKED | -RESTRICTED
|
||||
|
||||
JOIN_TRANSITION = IS_NOT_MEMBER >> IS_MEMBER
|
||||
LEAVE_TRANSITION = ~JOIN_TRANSITION
|
||||
PROMOTED_TRANSITION = (MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR
|
||||
|
||||
|
||||
class ChatMemberUpdatedFilter(Filter):
|
||||
__slots__ = ("member_status_changed",)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
member_status_changed: Union[
|
||||
_MemberStatusMarker,
|
||||
_MemberStatusGroupMarker,
|
||||
_MemberStatusTransition,
|
||||
],
|
||||
):
|
||||
self.member_status_changed = member_status_changed
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._signature_to_string(
|
||||
member_status_changed=self.member_status_changed,
|
||||
)
|
||||
|
||||
async def __call__(self, member_updated: ChatMemberUpdated) -> Union[bool, Dict[str, Any]]:
|
||||
old = member_updated.old_chat_member
|
||||
new = member_updated.new_chat_member
|
||||
rule = self.member_status_changed
|
||||
|
||||
if isinstance(rule, (_MemberStatusMarker, _MemberStatusGroupMarker)):
|
||||
return rule.check(member=new)
|
||||
if isinstance(rule, _MemberStatusTransition):
|
||||
return rule.check(old=old, new=new)
|
||||
|
||||
# Impossible variant in due to pydantic validation
|
||||
return False # pragma: no cover
|
298
myenv/Lib/site-packages/aiogram/filters/command.py
Normal file
298
myenv/Lib/site-packages/aiogram/filters/command.py
Normal file
@@ -0,0 +1,298 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass, field, replace
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
Iterable,
|
||||
Match,
|
||||
Optional,
|
||||
Pattern,
|
||||
Sequence,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from magic_filter import MagicFilter
|
||||
|
||||
from aiogram.filters.base import Filter
|
||||
from aiogram.types import BotCommand, Message
|
||||
from aiogram.utils.deep_linking import decode_payload
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram import Bot
|
||||
|
||||
# TODO: rm type ignore after py3.8 support expiration or mypy bug fix
|
||||
CommandPatternType = Union[str, re.Pattern, BotCommand] # type: ignore[type-arg]
|
||||
|
||||
|
||||
class CommandException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Command(Filter):
|
||||
"""
|
||||
This filter can be helpful for handling commands from the text messages.
|
||||
|
||||
Works only with :class:`aiogram.types.message.Message` events which have the :code:`text`.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"commands",
|
||||
"prefix",
|
||||
"ignore_case",
|
||||
"ignore_mention",
|
||||
"magic",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*values: CommandPatternType,
|
||||
commands: Optional[Union[Sequence[CommandPatternType], CommandPatternType]] = None,
|
||||
prefix: str = "/",
|
||||
ignore_case: bool = False,
|
||||
ignore_mention: bool = False,
|
||||
magic: Optional[MagicFilter] = None,
|
||||
):
|
||||
"""
|
||||
List of commands (string or compiled regexp patterns)
|
||||
|
||||
:param prefix: Prefix for command.
|
||||
Prefix is always a single char but here you can pass all of allowed prefixes,
|
||||
for example: :code:`"/!"` will work with commands prefixed
|
||||
by :code:`"/"` or :code:`"!"`.
|
||||
:param ignore_case: Ignore case (Does not work with regexp, use flags instead)
|
||||
:param ignore_mention: Ignore bot mention. By default,
|
||||
bot can not handle commands intended for other bots
|
||||
:param magic: Validate command object via Magic filter after all checks done
|
||||
"""
|
||||
if commands is None:
|
||||
commands = []
|
||||
if isinstance(commands, (str, re.Pattern, BotCommand)):
|
||||
commands = [commands]
|
||||
|
||||
if not isinstance(commands, Iterable):
|
||||
raise ValueError(
|
||||
"Command filter only supports str, re.Pattern, BotCommand object"
|
||||
" or their Iterable"
|
||||
)
|
||||
|
||||
items = []
|
||||
for command in (*values, *commands):
|
||||
if isinstance(command, BotCommand):
|
||||
command = command.command
|
||||
if not isinstance(command, (str, re.Pattern)):
|
||||
raise ValueError(
|
||||
"Command filter only supports str, re.Pattern, BotCommand object"
|
||||
" or their Iterable"
|
||||
)
|
||||
if ignore_case and isinstance(command, str):
|
||||
command = command.casefold()
|
||||
items.append(command)
|
||||
|
||||
if not items:
|
||||
raise ValueError("At least one command should be specified")
|
||||
|
||||
self.commands = tuple(items)
|
||||
self.prefix = prefix
|
||||
self.ignore_case = ignore_case
|
||||
self.ignore_mention = ignore_mention
|
||||
self.magic = magic
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._signature_to_string(
|
||||
*self.commands,
|
||||
prefix=self.prefix,
|
||||
ignore_case=self.ignore_case,
|
||||
ignore_mention=self.ignore_mention,
|
||||
magic=self.magic,
|
||||
)
|
||||
|
||||
def update_handler_flags(self, flags: Dict[str, Any]) -> None:
|
||||
commands = flags.setdefault("commands", [])
|
||||
commands.append(self)
|
||||
|
||||
async def __call__(self, message: Message, bot: Bot) -> Union[bool, Dict[str, Any]]:
|
||||
if not isinstance(message, Message):
|
||||
return False
|
||||
|
||||
text = message.text or message.caption
|
||||
if not text:
|
||||
return False
|
||||
|
||||
try:
|
||||
command = await self.parse_command(text=text, bot=bot)
|
||||
except CommandException:
|
||||
return False
|
||||
result = {"command": command}
|
||||
if command.magic_result and isinstance(command.magic_result, dict):
|
||||
result.update(command.magic_result)
|
||||
return result
|
||||
|
||||
def extract_command(self, text: str) -> CommandObject:
|
||||
# First step: separate command with arguments
|
||||
# "/command@mention arg1 arg2" -> "/command@mention", ["arg1 arg2"]
|
||||
try:
|
||||
full_command, *args = text.split(maxsplit=1)
|
||||
except ValueError:
|
||||
raise CommandException("not enough values to unpack")
|
||||
|
||||
# Separate command into valuable parts
|
||||
# "/command@mention" -> "/", ("command", "@", "mention")
|
||||
prefix, (command, _, mention) = full_command[0], full_command[1:].partition("@")
|
||||
return CommandObject(
|
||||
prefix=prefix,
|
||||
command=command,
|
||||
mention=mention or None,
|
||||
args=args[0] if args else None,
|
||||
)
|
||||
|
||||
def validate_prefix(self, command: CommandObject) -> None:
|
||||
if command.prefix not in self.prefix:
|
||||
raise CommandException("Invalid command prefix")
|
||||
|
||||
async def validate_mention(self, bot: Bot, command: CommandObject) -> None:
|
||||
if command.mention and not self.ignore_mention:
|
||||
me = await bot.me()
|
||||
if me.username and command.mention.lower() != me.username.lower():
|
||||
raise CommandException("Mention did not match")
|
||||
|
||||
def validate_command(self, command: CommandObject) -> CommandObject:
|
||||
for allowed_command in cast(Sequence[CommandPatternType], self.commands):
|
||||
# Command can be presented as regexp pattern or raw string
|
||||
# then need to validate that in different ways
|
||||
if isinstance(allowed_command, Pattern): # Regexp
|
||||
result = allowed_command.match(command.command)
|
||||
if result:
|
||||
return replace(command, regexp_match=result)
|
||||
|
||||
command_name = command.command
|
||||
if self.ignore_case:
|
||||
command_name = command_name.casefold()
|
||||
|
||||
if command_name == allowed_command: # String
|
||||
return command
|
||||
raise CommandException("Command did not match pattern")
|
||||
|
||||
async def parse_command(self, text: str, bot: Bot) -> CommandObject:
|
||||
"""
|
||||
Extract command from the text and validate
|
||||
|
||||
:param text:
|
||||
:param bot:
|
||||
:return:
|
||||
"""
|
||||
command = self.extract_command(text)
|
||||
self.validate_prefix(command=command)
|
||||
await self.validate_mention(bot=bot, command=command)
|
||||
command = self.validate_command(command)
|
||||
command = self.do_magic(command=command)
|
||||
return command # noqa: RET504
|
||||
|
||||
def do_magic(self, command: CommandObject) -> Any:
|
||||
if self.magic is None:
|
||||
return command
|
||||
result = self.magic.resolve(command)
|
||||
if not result:
|
||||
raise CommandException("Rejected via magic filter")
|
||||
return replace(command, magic_result=result)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CommandObject:
|
||||
"""
|
||||
Instance of this object is always has command and it prefix.
|
||||
Can be passed as keyword argument **command** to the handler
|
||||
"""
|
||||
|
||||
prefix: str = "/"
|
||||
"""Command prefix"""
|
||||
command: str = ""
|
||||
"""Command without prefix and mention"""
|
||||
mention: Optional[str] = None
|
||||
"""Mention (if available)"""
|
||||
args: Optional[str] = field(repr=False, default=None)
|
||||
"""Command argument"""
|
||||
regexp_match: Optional[Match[str]] = field(repr=False, default=None)
|
||||
"""Will be presented match result if the command is presented as regexp in filter"""
|
||||
magic_result: Optional[Any] = field(repr=False, default=None)
|
||||
|
||||
@property
|
||||
def mentioned(self) -> bool:
|
||||
"""
|
||||
This command has mention?
|
||||
"""
|
||||
return bool(self.mention)
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
"""
|
||||
Generate original text from object
|
||||
"""
|
||||
line = self.prefix + self.command
|
||||
if self.mention:
|
||||
line += "@" + self.mention
|
||||
if self.args:
|
||||
line += " " + self.args
|
||||
return line
|
||||
|
||||
|
||||
class CommandStart(Command):
|
||||
def __init__(
|
||||
self,
|
||||
deep_link: bool = False,
|
||||
deep_link_encoded: bool = False,
|
||||
ignore_case: bool = False,
|
||||
ignore_mention: bool = False,
|
||||
magic: Optional[MagicFilter] = None,
|
||||
):
|
||||
super().__init__(
|
||||
"start",
|
||||
prefix="/",
|
||||
ignore_case=ignore_case,
|
||||
ignore_mention=ignore_mention,
|
||||
magic=magic,
|
||||
)
|
||||
self.deep_link = deep_link
|
||||
self.deep_link_encoded = deep_link_encoded
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._signature_to_string(
|
||||
ignore_case=self.ignore_case,
|
||||
ignore_mention=self.ignore_mention,
|
||||
magic=self.magic,
|
||||
deep_link=self.deep_link,
|
||||
deep_link_encoded=self.deep_link_encoded,
|
||||
)
|
||||
|
||||
async def parse_command(self, text: str, bot: Bot) -> CommandObject:
|
||||
"""
|
||||
Extract command from the text and validate
|
||||
|
||||
:param text:
|
||||
:param bot:
|
||||
:return:
|
||||
"""
|
||||
command = self.extract_command(text)
|
||||
self.validate_prefix(command=command)
|
||||
await self.validate_mention(bot=bot, command=command)
|
||||
command = self.validate_command(command)
|
||||
command = self.validate_deeplink(command=command)
|
||||
command = self.do_magic(command=command)
|
||||
return command # noqa: RET504
|
||||
|
||||
def validate_deeplink(self, command: CommandObject) -> CommandObject:
|
||||
if not self.deep_link:
|
||||
return command
|
||||
if not command.args:
|
||||
raise CommandException("Deep-link was missing")
|
||||
args = command.args
|
||||
if self.deep_link_encoded:
|
||||
try:
|
||||
args = decode_payload(args)
|
||||
except UnicodeDecodeError as e:
|
||||
raise CommandException(f"Failed to decode Base64: {e}")
|
||||
return replace(command, args=args)
|
||||
return command
|
55
myenv/Lib/site-packages/aiogram/filters/exception.py
Normal file
55
myenv/Lib/site-packages/aiogram/filters/exception.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import re
|
||||
from typing import Any, Dict, Pattern, Type, Union, cast
|
||||
|
||||
from aiogram.filters.base import Filter
|
||||
from aiogram.types import TelegramObject
|
||||
from aiogram.types.error_event import ErrorEvent
|
||||
|
||||
|
||||
class ExceptionTypeFilter(Filter):
|
||||
"""
|
||||
Allows to match exception by type
|
||||
"""
|
||||
|
||||
__slots__ = ("exceptions",)
|
||||
|
||||
def __init__(self, *exceptions: Type[Exception]):
|
||||
"""
|
||||
:param exceptions: Exception type(s)
|
||||
"""
|
||||
if not exceptions:
|
||||
raise ValueError("At least one exception type is required")
|
||||
self.exceptions = exceptions
|
||||
|
||||
async def __call__(self, obj: TelegramObject) -> Union[bool, Dict[str, Any]]:
|
||||
return isinstance(cast(ErrorEvent, obj).exception, self.exceptions)
|
||||
|
||||
|
||||
class ExceptionMessageFilter(Filter):
|
||||
"""
|
||||
Allow to match exception by message
|
||||
"""
|
||||
|
||||
__slots__ = ("pattern",)
|
||||
|
||||
def __init__(self, pattern: Union[str, Pattern[str]]):
|
||||
"""
|
||||
:param pattern: Regexp pattern
|
||||
"""
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
self.pattern = pattern
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._signature_to_string(
|
||||
pattern=self.pattern,
|
||||
)
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
obj: TelegramObject,
|
||||
) -> Union[bool, Dict[str, Any]]:
|
||||
result = self.pattern.match(str(cast(ErrorEvent, obj).exception))
|
||||
if not result:
|
||||
return False
|
||||
return {"match_exception": result}
|
77
myenv/Lib/site-packages/aiogram/filters/logic.py
Normal file
77
myenv/Lib/site-packages/aiogram/filters/logic.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from abc import ABC
|
||||
from typing import TYPE_CHECKING, Any, Dict, Union
|
||||
|
||||
from aiogram.filters import Filter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.dispatcher.event.handler import CallbackType, FilterObject
|
||||
|
||||
|
||||
class _LogicFilter(Filter, ABC):
|
||||
pass
|
||||
|
||||
|
||||
class _InvertFilter(_LogicFilter):
|
||||
__slots__ = ("target",)
|
||||
|
||||
def __init__(self, target: "FilterObject") -> None:
|
||||
self.target = target
|
||||
|
||||
async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]:
|
||||
return not bool(await self.target.call(*args, **kwargs))
|
||||
|
||||
|
||||
class _AndFilter(_LogicFilter):
|
||||
__slots__ = ("targets",)
|
||||
|
||||
def __init__(self, *targets: "FilterObject") -> None:
|
||||
self.targets = targets
|
||||
|
||||
async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]:
|
||||
final_result = {}
|
||||
|
||||
for target in self.targets:
|
||||
result = await target.call(*args, **kwargs)
|
||||
if not result:
|
||||
return False
|
||||
if isinstance(result, dict):
|
||||
final_result.update(result)
|
||||
|
||||
if final_result:
|
||||
return final_result
|
||||
return True
|
||||
|
||||
|
||||
class _OrFilter(_LogicFilter):
|
||||
__slots__ = ("targets",)
|
||||
|
||||
def __init__(self, *targets: "FilterObject") -> None:
|
||||
self.targets = targets
|
||||
|
||||
async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]:
|
||||
for target in self.targets:
|
||||
result = await target.call(*args, **kwargs)
|
||||
if not result:
|
||||
continue
|
||||
if isinstance(result, dict):
|
||||
return result
|
||||
return bool(result)
|
||||
return False
|
||||
|
||||
|
||||
def and_f(*targets: "CallbackType") -> _AndFilter:
|
||||
from aiogram.dispatcher.event.handler import FilterObject
|
||||
|
||||
return _AndFilter(*(FilterObject(target) for target in targets))
|
||||
|
||||
|
||||
def or_f(*targets: "CallbackType") -> _OrFilter:
|
||||
from aiogram.dispatcher.event.handler import FilterObject
|
||||
|
||||
return _OrFilter(*(FilterObject(target) for target in targets))
|
||||
|
||||
|
||||
def invert_f(target: "CallbackType") -> _InvertFilter:
|
||||
from aiogram.dispatcher.event.handler import FilterObject
|
||||
|
||||
return _InvertFilter(FilterObject(target))
|
27
myenv/Lib/site-packages/aiogram/filters/magic_data.py
Normal file
27
myenv/Lib/site-packages/aiogram/filters/magic_data.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from typing import Any
|
||||
|
||||
from magic_filter import AttrDict, MagicFilter
|
||||
|
||||
from aiogram.filters.base import Filter
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
||||
class MagicData(Filter):
|
||||
"""
|
||||
This filter helps to filter event with contextual data
|
||||
"""
|
||||
|
||||
__slots__ = ("magic_data",)
|
||||
|
||||
def __init__(self, magic_data: MagicFilter) -> None:
|
||||
self.magic_data = magic_data
|
||||
|
||||
async def __call__(self, event: TelegramObject, *args: Any, **kwargs: Any) -> Any:
|
||||
return self.magic_data.resolve(
|
||||
AttrDict({"event": event, **dict(enumerate(args)), **kwargs})
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._signature_to_string(
|
||||
magic_data=self.magic_data,
|
||||
)
|
43
myenv/Lib/site-packages/aiogram/filters/state.py
Normal file
43
myenv/Lib/site-packages/aiogram/filters/state.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from inspect import isclass
|
||||
from typing import Any, Dict, Optional, Sequence, Type, Union, cast
|
||||
|
||||
from aiogram.filters.base import Filter
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
StateType = Union[str, None, State, StatesGroup, Type[StatesGroup]]
|
||||
|
||||
|
||||
class StateFilter(Filter):
|
||||
"""
|
||||
State filter
|
||||
"""
|
||||
|
||||
__slots__ = ("states",)
|
||||
|
||||
def __init__(self, *states: StateType) -> None:
|
||||
if not states:
|
||||
raise ValueError("At least one state is required")
|
||||
|
||||
self.states = states
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._signature_to_string(
|
||||
*self.states,
|
||||
)
|
||||
|
||||
async def __call__(
|
||||
self, obj: TelegramObject, raw_state: Optional[str] = None
|
||||
) -> Union[bool, Dict[str, Any]]:
|
||||
allowed_states = cast(Sequence[StateType], self.states)
|
||||
for allowed_state in allowed_states:
|
||||
if isinstance(allowed_state, str) or allowed_state is None:
|
||||
if allowed_state == "*" or raw_state == allowed_state:
|
||||
return True
|
||||
elif isinstance(allowed_state, (State, StatesGroup)):
|
||||
if allowed_state(event=obj, raw_state=raw_state):
|
||||
return True
|
||||
elif isclass(allowed_state) and issubclass(allowed_state, StatesGroup):
|
||||
if allowed_state()(event=obj, raw_state=raw_state):
|
||||
return True
|
||||
return False
|
0
myenv/Lib/site-packages/aiogram/fsm/__init__.py
Normal file
0
myenv/Lib/site-packages/aiogram/fsm/__init__.py
Normal file
41
myenv/Lib/site-packages/aiogram/fsm/context.py
Normal file
41
myenv/Lib/site-packages/aiogram/fsm/context.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from typing import Any, Dict, Optional, overload
|
||||
|
||||
from aiogram.fsm.storage.base import BaseStorage, StateType, StorageKey
|
||||
|
||||
|
||||
class FSMContext:
|
||||
def __init__(self, storage: BaseStorage, key: StorageKey) -> None:
|
||||
self.storage = storage
|
||||
self.key = key
|
||||
|
||||
async def set_state(self, state: StateType = None) -> None:
|
||||
await self.storage.set_state(key=self.key, state=state)
|
||||
|
||||
async def get_state(self) -> Optional[str]:
|
||||
return await self.storage.get_state(key=self.key)
|
||||
|
||||
async def set_data(self, data: Dict[str, Any]) -> None:
|
||||
await self.storage.set_data(key=self.key, data=data)
|
||||
|
||||
async def get_data(self) -> Dict[str, Any]:
|
||||
return await self.storage.get_data(key=self.key)
|
||||
|
||||
@overload
|
||||
async def get_value(self, key: str) -> Optional[Any]: ...
|
||||
|
||||
@overload
|
||||
async def get_value(self, key: str, default: Any) -> Any: ...
|
||||
|
||||
async def get_value(self, key: str, default: Optional[Any] = None) -> Optional[Any]:
|
||||
return await self.storage.get_value(storage_key=self.key, dict_key=key, default=default)
|
||||
|
||||
async def update_data(
|
||||
self, data: Optional[Dict[str, Any]] = None, **kwargs: Any
|
||||
) -> Dict[str, Any]:
|
||||
if data:
|
||||
kwargs.update(data)
|
||||
return await self.storage.update_data(key=self.key, data=kwargs)
|
||||
|
||||
async def clear(self) -> None:
|
||||
await self.set_state(state=None)
|
||||
await self.set_data({})
|
113
myenv/Lib/site-packages/aiogram/fsm/middleware.py
Normal file
113
myenv/Lib/site-packages/aiogram/fsm/middleware.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from typing import Any, Awaitable, Callable, Dict, Optional, cast
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
||||
from aiogram.dispatcher.middlewares.user_context import EVENT_CONTEXT_KEY, EventContext
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.storage.base import (
|
||||
DEFAULT_DESTINY,
|
||||
BaseEventIsolation,
|
||||
BaseStorage,
|
||||
StorageKey,
|
||||
)
|
||||
from aiogram.fsm.strategy import FSMStrategy, apply_strategy
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
||||
class FSMContextMiddleware(BaseMiddleware):
|
||||
def __init__(
|
||||
self,
|
||||
storage: BaseStorage,
|
||||
events_isolation: BaseEventIsolation,
|
||||
strategy: FSMStrategy = FSMStrategy.USER_IN_CHAT,
|
||||
) -> None:
|
||||
self.storage = storage
|
||||
self.strategy = strategy
|
||||
self.events_isolation = events_isolation
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
bot: Bot = cast(Bot, data["bot"])
|
||||
context = self.resolve_event_context(bot, data)
|
||||
data["fsm_storage"] = self.storage
|
||||
if context:
|
||||
# Bugfix: https://github.com/aiogram/aiogram/issues/1317
|
||||
# State should be loaded after lock is acquired
|
||||
async with self.events_isolation.lock(key=context.key):
|
||||
data.update({"state": context, "raw_state": await context.get_state()})
|
||||
return await handler(event, data)
|
||||
return await handler(event, data)
|
||||
|
||||
def resolve_event_context(
|
||||
self,
|
||||
bot: Bot,
|
||||
data: Dict[str, Any],
|
||||
destiny: str = DEFAULT_DESTINY,
|
||||
) -> Optional[FSMContext]:
|
||||
event_context: EventContext = cast(EventContext, data.get(EVENT_CONTEXT_KEY))
|
||||
return self.resolve_context(
|
||||
bot=bot,
|
||||
chat_id=event_context.chat_id,
|
||||
user_id=event_context.user_id,
|
||||
thread_id=event_context.thread_id,
|
||||
business_connection_id=event_context.business_connection_id,
|
||||
destiny=destiny,
|
||||
)
|
||||
|
||||
def resolve_context(
|
||||
self,
|
||||
bot: Bot,
|
||||
chat_id: Optional[int],
|
||||
user_id: Optional[int],
|
||||
thread_id: Optional[int] = None,
|
||||
business_connection_id: Optional[str] = None,
|
||||
destiny: str = DEFAULT_DESTINY,
|
||||
) -> Optional[FSMContext]:
|
||||
if chat_id is None:
|
||||
chat_id = user_id
|
||||
|
||||
if chat_id is not None and user_id is not None:
|
||||
chat_id, user_id, thread_id = apply_strategy(
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
thread_id=thread_id,
|
||||
strategy=self.strategy,
|
||||
)
|
||||
return self.get_context(
|
||||
bot=bot,
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
thread_id=thread_id,
|
||||
business_connection_id=business_connection_id,
|
||||
destiny=destiny,
|
||||
)
|
||||
return None
|
||||
|
||||
def get_context(
|
||||
self,
|
||||
bot: Bot,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
thread_id: Optional[int] = None,
|
||||
business_connection_id: Optional[str] = None,
|
||||
destiny: str = DEFAULT_DESTINY,
|
||||
) -> FSMContext:
|
||||
return FSMContext(
|
||||
storage=self.storage,
|
||||
key=StorageKey(
|
||||
user_id=user_id,
|
||||
chat_id=chat_id,
|
||||
bot_id=bot.id,
|
||||
thread_id=thread_id,
|
||||
business_connection_id=business_connection_id,
|
||||
destiny=destiny,
|
||||
),
|
||||
)
|
||||
|
||||
async def close(self) -> None:
|
||||
await self.storage.close()
|
||||
await self.events_isolation.close()
|
952
myenv/Lib/site-packages/aiogram/fsm/scene.py
Normal file
952
myenv/Lib/site-packages/aiogram/fsm/scene.py
Normal file
@@ -0,0 +1,952 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, replace
|
||||
from enum import Enum, auto
|
||||
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, Union, overload
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from aiogram import loggers
|
||||
from aiogram.dispatcher.dispatcher import Dispatcher
|
||||
from aiogram.dispatcher.event.bases import NextMiddlewareType
|
||||
from aiogram.dispatcher.event.handler import CallableObject, CallbackType
|
||||
from aiogram.dispatcher.flags import extract_flags_from_object
|
||||
from aiogram.dispatcher.router import Router
|
||||
from aiogram.exceptions import SceneException
|
||||
from aiogram.filters import StateFilter
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State
|
||||
from aiogram.fsm.storage.memory import MemoryStorageRecord
|
||||
from aiogram.types import TelegramObject, Update
|
||||
from aiogram.utils.class_attrs_resolver import (
|
||||
ClassAttrsResolver,
|
||||
get_sorted_mro_attrs_resolver,
|
||||
)
|
||||
|
||||
|
||||
class HistoryManager:
|
||||
def __init__(self, state: FSMContext, destiny: str = "scenes_history", size: int = 10):
|
||||
self._size = size
|
||||
self._state = state
|
||||
self._history_state = FSMContext(
|
||||
storage=state.storage, key=replace(state.key, destiny=destiny)
|
||||
)
|
||||
|
||||
async def push(self, state: Optional[str], data: Dict[str, Any]) -> None:
|
||||
history_data = await self._history_state.get_data()
|
||||
history = history_data.setdefault("history", [])
|
||||
history.append({"state": state, "data": data})
|
||||
if len(history) > self._size:
|
||||
history = history[-self._size :]
|
||||
loggers.scene.debug("Push state=%s data=%s to history", state, data)
|
||||
|
||||
await self._history_state.update_data(history=history)
|
||||
|
||||
async def pop(self) -> Optional[MemoryStorageRecord]:
|
||||
history_data = await self._history_state.get_data()
|
||||
history = history_data.setdefault("history", [])
|
||||
if not history:
|
||||
return None
|
||||
record = history.pop()
|
||||
state = record["state"]
|
||||
data = record["data"]
|
||||
if not history:
|
||||
await self._history_state.set_data({})
|
||||
else:
|
||||
await self._history_state.update_data(history=history)
|
||||
loggers.scene.debug("Pop state=%s data=%s from history", state, data)
|
||||
return MemoryStorageRecord(state=state, data=data)
|
||||
|
||||
async def get(self) -> Optional[MemoryStorageRecord]:
|
||||
history_data = await self._history_state.get_data()
|
||||
history = history_data.setdefault("history", [])
|
||||
if not history:
|
||||
return None
|
||||
return MemoryStorageRecord(**history[-1])
|
||||
|
||||
async def all(self) -> List[MemoryStorageRecord]:
|
||||
history_data = await self._history_state.get_data()
|
||||
history = history_data.setdefault("history", [])
|
||||
return [MemoryStorageRecord(**item) for item in history]
|
||||
|
||||
async def clear(self) -> None:
|
||||
loggers.scene.debug("Clear history")
|
||||
await self._history_state.set_data({})
|
||||
|
||||
async def snapshot(self) -> None:
|
||||
state = await self._state.get_state()
|
||||
data = await self._state.get_data()
|
||||
await self.push(state, data)
|
||||
|
||||
async def _set_state(self, state: Optional[str], data: Dict[str, Any]) -> None:
|
||||
await self._state.set_state(state)
|
||||
await self._state.set_data(data)
|
||||
|
||||
async def rollback(self) -> Optional[str]:
|
||||
previous_state = await self.pop()
|
||||
if not previous_state:
|
||||
await self._set_state(None, {})
|
||||
return None
|
||||
|
||||
loggers.scene.debug(
|
||||
"Rollback to state=%s data=%s",
|
||||
previous_state.state,
|
||||
previous_state.data,
|
||||
)
|
||||
await self._set_state(previous_state.state, previous_state.data)
|
||||
return previous_state.state
|
||||
|
||||
|
||||
class ObserverDecorator:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
filters: tuple[CallbackType, ...],
|
||||
action: SceneAction | None = None,
|
||||
after: Optional[After] = None,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.filters = filters
|
||||
self.action = action
|
||||
self.after = after
|
||||
|
||||
def _wrap_filter(self, target: Type[Scene] | CallbackType) -> None:
|
||||
handlers = getattr(target, "__aiogram_handler__", None)
|
||||
if not handlers:
|
||||
handlers = []
|
||||
setattr(target, "__aiogram_handler__", handlers)
|
||||
|
||||
handlers.append(
|
||||
HandlerContainer(
|
||||
name=self.name,
|
||||
handler=target,
|
||||
filters=self.filters,
|
||||
after=self.after,
|
||||
)
|
||||
)
|
||||
|
||||
def _wrap_action(self, target: CallbackType) -> None:
|
||||
assert self.action is not None, "Scene action is not specified"
|
||||
|
||||
action = getattr(target, "__aiogram_action__", None)
|
||||
if action is None:
|
||||
action = defaultdict(dict)
|
||||
setattr(target, "__aiogram_action__", action)
|
||||
action[self.action][self.name] = CallableObject(target)
|
||||
|
||||
def __call__(self, target: CallbackType) -> CallbackType:
|
||||
if inspect.isfunction(target):
|
||||
if self.action is None:
|
||||
self._wrap_filter(target)
|
||||
else:
|
||||
self._wrap_action(target)
|
||||
else:
|
||||
raise TypeError("Only function or method is allowed")
|
||||
return target
|
||||
|
||||
def leave(self) -> ActionContainer:
|
||||
return ActionContainer(self.name, self.filters, SceneAction.leave)
|
||||
|
||||
def enter(self, target: Type[Scene]) -> ActionContainer:
|
||||
return ActionContainer(self.name, self.filters, SceneAction.enter, target)
|
||||
|
||||
def exit(self) -> ActionContainer:
|
||||
return ActionContainer(self.name, self.filters, SceneAction.exit)
|
||||
|
||||
def back(self) -> ActionContainer:
|
||||
return ActionContainer(self.name, self.filters, SceneAction.back)
|
||||
|
||||
|
||||
class SceneAction(Enum):
|
||||
enter = auto()
|
||||
leave = auto()
|
||||
exit = auto()
|
||||
back = auto()
|
||||
|
||||
|
||||
class ActionContainer:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
filters: Tuple[CallbackType, ...],
|
||||
action: SceneAction,
|
||||
target: Optional[Union[Type[Scene], str]] = None,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.filters = filters
|
||||
self.action = action
|
||||
self.target = target
|
||||
|
||||
async def execute(self, wizard: SceneWizard) -> None:
|
||||
if self.action == SceneAction.enter and self.target is not None:
|
||||
await wizard.goto(self.target)
|
||||
elif self.action == SceneAction.leave:
|
||||
await wizard.leave()
|
||||
elif self.action == SceneAction.exit:
|
||||
await wizard.exit()
|
||||
elif self.action == SceneAction.back:
|
||||
await wizard.back()
|
||||
|
||||
|
||||
class HandlerContainer:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
handler: CallbackType,
|
||||
filters: Tuple[CallbackType, ...],
|
||||
after: Optional[After] = None,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.handler = handler
|
||||
self.filters = filters
|
||||
self.after = after
|
||||
|
||||
|
||||
@dataclass()
|
||||
class SceneConfig:
|
||||
state: Optional[str]
|
||||
"""Scene state"""
|
||||
handlers: List[HandlerContainer]
|
||||
"""Scene handlers"""
|
||||
actions: Dict[SceneAction, Dict[str, CallableObject]]
|
||||
"""Scene actions"""
|
||||
reset_data_on_enter: Optional[bool] = None
|
||||
"""Reset scene data on enter"""
|
||||
reset_history_on_enter: Optional[bool] = None
|
||||
"""Reset scene history on enter"""
|
||||
callback_query_without_state: Optional[bool] = None
|
||||
"""Allow callback query without state"""
|
||||
attrs_resolver: ClassAttrsResolver = get_sorted_mro_attrs_resolver
|
||||
"""
|
||||
Attributes resolver.
|
||||
|
||||
.. danger::
|
||||
This attribute should only be changed when you know what you are doing.
|
||||
|
||||
.. versionadded:: 3.19.0
|
||||
"""
|
||||
|
||||
|
||||
async def _empty_handler(*args: Any, **kwargs: Any) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class SceneHandlerWrapper:
|
||||
def __init__(
|
||||
self,
|
||||
scene: Type[Scene],
|
||||
handler: CallbackType,
|
||||
after: Optional[After] = None,
|
||||
) -> None:
|
||||
self.scene = scene
|
||||
self.handler = CallableObject(handler)
|
||||
self.after = after
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
event: TelegramObject,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
state: FSMContext = kwargs["state"]
|
||||
scenes: ScenesManager = kwargs["scenes"]
|
||||
event_update: Update = kwargs["event_update"]
|
||||
scene = self.scene(
|
||||
wizard=SceneWizard(
|
||||
scene_config=self.scene.__scene_config__,
|
||||
manager=scenes,
|
||||
state=state,
|
||||
update_type=event_update.event_type,
|
||||
event=event,
|
||||
data=kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
result = await self.handler.call(scene, event, **kwargs)
|
||||
|
||||
if self.after:
|
||||
action_container = ActionContainer(
|
||||
"after",
|
||||
(),
|
||||
self.after.action,
|
||||
self.after.scene,
|
||||
)
|
||||
await action_container.execute(scene.wizard)
|
||||
return result
|
||||
|
||||
def __await__(self) -> Self:
|
||||
return self
|
||||
|
||||
def __str__(self) -> str:
|
||||
result = f"SceneHandlerWrapper({self.scene}, {self.handler.callback}"
|
||||
if self.after:
|
||||
result += f", after={self.after}"
|
||||
result += ")"
|
||||
return result
|
||||
|
||||
|
||||
class Scene:
|
||||
"""
|
||||
Represents a scene in a conversation flow.
|
||||
|
||||
A scene is a specific state in a conversation where certain actions can take place.
|
||||
|
||||
Each scene has a set of filters that determine when it should be triggered,
|
||||
and a set of handlers that define the actions to be executed when the scene is active.
|
||||
|
||||
.. note::
|
||||
This class is not meant to be used directly. Instead, it should be subclassed
|
||||
to define custom scenes.
|
||||
"""
|
||||
|
||||
__scene_config__: ClassVar[SceneConfig]
|
||||
"""Scene configuration."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
wizard: SceneWizard,
|
||||
) -> None:
|
||||
self.wizard = wizard
|
||||
self.wizard.scene = self
|
||||
|
||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||
state_name = kwargs.pop("state", None)
|
||||
reset_data_on_enter = kwargs.pop("reset_data_on_enter", None)
|
||||
reset_history_on_enter = kwargs.pop("reset_history_on_enter", None)
|
||||
callback_query_without_state = kwargs.pop("callback_query_without_state", None)
|
||||
attrs_resolver = kwargs.pop("attrs_resolver", None)
|
||||
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
handlers: list[HandlerContainer] = []
|
||||
actions: defaultdict[SceneAction, Dict[str, CallableObject]] = defaultdict(dict)
|
||||
|
||||
for base in cls.__bases__:
|
||||
if not issubclass(base, Scene):
|
||||
continue
|
||||
|
||||
parent_scene_config = getattr(base, "__scene_config__", None)
|
||||
if not parent_scene_config:
|
||||
continue
|
||||
|
||||
if reset_data_on_enter is None:
|
||||
reset_data_on_enter = parent_scene_config.reset_data_on_enter
|
||||
if reset_history_on_enter is None:
|
||||
reset_history_on_enter = parent_scene_config.reset_history_on_enter
|
||||
if callback_query_without_state is None:
|
||||
callback_query_without_state = parent_scene_config.callback_query_without_state
|
||||
if attrs_resolver is None:
|
||||
attrs_resolver = parent_scene_config.attrs_resolver
|
||||
|
||||
if attrs_resolver is None:
|
||||
attrs_resolver = get_sorted_mro_attrs_resolver
|
||||
|
||||
for name, value in attrs_resolver(cls):
|
||||
if scene_handlers := getattr(value, "__aiogram_handler__", None):
|
||||
handlers.extend(scene_handlers)
|
||||
if isinstance(value, ObserverDecorator):
|
||||
handlers.append(
|
||||
HandlerContainer(
|
||||
value.name,
|
||||
_empty_handler,
|
||||
value.filters,
|
||||
after=value.after,
|
||||
)
|
||||
)
|
||||
if hasattr(value, "__aiogram_action__"):
|
||||
for action, action_handlers in value.__aiogram_action__.items():
|
||||
actions[action].update(action_handlers)
|
||||
|
||||
cls.__scene_config__ = SceneConfig(
|
||||
state=state_name,
|
||||
handlers=handlers,
|
||||
actions=dict(actions),
|
||||
reset_data_on_enter=reset_data_on_enter,
|
||||
reset_history_on_enter=reset_history_on_enter,
|
||||
callback_query_without_state=callback_query_without_state,
|
||||
attrs_resolver=attrs_resolver,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def add_to_router(cls, router: Router) -> None:
|
||||
"""
|
||||
Adds the scene to the given router.
|
||||
|
||||
:param router:
|
||||
:return:
|
||||
"""
|
||||
scene_config = cls.__scene_config__
|
||||
used_observers = set()
|
||||
|
||||
for handler in scene_config.handlers:
|
||||
router.observers[handler.name].register(
|
||||
SceneHandlerWrapper(
|
||||
cls,
|
||||
handler.handler,
|
||||
after=handler.after,
|
||||
),
|
||||
*handler.filters,
|
||||
flags=extract_flags_from_object(handler.handler),
|
||||
)
|
||||
used_observers.add(handler.name)
|
||||
|
||||
for observer_name in used_observers:
|
||||
if scene_config.callback_query_without_state and observer_name == "callback_query":
|
||||
continue
|
||||
router.observers[observer_name].filter(StateFilter(scene_config.state))
|
||||
|
||||
@classmethod
|
||||
def as_router(cls, name: Optional[str] = None) -> Router:
|
||||
"""
|
||||
Returns the scene as a router.
|
||||
|
||||
:return: new router
|
||||
"""
|
||||
if name is None:
|
||||
name = (
|
||||
f"Scene '{cls.__module__}.{cls.__qualname__}' "
|
||||
f"for state {cls.__scene_config__.state!r}"
|
||||
)
|
||||
router = Router(name=name)
|
||||
cls.add_to_router(router)
|
||||
return router
|
||||
|
||||
@classmethod
|
||||
def as_handler(cls, **kwargs: Any) -> CallbackType:
|
||||
"""
|
||||
Create an entry point handler for the scene, can be used to simplify the handler
|
||||
that starts the scene.
|
||||
|
||||
>>> router.message.register(MyScene.as_handler(), Command("start"))
|
||||
"""
|
||||
|
||||
async def enter_to_scene_handler(event: TelegramObject, scenes: ScenesManager) -> None:
|
||||
await scenes.enter(cls, **kwargs)
|
||||
|
||||
return enter_to_scene_handler
|
||||
|
||||
|
||||
class SceneWizard:
|
||||
"""
|
||||
A class that represents a wizard for managing scenes in a Telegram bot.
|
||||
|
||||
Instance of this class is passed to each scene as a parameter.
|
||||
So, you can use it to transition between scenes, get and set data, etc.
|
||||
|
||||
.. note::
|
||||
|
||||
This class is not meant to be used directly. Instead, it should be used
|
||||
as a parameter in the scene constructor.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
scene_config: SceneConfig,
|
||||
manager: ScenesManager,
|
||||
state: FSMContext,
|
||||
update_type: str,
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
):
|
||||
"""
|
||||
A class that represents a wizard for managing scenes in a Telegram bot.
|
||||
|
||||
:param scene_config: The configuration of the scene.
|
||||
:param manager: The scene manager.
|
||||
:param state: The FSMContext object for storing the state of the scene.
|
||||
:param update_type: The type of the update event.
|
||||
:param event: The TelegramObject represents the event.
|
||||
:param data: Additional data for the scene.
|
||||
"""
|
||||
self.scene_config = scene_config
|
||||
self.manager = manager
|
||||
self.state = state
|
||||
self.update_type = update_type
|
||||
self.event = event
|
||||
self.data = data
|
||||
|
||||
self.scene: Optional[Scene] = None
|
||||
|
||||
async def enter(self, **kwargs: Any) -> None:
|
||||
"""
|
||||
Enter method is used to transition into a scene in the SceneWizard class.
|
||||
It sets the state, clears data and history if specified,
|
||||
and triggers entering event of the scene.
|
||||
|
||||
:param kwargs: Additional keyword arguments.
|
||||
:return: None
|
||||
"""
|
||||
loggers.scene.debug("Entering scene %r", self.scene_config.state)
|
||||
if self.scene_config.reset_data_on_enter:
|
||||
await self.state.set_data({})
|
||||
if self.scene_config.reset_history_on_enter:
|
||||
await self.manager.history.clear()
|
||||
await self.state.set_state(self.scene_config.state)
|
||||
await self._on_action(SceneAction.enter, **kwargs)
|
||||
|
||||
async def leave(self, _with_history: bool = True, **kwargs: Any) -> None:
|
||||
"""
|
||||
Leaves the current scene.
|
||||
This method is used to exit a scene and transition to the next scene.
|
||||
|
||||
:param _with_history: Whether to include history in the snapshot. Defaults to True.
|
||||
:param kwargs: Additional keyword arguments.
|
||||
:return: None
|
||||
|
||||
"""
|
||||
loggers.scene.debug("Leaving scene %r", self.scene_config.state)
|
||||
if _with_history:
|
||||
await self.manager.history.snapshot()
|
||||
await self._on_action(SceneAction.leave, **kwargs)
|
||||
|
||||
async def exit(self, **kwargs: Any) -> None:
|
||||
"""
|
||||
Exit the current scene and enter the default scene/state.
|
||||
|
||||
:param kwargs: Additional keyword arguments.
|
||||
:return: None
|
||||
"""
|
||||
loggers.scene.debug("Exiting scene %r", self.scene_config.state)
|
||||
await self.manager.history.clear()
|
||||
await self._on_action(SceneAction.exit, **kwargs)
|
||||
await self.manager.enter(None, _check_active=False, **kwargs)
|
||||
|
||||
async def back(self, **kwargs: Any) -> None:
|
||||
"""
|
||||
This method is used to go back to the previous scene.
|
||||
|
||||
:param kwargs: Keyword arguments that can be passed to the method.
|
||||
:return: None
|
||||
"""
|
||||
loggers.scene.debug("Back to previous scene from scene %s", self.scene_config.state)
|
||||
await self.leave(_with_history=False, **kwargs)
|
||||
new_scene = await self.manager.history.rollback()
|
||||
await self.manager.enter(new_scene, _check_active=False, **kwargs)
|
||||
|
||||
async def retake(self, **kwargs: Any) -> None:
|
||||
"""
|
||||
This method allows to re-enter the current scene.
|
||||
|
||||
:param kwargs: Additional keyword arguments to pass to the scene.
|
||||
:return: None
|
||||
"""
|
||||
assert self.scene_config.state is not None, "Scene state is not specified"
|
||||
await self.goto(self.scene_config.state, **kwargs)
|
||||
|
||||
async def goto(self, scene: Union[Type[Scene], str], **kwargs: Any) -> None:
|
||||
"""
|
||||
The `goto` method transitions to a new scene.
|
||||
It first calls the `leave` method to perform any necessary cleanup
|
||||
in the current scene, then calls the `enter` event to enter the specified scene.
|
||||
|
||||
:param scene: The scene to transition to. Can be either a `Scene` instance
|
||||
or a string representing the scene.
|
||||
:param kwargs: Additional keyword arguments to pass to the `enter`
|
||||
method of the scene manager.
|
||||
:return: None
|
||||
"""
|
||||
await self.leave(**kwargs)
|
||||
await self.manager.enter(scene, _check_active=False, **kwargs)
|
||||
|
||||
async def _on_action(self, action: SceneAction, **kwargs: Any) -> bool:
|
||||
if not self.scene:
|
||||
raise SceneException("Scene is not initialized")
|
||||
|
||||
loggers.scene.debug("Call action %r in scene %r", action.name, self.scene_config.state)
|
||||
action_config = self.scene_config.actions.get(action, {})
|
||||
if not action_config:
|
||||
loggers.scene.debug(
|
||||
"Action %r not found in scene %r", action.name, self.scene_config.state
|
||||
)
|
||||
return False
|
||||
|
||||
event_type = self.update_type
|
||||
if event_type not in action_config:
|
||||
loggers.scene.debug(
|
||||
"Action %r for event %r not found in scene %r",
|
||||
action.name,
|
||||
event_type,
|
||||
self.scene_config.state,
|
||||
)
|
||||
return False
|
||||
|
||||
await action_config[event_type].call(self.scene, self.event, **{**self.data, **kwargs})
|
||||
return True
|
||||
|
||||
async def set_data(self, data: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Sets custom data in the current state.
|
||||
|
||||
:param data: A dictionary containing the custom data to be set in the current state.
|
||||
:return: None
|
||||
"""
|
||||
await self.state.set_data(data=data)
|
||||
|
||||
async def get_data(self) -> Dict[str, Any]:
|
||||
"""
|
||||
This method returns the data stored in the current state.
|
||||
|
||||
:return: A dictionary containing the data stored in the scene state.
|
||||
"""
|
||||
return await self.state.get_data()
|
||||
|
||||
@overload
|
||||
async def get_value(self, key: str) -> Optional[Any]:
|
||||
"""
|
||||
This method returns the value from key in the data of the current state.
|
||||
|
||||
:param key: The keyname of the item you want to return the value from.
|
||||
|
||||
:return: A dictionary containing the data stored in the scene state.
|
||||
"""
|
||||
pass
|
||||
|
||||
@overload
|
||||
async def get_value(self, key: str, default: Any) -> Any:
|
||||
"""
|
||||
This method returns the value from key in the data of the current state.
|
||||
|
||||
:param key: The keyname of the item you want to return the value from.
|
||||
:param default: Default value to return, if ``key`` was not found.
|
||||
|
||||
:return: A dictionary containing the data stored in the scene state.
|
||||
"""
|
||||
pass
|
||||
|
||||
async def get_value(self, key: str, default: Optional[Any] = None) -> Optional[Any]:
|
||||
return await self.state.get_value(key, default)
|
||||
|
||||
async def update_data(
|
||||
self, data: Optional[Dict[str, Any]] = None, **kwargs: Any
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
This method updates the data stored in the current state
|
||||
|
||||
:param data: Optional dictionary of data to update.
|
||||
:param kwargs: Additional key-value pairs of data to update.
|
||||
:return: Dictionary of updated data
|
||||
"""
|
||||
if data:
|
||||
kwargs.update(data)
|
||||
return await self.state.update_data(data=kwargs)
|
||||
|
||||
async def clear_data(self) -> None:
|
||||
"""
|
||||
Clears the data.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
await self.set_data({})
|
||||
|
||||
|
||||
class ScenesManager:
|
||||
"""
|
||||
The ScenesManager class is responsible for managing scenes in an application.
|
||||
It provides methods for entering and exiting scenes, as well as retrieving the active scene.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
registry: SceneRegistry,
|
||||
update_type: str,
|
||||
event: TelegramObject,
|
||||
state: FSMContext,
|
||||
data: Dict[str, Any],
|
||||
) -> None:
|
||||
self.registry = registry
|
||||
self.update_type = update_type
|
||||
self.event = event
|
||||
self.state = state
|
||||
self.data = data
|
||||
|
||||
self.history = HistoryManager(self.state)
|
||||
|
||||
async def _get_scene(self, scene_type: Optional[Union[Type[Scene], str]]) -> Scene:
|
||||
scene_type = self.registry.get(scene_type)
|
||||
return scene_type(
|
||||
wizard=SceneWizard(
|
||||
scene_config=scene_type.__scene_config__,
|
||||
manager=self,
|
||||
state=self.state,
|
||||
update_type=self.update_type,
|
||||
event=self.event,
|
||||
data=self.data,
|
||||
),
|
||||
)
|
||||
|
||||
async def _get_active_scene(self) -> Optional[Scene]:
|
||||
state = await self.state.get_state()
|
||||
try:
|
||||
return await self._get_scene(state)
|
||||
except SceneException:
|
||||
return None
|
||||
|
||||
async def enter(
|
||||
self,
|
||||
scene_type: Optional[Union[Type[Scene], str]],
|
||||
_check_active: bool = True,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
Enters the specified scene.
|
||||
|
||||
:param scene_type: Optional Type[Scene] or str representing the scene type to enter.
|
||||
:param _check_active: Optional bool indicating whether to check if
|
||||
there is an active scene to exit before entering the new scene. Defaults to True.
|
||||
:param kwargs: Additional keyword arguments to pass to the scene's wizard.enter() method.
|
||||
:return: None
|
||||
"""
|
||||
if _check_active:
|
||||
active_scene = await self._get_active_scene()
|
||||
if active_scene is not None:
|
||||
await active_scene.wizard.exit(**kwargs)
|
||||
|
||||
try:
|
||||
scene = await self._get_scene(scene_type)
|
||||
except SceneException:
|
||||
if scene_type is not None:
|
||||
raise
|
||||
await self.state.set_state(None)
|
||||
else:
|
||||
await scene.wizard.enter(**kwargs)
|
||||
|
||||
async def close(self, **kwargs: Any) -> None:
|
||||
"""
|
||||
Close method is used to exit the currently active scene in the ScenesManager.
|
||||
|
||||
:param kwargs: Additional keyword arguments passed to the scene's exit method.
|
||||
:return: None
|
||||
"""
|
||||
scene = await self._get_active_scene()
|
||||
if not scene:
|
||||
return
|
||||
await scene.wizard.exit(**kwargs)
|
||||
|
||||
|
||||
class SceneRegistry:
|
||||
"""
|
||||
A class that represents a registry for scenes in a Telegram bot.
|
||||
"""
|
||||
|
||||
def __init__(self, router: Router, register_on_add: bool = True) -> None:
|
||||
"""
|
||||
Initialize a new instance of the SceneRegistry class.
|
||||
|
||||
:param router: The router instance used for scene registration.
|
||||
:param register_on_add: Whether to register the scenes to the router when they are added.
|
||||
"""
|
||||
self.router = router
|
||||
self.register_on_add = register_on_add
|
||||
|
||||
self._scenes: Dict[Optional[str], Type[Scene]] = {}
|
||||
self._setup_middleware(router)
|
||||
|
||||
def _setup_middleware(self, router: Router) -> None:
|
||||
if isinstance(router, Dispatcher):
|
||||
# Small optimization for Dispatcher
|
||||
# - we don't need to set up middleware for all observers
|
||||
router.update.outer_middleware(self._update_middleware)
|
||||
return
|
||||
|
||||
for observer in router.observers.values():
|
||||
if observer.event_name in {"update", "error"}:
|
||||
continue
|
||||
observer.outer_middleware(self._middleware)
|
||||
|
||||
async def _update_middleware(
|
||||
self,
|
||||
handler: NextMiddlewareType[TelegramObject],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
assert isinstance(event, Update), "Event must be an Update instance"
|
||||
|
||||
data["scenes"] = ScenesManager(
|
||||
registry=self,
|
||||
update_type=event.event_type,
|
||||
event=event.event,
|
||||
state=data["state"],
|
||||
data=data,
|
||||
)
|
||||
return await handler(event, data)
|
||||
|
||||
async def _middleware(
|
||||
self,
|
||||
handler: NextMiddlewareType[TelegramObject],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
update: Update = data["event_update"]
|
||||
data["scenes"] = ScenesManager(
|
||||
registry=self,
|
||||
update_type=update.event_type,
|
||||
event=event,
|
||||
state=data["state"],
|
||||
data=data,
|
||||
)
|
||||
return await handler(event, data)
|
||||
|
||||
def add(self, *scenes: Type[Scene], router: Optional[Router] = None) -> None:
|
||||
"""
|
||||
This method adds the specified scenes to the registry
|
||||
and optionally registers it to the router.
|
||||
|
||||
If a scene with the same state already exists in the registry, a SceneException is raised.
|
||||
|
||||
.. warning::
|
||||
|
||||
If the router is not specified, the scenes will not be registered to the router.
|
||||
You will need to include the scenes manually to the router or use the register method.
|
||||
|
||||
:param scenes: A variable length parameter that accepts one or more types of scenes.
|
||||
These scenes are instances of the Scene class.
|
||||
:param router: An optional parameter that specifies the router
|
||||
to which the scenes should be added.
|
||||
:return: None
|
||||
"""
|
||||
if not scenes:
|
||||
raise ValueError("At least one scene must be specified")
|
||||
|
||||
for scene in scenes:
|
||||
if scene.__scene_config__.state in self._scenes:
|
||||
raise SceneException(
|
||||
f"Scene with state {scene.__scene_config__.state!r} already exists"
|
||||
)
|
||||
|
||||
self._scenes[scene.__scene_config__.state] = scene
|
||||
|
||||
if router:
|
||||
router.include_router(scene.as_router())
|
||||
elif self.register_on_add:
|
||||
self.router.include_router(scene.as_router())
|
||||
|
||||
def register(self, *scenes: Type[Scene]) -> None:
|
||||
"""
|
||||
Registers one or more scenes to the SceneRegistry.
|
||||
|
||||
:param scenes: One or more scene classes to register.
|
||||
:return: None
|
||||
"""
|
||||
self.add(*scenes, router=self.router)
|
||||
|
||||
def get(self, scene: Optional[Union[Type[Scene], str]]) -> Type[Scene]:
|
||||
"""
|
||||
This method returns the registered Scene object for the specified scene.
|
||||
The scene parameter can be either a Scene object or a string representing
|
||||
the name of the scene. If a Scene object is provided, the state attribute
|
||||
of the SceneConfig object associated with the Scene object will be used as the scene name.
|
||||
If None or an invalid type is provided, a SceneException will be raised.
|
||||
|
||||
If the specified scene is not registered in the SceneRegistry object,
|
||||
a SceneException will be raised.
|
||||
|
||||
:param scene: A Scene object or a string representing the name of the scene.
|
||||
:return: The registered Scene object corresponding to the given scene parameter.
|
||||
|
||||
"""
|
||||
if inspect.isclass(scene) and issubclass(scene, Scene):
|
||||
scene = scene.__scene_config__.state
|
||||
if isinstance(scene, State):
|
||||
scene = scene.state
|
||||
if scene is not None and not isinstance(scene, str):
|
||||
raise SceneException("Scene must be a subclass of Scene or a string")
|
||||
|
||||
try:
|
||||
return self._scenes[scene]
|
||||
except KeyError:
|
||||
raise SceneException(f"Scene {scene!r} is not registered")
|
||||
|
||||
|
||||
@dataclass
|
||||
class After:
|
||||
action: SceneAction
|
||||
scene: Optional[Union[Type[Scene], str]] = None
|
||||
|
||||
@classmethod
|
||||
def exit(cls) -> After:
|
||||
return cls(action=SceneAction.exit)
|
||||
|
||||
@classmethod
|
||||
def back(cls) -> After:
|
||||
return cls(action=SceneAction.back)
|
||||
|
||||
@classmethod
|
||||
def goto(cls, scene: Optional[Union[Type[Scene], str]]) -> After:
|
||||
return cls(action=SceneAction.enter, scene=scene)
|
||||
|
||||
|
||||
class ObserverMarker:
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
*filters: CallbackType,
|
||||
after: Optional[After] = None,
|
||||
) -> ObserverDecorator:
|
||||
return ObserverDecorator(
|
||||
self.name,
|
||||
filters,
|
||||
after=after,
|
||||
)
|
||||
|
||||
def enter(self, *filters: CallbackType) -> ObserverDecorator:
|
||||
return ObserverDecorator(self.name, filters, action=SceneAction.enter)
|
||||
|
||||
def leave(self) -> ObserverDecorator:
|
||||
return ObserverDecorator(self.name, (), action=SceneAction.leave)
|
||||
|
||||
def exit(self) -> ObserverDecorator:
|
||||
return ObserverDecorator(self.name, (), action=SceneAction.exit)
|
||||
|
||||
def back(self) -> ObserverDecorator:
|
||||
return ObserverDecorator(self.name, (), action=SceneAction.back)
|
||||
|
||||
|
||||
class OnMarker:
|
||||
"""
|
||||
The `OnMarker` class is used as a marker class to define different
|
||||
types of events in the Scenes.
|
||||
|
||||
Attributes:
|
||||
|
||||
- :code:`message`: Event marker for handling `Message` events.
|
||||
- :code:`edited_message`: Event marker for handling edited `Message` events.
|
||||
- :code:`channel_post`: Event marker for handling channel `Post` events.
|
||||
- :code:`edited_channel_post`: Event marker for handling edited channel `Post` events.
|
||||
- :code:`inline_query`: Event marker for handling `InlineQuery` events.
|
||||
- :code:`chosen_inline_result`: Event marker for handling chosen `InlineResult` events.
|
||||
- :code:`callback_query`: Event marker for handling `CallbackQuery` events.
|
||||
- :code:`shipping_query`: Event marker for handling `ShippingQuery` events.
|
||||
- :code:`pre_checkout_query`: Event marker for handling `PreCheckoutQuery` events.
|
||||
- :code:`poll`: Event marker for handling `Poll` events.
|
||||
- :code:`poll_answer`: Event marker for handling `PollAnswer` events.
|
||||
- :code:`my_chat_member`: Event marker for handling my chat `Member` events.
|
||||
- :code:`chat_member`: Event marker for handling chat `Member` events.
|
||||
- :code:`chat_join_request`: Event marker for handling chat `JoinRequest` events.
|
||||
- :code:`error`: Event marker for handling `Error` events.
|
||||
|
||||
.. note::
|
||||
|
||||
This is a marker class and does not contain any methods or implementation logic.
|
||||
"""
|
||||
|
||||
message = ObserverMarker("message")
|
||||
edited_message = ObserverMarker("edited_message")
|
||||
channel_post = ObserverMarker("channel_post")
|
||||
edited_channel_post = ObserverMarker("edited_channel_post")
|
||||
inline_query = ObserverMarker("inline_query")
|
||||
chosen_inline_result = ObserverMarker("chosen_inline_result")
|
||||
callback_query = ObserverMarker("callback_query")
|
||||
shipping_query = ObserverMarker("shipping_query")
|
||||
pre_checkout_query = ObserverMarker("pre_checkout_query")
|
||||
poll = ObserverMarker("poll")
|
||||
poll_answer = ObserverMarker("poll_answer")
|
||||
my_chat_member = ObserverMarker("my_chat_member")
|
||||
chat_member = ObserverMarker("chat_member")
|
||||
chat_join_request = ObserverMarker("chat_join_request")
|
||||
|
||||
|
||||
on = OnMarker()
|
172
myenv/Lib/site-packages/aiogram/fsm/state.py
Normal file
172
myenv/Lib/site-packages/aiogram/fsm/state.py
Normal file
@@ -0,0 +1,172 @@
|
||||
import inspect
|
||||
from typing import Any, Iterator, Optional, Tuple, Type, no_type_check
|
||||
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
||||
class State:
|
||||
"""
|
||||
State object
|
||||
"""
|
||||
|
||||
def __init__(self, state: Optional[str] = None, group_name: Optional[str] = None) -> None:
|
||||
self._state = state
|
||||
self._group_name = group_name
|
||||
self._group: Optional[Type[StatesGroup]] = None
|
||||
|
||||
@property
|
||||
def group(self) -> "Type[StatesGroup]":
|
||||
if not self._group:
|
||||
raise RuntimeError("This state is not in any group.")
|
||||
return self._group
|
||||
|
||||
@property
|
||||
def state(self) -> Optional[str]:
|
||||
if self._state is None or self._state == "*":
|
||||
return self._state
|
||||
|
||||
if self._group_name is None and self._group:
|
||||
group = self._group.__full_group_name__
|
||||
elif self._group_name:
|
||||
group = self._group_name
|
||||
else:
|
||||
group = "@"
|
||||
|
||||
return f"{group}:{self._state}"
|
||||
|
||||
def set_parent(self, group: "Type[StatesGroup]") -> None:
|
||||
if not issubclass(group, StatesGroup):
|
||||
raise ValueError("Group must be subclass of StatesGroup")
|
||||
self._group = group
|
||||
|
||||
def __set_name__(self, owner: "Type[StatesGroup]", name: str) -> None:
|
||||
if self._state is None:
|
||||
self._state = name
|
||||
self.set_parent(owner)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"<State '{self.state or ''}'>"
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def __call__(self, event: TelegramObject, raw_state: Optional[str] = None) -> bool:
|
||||
if self.state == "*":
|
||||
return True
|
||||
return raw_state == self.state
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
return self.state == other.state
|
||||
if isinstance(other, str):
|
||||
return self.state == other
|
||||
return NotImplemented
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.state)
|
||||
|
||||
|
||||
class StatesGroupMeta(type):
|
||||
__parent__: "Optional[Type[StatesGroup]]"
|
||||
__childs__: "Tuple[Type[StatesGroup], ...]"
|
||||
__states__: Tuple[State, ...]
|
||||
__state_names__: Tuple[str, ...]
|
||||
__all_childs__: Tuple[Type["StatesGroup"], ...]
|
||||
__all_states__: Tuple[State, ...]
|
||||
__all_states_names__: Tuple[str, ...]
|
||||
|
||||
@no_type_check
|
||||
def __new__(mcs, name, bases, namespace, **kwargs):
|
||||
cls = super().__new__(mcs, name, bases, namespace)
|
||||
|
||||
states = []
|
||||
childs = []
|
||||
|
||||
for name, arg in namespace.items():
|
||||
if isinstance(arg, State):
|
||||
states.append(arg)
|
||||
elif inspect.isclass(arg) and issubclass(arg, StatesGroup):
|
||||
child = cls._prepare_child(arg)
|
||||
childs.append(child)
|
||||
|
||||
cls.__parent__ = None
|
||||
cls.__childs__ = tuple(childs)
|
||||
cls.__states__ = tuple(states)
|
||||
cls.__state_names__ = tuple(state.state for state in states)
|
||||
|
||||
cls.__all_childs__ = cls._get_all_childs()
|
||||
cls.__all_states__ = cls._get_all_states()
|
||||
|
||||
# In order to ensure performance, we calculate this parameter
|
||||
# in advance already during the production of the class.
|
||||
# Depending on the relationship, it should be recalculated
|
||||
cls.__all_states_names__ = cls._get_all_states_names()
|
||||
|
||||
return cls
|
||||
|
||||
@property
|
||||
def __full_group_name__(cls) -> str:
|
||||
if cls.__parent__:
|
||||
return ".".join((cls.__parent__.__full_group_name__, cls.__name__))
|
||||
return cls.__name__
|
||||
|
||||
def _prepare_child(cls, child: Type["StatesGroup"]) -> Type["StatesGroup"]:
|
||||
"""Prepare child.
|
||||
|
||||
While adding `cls` for its children, we also need to recalculate
|
||||
the parameter `__all_states_names__` for each child
|
||||
`StatesGroup`. Since the child class appears before the
|
||||
parent, at the time of adding the parent, the child's
|
||||
`__all_states_names__` is already recorded without taking into
|
||||
account the name of current parent.
|
||||
"""
|
||||
child.__parent__ = cls # type: ignore[assignment]
|
||||
child.__all_states_names__ = child._get_all_states_names()
|
||||
return child
|
||||
|
||||
def _get_all_childs(cls) -> Tuple[Type["StatesGroup"], ...]:
|
||||
result = cls.__childs__
|
||||
for child in cls.__childs__:
|
||||
result += child.__childs__
|
||||
return result
|
||||
|
||||
def _get_all_states(cls) -> Tuple[State, ...]:
|
||||
result = cls.__states__
|
||||
for group in cls.__childs__:
|
||||
result += group.__all_states__
|
||||
return result
|
||||
|
||||
def _get_all_states_names(cls) -> Tuple[str, ...]:
|
||||
return tuple(state.state for state in cls.__all_states__ if state.state)
|
||||
|
||||
def __contains__(cls, item: Any) -> bool:
|
||||
if isinstance(item, str):
|
||||
return item in cls.__all_states_names__
|
||||
if isinstance(item, State):
|
||||
return item in cls.__all_states__
|
||||
if isinstance(item, StatesGroupMeta):
|
||||
return item in cls.__all_childs__
|
||||
return False
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"<StatesGroup '{self.__full_group_name__}'>"
|
||||
|
||||
def __iter__(self) -> Iterator[State]:
|
||||
return iter(self.__all_states__)
|
||||
|
||||
|
||||
class StatesGroup(metaclass=StatesGroupMeta):
|
||||
@classmethod
|
||||
def get_root(cls) -> Type["StatesGroup"]:
|
||||
if cls.__parent__ is None:
|
||||
return cls
|
||||
return cls.__parent__.get_root()
|
||||
|
||||
def __call__(self, event: TelegramObject, raw_state: Optional[str] = None) -> bool:
|
||||
return raw_state in type(self).__all_states_names__
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"StatesGroup {type(self).__full_group_name__}"
|
||||
|
||||
|
||||
default_state = State()
|
||||
any_state = State(state="*")
|
212
myenv/Lib/site-packages/aiogram/fsm/storage/base.py
Normal file
212
myenv/Lib/site-packages/aiogram/fsm/storage/base.py
Normal file
@@ -0,0 +1,212 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, AsyncGenerator, Dict, Literal, Optional, Union, overload
|
||||
|
||||
from aiogram.fsm.state import State
|
||||
|
||||
StateType = Optional[Union[str, State]]
|
||||
|
||||
DEFAULT_DESTINY = "default"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StorageKey:
|
||||
bot_id: int
|
||||
chat_id: int
|
||||
user_id: int
|
||||
thread_id: Optional[int] = None
|
||||
business_connection_id: Optional[str] = None
|
||||
destiny: str = DEFAULT_DESTINY
|
||||
|
||||
|
||||
class KeyBuilder(ABC):
|
||||
"""Base class for key builder."""
|
||||
|
||||
@abstractmethod
|
||||
def build(
|
||||
self,
|
||||
key: StorageKey,
|
||||
part: Optional[Literal["data", "state", "lock"]] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Build key to be used in storage's db queries
|
||||
|
||||
:param key: contextual key
|
||||
:param part: part of the record
|
||||
:return: key to be used in storage's db queries
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DefaultKeyBuilder(KeyBuilder):
|
||||
"""
|
||||
Simple key builder with default prefix.
|
||||
|
||||
Generates a colon-joined string with prefix, chat_id, user_id,
|
||||
optional bot_id, business_connection_id, destiny and field.
|
||||
|
||||
Format:
|
||||
:code:`<prefix>:<bot_id?>:<business_connection_id?>:<chat_id>:<user_id>:<destiny?>:<field?>`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
prefix: str = "fsm",
|
||||
separator: str = ":",
|
||||
with_bot_id: bool = False,
|
||||
with_business_connection_id: bool = False,
|
||||
with_destiny: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
:param prefix: prefix for all records
|
||||
:param separator: separator
|
||||
:param with_bot_id: include Bot id in the key
|
||||
:param with_business_connection_id: include business connection id
|
||||
:param with_destiny: include destiny key
|
||||
"""
|
||||
self.prefix = prefix
|
||||
self.separator = separator
|
||||
self.with_bot_id = with_bot_id
|
||||
self.with_business_connection_id = with_business_connection_id
|
||||
self.with_destiny = with_destiny
|
||||
|
||||
def build(
|
||||
self,
|
||||
key: StorageKey,
|
||||
part: Optional[Literal["data", "state", "lock"]] = None,
|
||||
) -> str:
|
||||
parts = [self.prefix]
|
||||
if self.with_bot_id:
|
||||
parts.append(str(key.bot_id))
|
||||
if self.with_business_connection_id and key.business_connection_id:
|
||||
parts.append(str(key.business_connection_id))
|
||||
parts.append(str(key.chat_id))
|
||||
if key.thread_id:
|
||||
parts.append(str(key.thread_id))
|
||||
parts.append(str(key.user_id))
|
||||
if self.with_destiny:
|
||||
parts.append(key.destiny)
|
||||
elif key.destiny != DEFAULT_DESTINY:
|
||||
error_message = (
|
||||
"Default key builder is not configured to use key destiny other than the default."
|
||||
"\n\nProbably, you should set `with_destiny=True` in for DefaultKeyBuilder."
|
||||
)
|
||||
raise ValueError(error_message)
|
||||
if part:
|
||||
parts.append(part)
|
||||
return self.separator.join(parts)
|
||||
|
||||
|
||||
class BaseStorage(ABC):
|
||||
"""
|
||||
Base class for all FSM storages
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def set_state(self, key: StorageKey, state: StateType = None) -> None:
|
||||
"""
|
||||
Set state for specified key
|
||||
|
||||
:param key: storage key
|
||||
:param state: new state
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_state(self, key: StorageKey) -> Optional[str]:
|
||||
"""
|
||||
Get key state
|
||||
|
||||
:param key: storage key
|
||||
:return: current state
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_data(self, key: StorageKey, data: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Write data (replace)
|
||||
|
||||
:param key: storage key
|
||||
:param data: new data
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_data(self, key: StorageKey) -> Dict[str, Any]:
|
||||
"""
|
||||
Get current data for key
|
||||
|
||||
:param key: storage key
|
||||
:return: current data
|
||||
"""
|
||||
pass
|
||||
|
||||
@overload
|
||||
async def get_value(self, storage_key: StorageKey, dict_key: str) -> Optional[Any]:
|
||||
"""
|
||||
Get single value from data by key
|
||||
|
||||
:param storage_key: storage key
|
||||
:param dict_key: value key
|
||||
:return: value stored in key of dict or ``None``
|
||||
"""
|
||||
pass
|
||||
|
||||
@overload
|
||||
async def get_value(self, storage_key: StorageKey, dict_key: str, default: Any) -> Any:
|
||||
"""
|
||||
Get single value from data by key
|
||||
|
||||
:param storage_key: storage key
|
||||
:param dict_key: value key
|
||||
:param default: default value to return
|
||||
:return: value stored in key of dict or default
|
||||
"""
|
||||
pass
|
||||
|
||||
async def get_value(
|
||||
self, storage_key: StorageKey, dict_key: str, default: Optional[Any] = None
|
||||
) -> Optional[Any]:
|
||||
data = await self.get_data(storage_key)
|
||||
return data.get(dict_key, default)
|
||||
|
||||
async def update_data(self, key: StorageKey, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Update date in the storage for key (like dict.update)
|
||||
|
||||
:param key: storage key
|
||||
:param data: partial data
|
||||
:return: new data
|
||||
"""
|
||||
current_data = await self.get_data(key=key)
|
||||
current_data.update(data)
|
||||
await self.set_data(key=key, data=current_data)
|
||||
return current_data.copy()
|
||||
|
||||
@abstractmethod
|
||||
async def close(self) -> None: # pragma: no cover
|
||||
"""
|
||||
Close storage (database connection, file or etc.)
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BaseEventIsolation(ABC):
|
||||
@abstractmethod
|
||||
@asynccontextmanager
|
||||
async def lock(self, key: StorageKey) -> AsyncGenerator[None, None]:
|
||||
"""
|
||||
Isolate events with lock.
|
||||
Will be used as context manager
|
||||
|
||||
:param key: storage key
|
||||
:return: An async generator
|
||||
"""
|
||||
yield None
|
||||
|
||||
@abstractmethod
|
||||
async def close(self) -> None:
|
||||
pass
|
87
myenv/Lib/site-packages/aiogram/fsm/storage/memory.py
Normal file
87
myenv/Lib/site-packages/aiogram/fsm/storage/memory.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from asyncio import Lock
|
||||
from collections import defaultdict
|
||||
from contextlib import asynccontextmanager
|
||||
from copy import copy
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, AsyncGenerator, DefaultDict, Dict, Hashable, Optional, overload
|
||||
|
||||
from aiogram.fsm.state import State
|
||||
from aiogram.fsm.storage.base import (
|
||||
BaseEventIsolation,
|
||||
BaseStorage,
|
||||
StateType,
|
||||
StorageKey,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MemoryStorageRecord:
|
||||
data: Dict[str, Any] = field(default_factory=dict)
|
||||
state: Optional[str] = None
|
||||
|
||||
|
||||
class MemoryStorage(BaseStorage):
|
||||
"""
|
||||
Default FSM storage, stores all data in :class:`dict` and loss everything on shutdown
|
||||
|
||||
.. warning::
|
||||
|
||||
Is not recommended using in production in due to you will lose all data
|
||||
when your bot restarts
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.storage: DefaultDict[StorageKey, MemoryStorageRecord] = defaultdict(
|
||||
MemoryStorageRecord
|
||||
)
|
||||
|
||||
async def close(self) -> None:
|
||||
pass
|
||||
|
||||
async def set_state(self, key: StorageKey, state: StateType = None) -> None:
|
||||
self.storage[key].state = state.state if isinstance(state, State) else state
|
||||
|
||||
async def get_state(self, key: StorageKey) -> Optional[str]:
|
||||
return self.storage[key].state
|
||||
|
||||
async def set_data(self, key: StorageKey, data: Dict[str, Any]) -> None:
|
||||
self.storage[key].data = data.copy()
|
||||
|
||||
async def get_data(self, key: StorageKey) -> Dict[str, Any]:
|
||||
return self.storage[key].data.copy()
|
||||
|
||||
@overload
|
||||
async def get_value(self, storage_key: StorageKey, dict_key: str) -> Optional[Any]: ...
|
||||
|
||||
@overload
|
||||
async def get_value(self, storage_key: StorageKey, dict_key: str, default: Any) -> Any: ...
|
||||
|
||||
async def get_value(
|
||||
self, storage_key: StorageKey, dict_key: str, default: Optional[Any] = None
|
||||
) -> Optional[Any]:
|
||||
data = self.storage[storage_key].data
|
||||
return copy(data.get(dict_key, default))
|
||||
|
||||
|
||||
class DisabledEventIsolation(BaseEventIsolation):
|
||||
@asynccontextmanager
|
||||
async def lock(self, key: StorageKey) -> AsyncGenerator[None, None]:
|
||||
yield
|
||||
|
||||
async def close(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class SimpleEventIsolation(BaseEventIsolation):
|
||||
def __init__(self) -> None:
|
||||
# TODO: Unused locks cleaner is needed
|
||||
self._locks: DefaultDict[Hashable, Lock] = defaultdict(Lock)
|
||||
|
||||
@asynccontextmanager
|
||||
async def lock(self, key: StorageKey) -> AsyncGenerator[None, None]:
|
||||
lock = self._locks[key]
|
||||
async with lock:
|
||||
yield
|
||||
|
||||
async def close(self) -> None:
|
||||
self._locks.clear()
|
130
myenv/Lib/site-packages/aiogram/fsm/storage/mongo.py
Normal file
130
myenv/Lib/site-packages/aiogram/fsm/storage/mongo.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from typing import Any, Dict, Optional, cast
|
||||
|
||||
from motor.motor_asyncio import AsyncIOMotorClient
|
||||
|
||||
from aiogram.fsm.state import State
|
||||
from aiogram.fsm.storage.base import (
|
||||
BaseStorage,
|
||||
DefaultKeyBuilder,
|
||||
KeyBuilder,
|
||||
StateType,
|
||||
StorageKey,
|
||||
)
|
||||
|
||||
|
||||
class MongoStorage(BaseStorage):
|
||||
"""
|
||||
MongoDB storage required :code:`motor` package installed (:code:`pip install motor`)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: AsyncIOMotorClient,
|
||||
key_builder: Optional[KeyBuilder] = None,
|
||||
db_name: str = "aiogram_fsm",
|
||||
collection_name: str = "states_and_data",
|
||||
) -> None:
|
||||
"""
|
||||
:param client: Instance of AsyncIOMotorClient
|
||||
:param key_builder: builder that helps to convert contextual key to string
|
||||
:param db_name: name of the MongoDB database for FSM
|
||||
:param collection_name: name of the collection for storing FSM states and data
|
||||
"""
|
||||
if key_builder is None:
|
||||
key_builder = DefaultKeyBuilder()
|
||||
self._client = client
|
||||
self._database = self._client[db_name]
|
||||
self._collection = self._database[collection_name]
|
||||
self._key_builder = key_builder
|
||||
|
||||
@classmethod
|
||||
def from_url(
|
||||
cls, url: str, connection_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any
|
||||
) -> "MongoStorage":
|
||||
"""
|
||||
Create an instance of :class:`MongoStorage` with specifying the connection string
|
||||
|
||||
:param url: for example :code:`mongodb://user:password@host:port`
|
||||
:param connection_kwargs: see :code:`motor` docs
|
||||
:param kwargs: arguments to be passed to :class:`MongoStorage`
|
||||
:return: an instance of :class:`MongoStorage`
|
||||
"""
|
||||
if connection_kwargs is None:
|
||||
connection_kwargs = {}
|
||||
client = AsyncIOMotorClient(url, **connection_kwargs)
|
||||
return cls(client=client, **kwargs)
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Cleanup client resources and disconnect from MongoDB."""
|
||||
self._client.close()
|
||||
|
||||
def resolve_state(self, value: StateType) -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, State):
|
||||
return value.state
|
||||
return str(value)
|
||||
|
||||
async def set_state(self, key: StorageKey, state: StateType = None) -> None:
|
||||
document_id = self._key_builder.build(key)
|
||||
if state is None:
|
||||
updated = await self._collection.find_one_and_update(
|
||||
filter={"_id": document_id},
|
||||
update={"$unset": {"state": 1}},
|
||||
projection={"_id": 0},
|
||||
return_document=True,
|
||||
)
|
||||
if updated == {}:
|
||||
await self._collection.delete_one({"_id": document_id})
|
||||
else:
|
||||
await self._collection.update_one(
|
||||
filter={"_id": document_id},
|
||||
update={"$set": {"state": self.resolve_state(state)}},
|
||||
upsert=True,
|
||||
)
|
||||
|
||||
async def get_state(self, key: StorageKey) -> Optional[str]:
|
||||
document_id = self._key_builder.build(key)
|
||||
document = await self._collection.find_one({"_id": document_id})
|
||||
if document is None:
|
||||
return None
|
||||
return document.get("state")
|
||||
|
||||
async def set_data(self, key: StorageKey, data: Dict[str, Any]) -> None:
|
||||
document_id = self._key_builder.build(key)
|
||||
if not data:
|
||||
updated = await self._collection.find_one_and_update(
|
||||
filter={"_id": document_id},
|
||||
update={"$unset": {"data": 1}},
|
||||
projection={"_id": 0},
|
||||
return_document=True,
|
||||
)
|
||||
if updated == {}:
|
||||
await self._collection.delete_one({"_id": document_id})
|
||||
else:
|
||||
await self._collection.update_one(
|
||||
filter={"_id": document_id},
|
||||
update={"$set": {"data": data}},
|
||||
upsert=True,
|
||||
)
|
||||
|
||||
async def get_data(self, key: StorageKey) -> Dict[str, Any]:
|
||||
document_id = self._key_builder.build(key)
|
||||
document = await self._collection.find_one({"_id": document_id})
|
||||
if document is None or not document.get("data"):
|
||||
return {}
|
||||
return cast(Dict[str, Any], document["data"])
|
||||
|
||||
async def update_data(self, key: StorageKey, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
document_id = self._key_builder.build(key)
|
||||
update_with = {f"data.{key}": value for key, value in data.items()}
|
||||
update_result = await self._collection.find_one_and_update(
|
||||
filter={"_id": document_id},
|
||||
update={"$set": update_with},
|
||||
upsert=True,
|
||||
return_document=True,
|
||||
projection={"_id": 0},
|
||||
)
|
||||
if not update_result:
|
||||
await self._collection.delete_one({"_id": document_id})
|
||||
return update_result.get("data", {})
|
169
myenv/Lib/site-packages/aiogram/fsm/storage/redis.py
Normal file
169
myenv/Lib/site-packages/aiogram/fsm/storage/redis.py
Normal file
@@ -0,0 +1,169 @@
|
||||
import json
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any, AsyncGenerator, Callable, Dict, Optional, cast
|
||||
|
||||
from redis.asyncio.client import Redis
|
||||
from redis.asyncio.connection import ConnectionPool
|
||||
from redis.asyncio.lock import Lock
|
||||
from redis.typing import ExpiryT
|
||||
|
||||
from aiogram.fsm.state import State
|
||||
from aiogram.fsm.storage.base import (
|
||||
BaseEventIsolation,
|
||||
BaseStorage,
|
||||
DefaultKeyBuilder,
|
||||
KeyBuilder,
|
||||
StateType,
|
||||
StorageKey,
|
||||
)
|
||||
|
||||
DEFAULT_REDIS_LOCK_KWARGS = {"timeout": 60}
|
||||
_JsonLoads = Callable[..., Any]
|
||||
_JsonDumps = Callable[..., str]
|
||||
|
||||
|
||||
class RedisStorage(BaseStorage):
|
||||
"""
|
||||
Redis storage required :code:`redis` package installed (:code:`pip install redis`)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
redis: Redis,
|
||||
key_builder: Optional[KeyBuilder] = None,
|
||||
state_ttl: Optional[ExpiryT] = None,
|
||||
data_ttl: Optional[ExpiryT] = None,
|
||||
json_loads: _JsonLoads = json.loads,
|
||||
json_dumps: _JsonDumps = json.dumps,
|
||||
) -> None:
|
||||
"""
|
||||
:param redis: Instance of Redis connection
|
||||
:param key_builder: builder that helps to convert contextual key to string
|
||||
:param state_ttl: TTL for state records
|
||||
:param data_ttl: TTL for data records
|
||||
"""
|
||||
if key_builder is None:
|
||||
key_builder = DefaultKeyBuilder()
|
||||
self.redis = redis
|
||||
self.key_builder = key_builder
|
||||
self.state_ttl = state_ttl
|
||||
self.data_ttl = data_ttl
|
||||
self.json_loads = json_loads
|
||||
self.json_dumps = json_dumps
|
||||
|
||||
@classmethod
|
||||
def from_url(
|
||||
cls, url: str, connection_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any
|
||||
) -> "RedisStorage":
|
||||
"""
|
||||
Create an instance of :class:`RedisStorage` with specifying the connection string
|
||||
|
||||
:param url: for example :code:`redis://user:password@host:port/db`
|
||||
:param connection_kwargs: see :code:`redis` docs
|
||||
:param kwargs: arguments to be passed to :class:`RedisStorage`
|
||||
:return: an instance of :class:`RedisStorage`
|
||||
"""
|
||||
if connection_kwargs is None:
|
||||
connection_kwargs = {}
|
||||
pool = ConnectionPool.from_url(url, **connection_kwargs)
|
||||
redis = Redis(connection_pool=pool)
|
||||
return cls(redis=redis, **kwargs)
|
||||
|
||||
def create_isolation(self, **kwargs: Any) -> "RedisEventIsolation":
|
||||
return RedisEventIsolation(redis=self.redis, key_builder=self.key_builder, **kwargs)
|
||||
|
||||
async def close(self) -> None:
|
||||
await self.redis.aclose(close_connection_pool=True)
|
||||
|
||||
async def set_state(
|
||||
self,
|
||||
key: StorageKey,
|
||||
state: StateType = None,
|
||||
) -> None:
|
||||
redis_key = self.key_builder.build(key, "state")
|
||||
if state is None:
|
||||
await self.redis.delete(redis_key)
|
||||
else:
|
||||
await self.redis.set(
|
||||
redis_key,
|
||||
cast(str, state.state if isinstance(state, State) else state),
|
||||
ex=self.state_ttl,
|
||||
)
|
||||
|
||||
async def get_state(
|
||||
self,
|
||||
key: StorageKey,
|
||||
) -> Optional[str]:
|
||||
redis_key = self.key_builder.build(key, "state")
|
||||
value = await self.redis.get(redis_key)
|
||||
if isinstance(value, bytes):
|
||||
return value.decode("utf-8")
|
||||
return cast(Optional[str], value)
|
||||
|
||||
async def set_data(
|
||||
self,
|
||||
key: StorageKey,
|
||||
data: Dict[str, Any],
|
||||
) -> None:
|
||||
redis_key = self.key_builder.build(key, "data")
|
||||
if not data:
|
||||
await self.redis.delete(redis_key)
|
||||
return
|
||||
await self.redis.set(
|
||||
redis_key,
|
||||
self.json_dumps(data),
|
||||
ex=self.data_ttl,
|
||||
)
|
||||
|
||||
async def get_data(
|
||||
self,
|
||||
key: StorageKey,
|
||||
) -> Dict[str, Any]:
|
||||
redis_key = self.key_builder.build(key, "data")
|
||||
value = await self.redis.get(redis_key)
|
||||
if value is None:
|
||||
return {}
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode("utf-8")
|
||||
return cast(Dict[str, Any], self.json_loads(value))
|
||||
|
||||
|
||||
class RedisEventIsolation(BaseEventIsolation):
|
||||
def __init__(
|
||||
self,
|
||||
redis: Redis,
|
||||
key_builder: Optional[KeyBuilder] = None,
|
||||
lock_kwargs: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
if key_builder is None:
|
||||
key_builder = DefaultKeyBuilder()
|
||||
if lock_kwargs is None:
|
||||
lock_kwargs = DEFAULT_REDIS_LOCK_KWARGS
|
||||
self.redis = redis
|
||||
self.key_builder = key_builder
|
||||
self.lock_kwargs = lock_kwargs
|
||||
|
||||
@classmethod
|
||||
def from_url(
|
||||
cls,
|
||||
url: str,
|
||||
connection_kwargs: Optional[Dict[str, Any]] = None,
|
||||
**kwargs: Any,
|
||||
) -> "RedisEventIsolation":
|
||||
if connection_kwargs is None:
|
||||
connection_kwargs = {}
|
||||
pool = ConnectionPool.from_url(url, **connection_kwargs)
|
||||
redis = Redis(connection_pool=pool)
|
||||
return cls(redis=redis, **kwargs)
|
||||
|
||||
@asynccontextmanager
|
||||
async def lock(
|
||||
self,
|
||||
key: StorageKey,
|
||||
) -> AsyncGenerator[None, None]:
|
||||
redis_key = self.key_builder.build(key, "lock")
|
||||
async with self.redis.lock(name=redis_key, **self.lock_kwargs, lock_class=Lock):
|
||||
yield None
|
||||
|
||||
async def close(self) -> None:
|
||||
pass
|
37
myenv/Lib/site-packages/aiogram/fsm/strategy.py
Normal file
37
myenv/Lib/site-packages/aiogram/fsm/strategy.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from enum import Enum, auto
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
class FSMStrategy(Enum):
|
||||
"""
|
||||
FSM strategy for storage key generation.
|
||||
"""
|
||||
|
||||
USER_IN_CHAT = auto()
|
||||
"""State will be stored for each user in chat."""
|
||||
CHAT = auto()
|
||||
"""State will be stored for each chat globally without separating by users."""
|
||||
GLOBAL_USER = auto()
|
||||
"""State will be stored globally for each user globally."""
|
||||
USER_IN_TOPIC = auto()
|
||||
"""State will be stored for each user in chat and topic."""
|
||||
CHAT_TOPIC = auto()
|
||||
"""State will be stored for each chat and topic, but not separated by users."""
|
||||
|
||||
|
||||
def apply_strategy(
|
||||
strategy: FSMStrategy,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
thread_id: Optional[int] = None,
|
||||
) -> Tuple[int, int, Optional[int]]:
|
||||
if strategy == FSMStrategy.CHAT:
|
||||
return chat_id, chat_id, None
|
||||
if strategy == FSMStrategy.GLOBAL_USER:
|
||||
return user_id, user_id, None
|
||||
if strategy == FSMStrategy.USER_IN_TOPIC:
|
||||
return chat_id, user_id, thread_id
|
||||
if strategy == FSMStrategy.CHAT_TOPIC:
|
||||
return chat_id, chat_id, thread_id
|
||||
|
||||
return chat_id, user_id, None
|
25
myenv/Lib/site-packages/aiogram/handlers/__init__.py
Normal file
25
myenv/Lib/site-packages/aiogram/handlers/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from .base import BaseHandler, BaseHandlerMixin
|
||||
from .callback_query import CallbackQueryHandler
|
||||
from .chat_member import ChatMemberHandler
|
||||
from .chosen_inline_result import ChosenInlineResultHandler
|
||||
from .error import ErrorHandler
|
||||
from .inline_query import InlineQueryHandler
|
||||
from .message import MessageHandler, MessageHandlerCommandMixin
|
||||
from .poll import PollHandler
|
||||
from .pre_checkout_query import PreCheckoutQueryHandler
|
||||
from .shipping_query import ShippingQueryHandler
|
||||
|
||||
__all__ = (
|
||||
"BaseHandler",
|
||||
"BaseHandlerMixin",
|
||||
"CallbackQueryHandler",
|
||||
"ChatMemberHandler",
|
||||
"ChosenInlineResultHandler",
|
||||
"ErrorHandler",
|
||||
"InlineQueryHandler",
|
||||
"MessageHandler",
|
||||
"MessageHandlerCommandMixin",
|
||||
"PollHandler",
|
||||
"PreCheckoutQueryHandler",
|
||||
"ShippingQueryHandler",
|
||||
)
|
46
myenv/Lib/site-packages/aiogram/handlers/base.py
Normal file
46
myenv/Lib/site-packages/aiogram/handlers/base.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Dict, Generic, TypeVar, cast
|
||||
|
||||
from aiogram.types import Update
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram import Bot
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class BaseHandlerMixin(Generic[T]):
|
||||
if TYPE_CHECKING:
|
||||
event: T
|
||||
data: Dict[str, Any]
|
||||
|
||||
|
||||
class BaseHandler(BaseHandlerMixin[T], ABC):
|
||||
"""
|
||||
Base class for all class-based handlers
|
||||
"""
|
||||
|
||||
def __init__(self, event: T, **kwargs: Any) -> None:
|
||||
self.event: T = event
|
||||
self.data: Dict[str, Any] = kwargs
|
||||
|
||||
@property
|
||||
def bot(self) -> Bot:
|
||||
from aiogram import Bot
|
||||
|
||||
if "bot" in self.data:
|
||||
return cast(Bot, self.data["bot"])
|
||||
raise RuntimeError("Bot instance not found in the context")
|
||||
|
||||
@property
|
||||
def update(self) -> Update:
|
||||
return cast(Update, self.data.get("update", self.data.get("event_update")))
|
||||
|
||||
@abstractmethod
|
||||
async def handle(self) -> Any: # pragma: no cover
|
||||
pass
|
||||
|
||||
def __await__(self) -> Any:
|
||||
return self.handle().__await__()
|
43
myenv/Lib/site-packages/aiogram/handlers/callback_query.py
Normal file
43
myenv/Lib/site-packages/aiogram/handlers/callback_query.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from abc import ABC
|
||||
from typing import Optional
|
||||
|
||||
from aiogram.handlers import BaseHandler
|
||||
from aiogram.types import CallbackQuery, MaybeInaccessibleMessage, User
|
||||
|
||||
|
||||
class CallbackQueryHandler(BaseHandler[CallbackQuery], ABC):
|
||||
"""
|
||||
There is base class for callback query handlers.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from aiogram.handlers import CallbackQueryHandler
|
||||
|
||||
...
|
||||
|
||||
@router.callback_query()
|
||||
class MyHandler(CallbackQueryHandler):
|
||||
async def handle(self) -> Any: ...
|
||||
"""
|
||||
|
||||
@property
|
||||
def from_user(self) -> User:
|
||||
"""
|
||||
Is alias for `event.from_user`
|
||||
"""
|
||||
return self.event.from_user
|
||||
|
||||
@property
|
||||
def message(self) -> Optional[MaybeInaccessibleMessage]:
|
||||
"""
|
||||
Is alias for `event.message`
|
||||
"""
|
||||
return self.event.message
|
||||
|
||||
@property
|
||||
def callback_data(self) -> Optional[str]:
|
||||
"""
|
||||
Is alias for `event.data`
|
||||
"""
|
||||
return self.event.data
|
14
myenv/Lib/site-packages/aiogram/handlers/chat_member.py
Normal file
14
myenv/Lib/site-packages/aiogram/handlers/chat_member.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from abc import ABC
|
||||
|
||||
from aiogram.handlers import BaseHandler
|
||||
from aiogram.types import ChatMemberUpdated, User
|
||||
|
||||
|
||||
class ChatMemberHandler(BaseHandler[ChatMemberUpdated], ABC):
|
||||
"""
|
||||
Base class for chat member updated events
|
||||
"""
|
||||
|
||||
@property
|
||||
def from_user(self) -> User:
|
||||
return self.event.from_user
|
@@ -0,0 +1,18 @@
|
||||
from abc import ABC
|
||||
|
||||
from aiogram.handlers import BaseHandler
|
||||
from aiogram.types import ChosenInlineResult, User
|
||||
|
||||
|
||||
class ChosenInlineResultHandler(BaseHandler[ChosenInlineResult], ABC):
|
||||
"""
|
||||
Base class for chosen inline result handlers
|
||||
"""
|
||||
|
||||
@property
|
||||
def from_user(self) -> User:
|
||||
return self.event.from_user
|
||||
|
||||
@property
|
||||
def query(self) -> str:
|
||||
return self.event.query
|
17
myenv/Lib/site-packages/aiogram/handlers/error.py
Normal file
17
myenv/Lib/site-packages/aiogram/handlers/error.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from abc import ABC
|
||||
|
||||
from aiogram.handlers.base import BaseHandler
|
||||
|
||||
|
||||
class ErrorHandler(BaseHandler[Exception], ABC):
|
||||
"""
|
||||
Base class for errors handlers
|
||||
"""
|
||||
|
||||
@property
|
||||
def exception_name(self) -> str:
|
||||
return self.event.__class__.__name__
|
||||
|
||||
@property
|
||||
def exception_message(self) -> str:
|
||||
return str(self.event)
|
18
myenv/Lib/site-packages/aiogram/handlers/inline_query.py
Normal file
18
myenv/Lib/site-packages/aiogram/handlers/inline_query.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from abc import ABC
|
||||
|
||||
from aiogram.handlers import BaseHandler
|
||||
from aiogram.types import InlineQuery, User
|
||||
|
||||
|
||||
class InlineQueryHandler(BaseHandler[InlineQuery], ABC):
|
||||
"""
|
||||
Base class for inline query handlers
|
||||
"""
|
||||
|
||||
@property
|
||||
def from_user(self) -> User:
|
||||
return self.event.from_user
|
||||
|
||||
@property
|
||||
def query(self) -> str:
|
||||
return self.event.query
|
28
myenv/Lib/site-packages/aiogram/handlers/message.py
Normal file
28
myenv/Lib/site-packages/aiogram/handlers/message.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from abc import ABC
|
||||
from typing import Optional, cast
|
||||
|
||||
from aiogram.filters import CommandObject
|
||||
from aiogram.handlers.base import BaseHandler, BaseHandlerMixin
|
||||
from aiogram.types import Chat, Message, User
|
||||
|
||||
|
||||
class MessageHandler(BaseHandler[Message], ABC):
|
||||
"""
|
||||
Base class for message handlers
|
||||
"""
|
||||
|
||||
@property
|
||||
def from_user(self) -> Optional[User]:
|
||||
return self.event.from_user
|
||||
|
||||
@property
|
||||
def chat(self) -> Chat:
|
||||
return self.event.chat
|
||||
|
||||
|
||||
class MessageHandlerCommandMixin(BaseHandlerMixin[Message]):
|
||||
@property
|
||||
def command(self) -> Optional[CommandObject]:
|
||||
if "command" in self.data:
|
||||
return cast(CommandObject, self.data["command"])
|
||||
return None
|
19
myenv/Lib/site-packages/aiogram/handlers/poll.py
Normal file
19
myenv/Lib/site-packages/aiogram/handlers/poll.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from abc import ABC
|
||||
from typing import List
|
||||
|
||||
from aiogram.handlers import BaseHandler
|
||||
from aiogram.types import Poll, PollOption
|
||||
|
||||
|
||||
class PollHandler(BaseHandler[Poll], ABC):
|
||||
"""
|
||||
Base class for poll handlers
|
||||
"""
|
||||
|
||||
@property
|
||||
def question(self) -> str:
|
||||
return self.event.question
|
||||
|
||||
@property
|
||||
def options(self) -> List[PollOption]:
|
||||
return self.event.options
|
@@ -0,0 +1,14 @@
|
||||
from abc import ABC
|
||||
|
||||
from aiogram.handlers import BaseHandler
|
||||
from aiogram.types import PreCheckoutQuery, User
|
||||
|
||||
|
||||
class PreCheckoutQueryHandler(BaseHandler[PreCheckoutQuery], ABC):
|
||||
"""
|
||||
Base class for pre-checkout handlers
|
||||
"""
|
||||
|
||||
@property
|
||||
def from_user(self) -> User:
|
||||
return self.event.from_user
|
14
myenv/Lib/site-packages/aiogram/handlers/shipping_query.py
Normal file
14
myenv/Lib/site-packages/aiogram/handlers/shipping_query.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from abc import ABC
|
||||
|
||||
from aiogram.handlers import BaseHandler
|
||||
from aiogram.types import ShippingQuery, User
|
||||
|
||||
|
||||
class ShippingQueryHandler(BaseHandler[ShippingQuery], ABC):
|
||||
"""
|
||||
Base class for shipping query handlers
|
||||
"""
|
||||
|
||||
@property
|
||||
def from_user(self) -> User:
|
||||
return self.event.from_user
|
7
myenv/Lib/site-packages/aiogram/loggers.py
Normal file
7
myenv/Lib/site-packages/aiogram/loggers.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import logging
|
||||
|
||||
dispatcher = logging.getLogger("aiogram.dispatcher")
|
||||
event = logging.getLogger("aiogram.event")
|
||||
middlewares = logging.getLogger("aiogram.middlewares")
|
||||
webhook = logging.getLogger("aiogram.webhook")
|
||||
scene = logging.getLogger("aiogram.scene")
|
313
myenv/Lib/site-packages/aiogram/methods/__init__.py
Normal file
313
myenv/Lib/site-packages/aiogram/methods/__init__.py
Normal file
@@ -0,0 +1,313 @@
|
||||
from .add_sticker_to_set import AddStickerToSet
|
||||
from .answer_callback_query import AnswerCallbackQuery
|
||||
from .answer_inline_query import AnswerInlineQuery
|
||||
from .answer_pre_checkout_query import AnswerPreCheckoutQuery
|
||||
from .answer_shipping_query import AnswerShippingQuery
|
||||
from .answer_web_app_query import AnswerWebAppQuery
|
||||
from .approve_chat_join_request import ApproveChatJoinRequest
|
||||
from .ban_chat_member import BanChatMember
|
||||
from .ban_chat_sender_chat import BanChatSenderChat
|
||||
from .base import Request, Response, TelegramMethod
|
||||
from .close import Close
|
||||
from .close_forum_topic import CloseForumTopic
|
||||
from .close_general_forum_topic import CloseGeneralForumTopic
|
||||
from .convert_gift_to_stars import ConvertGiftToStars
|
||||
from .copy_message import CopyMessage
|
||||
from .copy_messages import CopyMessages
|
||||
from .create_chat_invite_link import CreateChatInviteLink
|
||||
from .create_chat_subscription_invite_link import CreateChatSubscriptionInviteLink
|
||||
from .create_forum_topic import CreateForumTopic
|
||||
from .create_invoice_link import CreateInvoiceLink
|
||||
from .create_new_sticker_set import CreateNewStickerSet
|
||||
from .decline_chat_join_request import DeclineChatJoinRequest
|
||||
from .delete_business_messages import DeleteBusinessMessages
|
||||
from .delete_chat_photo import DeleteChatPhoto
|
||||
from .delete_chat_sticker_set import DeleteChatStickerSet
|
||||
from .delete_forum_topic import DeleteForumTopic
|
||||
from .delete_message import DeleteMessage
|
||||
from .delete_messages import DeleteMessages
|
||||
from .delete_my_commands import DeleteMyCommands
|
||||
from .delete_sticker_from_set import DeleteStickerFromSet
|
||||
from .delete_sticker_set import DeleteStickerSet
|
||||
from .delete_story import DeleteStory
|
||||
from .delete_webhook import DeleteWebhook
|
||||
from .edit_chat_invite_link import EditChatInviteLink
|
||||
from .edit_chat_subscription_invite_link import EditChatSubscriptionInviteLink
|
||||
from .edit_forum_topic import EditForumTopic
|
||||
from .edit_general_forum_topic import EditGeneralForumTopic
|
||||
from .edit_message_caption import EditMessageCaption
|
||||
from .edit_message_live_location import EditMessageLiveLocation
|
||||
from .edit_message_media import EditMessageMedia
|
||||
from .edit_message_reply_markup import EditMessageReplyMarkup
|
||||
from .edit_message_text import EditMessageText
|
||||
from .edit_story import EditStory
|
||||
from .edit_user_star_subscription import EditUserStarSubscription
|
||||
from .export_chat_invite_link import ExportChatInviteLink
|
||||
from .forward_message import ForwardMessage
|
||||
from .forward_messages import ForwardMessages
|
||||
from .get_available_gifts import GetAvailableGifts
|
||||
from .get_business_account_gifts import GetBusinessAccountGifts
|
||||
from .get_business_account_star_balance import GetBusinessAccountStarBalance
|
||||
from .get_business_connection import GetBusinessConnection
|
||||
from .get_chat import GetChat
|
||||
from .get_chat_administrators import GetChatAdministrators
|
||||
from .get_chat_member import GetChatMember
|
||||
from .get_chat_member_count import GetChatMemberCount
|
||||
from .get_chat_menu_button import GetChatMenuButton
|
||||
from .get_custom_emoji_stickers import GetCustomEmojiStickers
|
||||
from .get_file import GetFile
|
||||
from .get_forum_topic_icon_stickers import GetForumTopicIconStickers
|
||||
from .get_game_high_scores import GetGameHighScores
|
||||
from .get_me import GetMe
|
||||
from .get_my_commands import GetMyCommands
|
||||
from .get_my_default_administrator_rights import GetMyDefaultAdministratorRights
|
||||
from .get_my_description import GetMyDescription
|
||||
from .get_my_name import GetMyName
|
||||
from .get_my_short_description import GetMyShortDescription
|
||||
from .get_star_transactions import GetStarTransactions
|
||||
from .get_sticker_set import GetStickerSet
|
||||
from .get_updates import GetUpdates
|
||||
from .get_user_chat_boosts import GetUserChatBoosts
|
||||
from .get_user_profile_photos import GetUserProfilePhotos
|
||||
from .get_webhook_info import GetWebhookInfo
|
||||
from .gift_premium_subscription import GiftPremiumSubscription
|
||||
from .hide_general_forum_topic import HideGeneralForumTopic
|
||||
from .leave_chat import LeaveChat
|
||||
from .log_out import LogOut
|
||||
from .pin_chat_message import PinChatMessage
|
||||
from .post_story import PostStory
|
||||
from .promote_chat_member import PromoteChatMember
|
||||
from .read_business_message import ReadBusinessMessage
|
||||
from .refund_star_payment import RefundStarPayment
|
||||
from .remove_business_account_profile_photo import RemoveBusinessAccountProfilePhoto
|
||||
from .remove_chat_verification import RemoveChatVerification
|
||||
from .remove_user_verification import RemoveUserVerification
|
||||
from .reopen_forum_topic import ReopenForumTopic
|
||||
from .reopen_general_forum_topic import ReopenGeneralForumTopic
|
||||
from .replace_sticker_in_set import ReplaceStickerInSet
|
||||
from .restrict_chat_member import RestrictChatMember
|
||||
from .revoke_chat_invite_link import RevokeChatInviteLink
|
||||
from .save_prepared_inline_message import SavePreparedInlineMessage
|
||||
from .send_animation import SendAnimation
|
||||
from .send_audio import SendAudio
|
||||
from .send_chat_action import SendChatAction
|
||||
from .send_contact import SendContact
|
||||
from .send_dice import SendDice
|
||||
from .send_document import SendDocument
|
||||
from .send_game import SendGame
|
||||
from .send_gift import SendGift
|
||||
from .send_invoice import SendInvoice
|
||||
from .send_location import SendLocation
|
||||
from .send_media_group import SendMediaGroup
|
||||
from .send_message import SendMessage
|
||||
from .send_paid_media import SendPaidMedia
|
||||
from .send_photo import SendPhoto
|
||||
from .send_poll import SendPoll
|
||||
from .send_sticker import SendSticker
|
||||
from .send_venue import SendVenue
|
||||
from .send_video import SendVideo
|
||||
from .send_video_note import SendVideoNote
|
||||
from .send_voice import SendVoice
|
||||
from .set_business_account_bio import SetBusinessAccountBio
|
||||
from .set_business_account_gift_settings import SetBusinessAccountGiftSettings
|
||||
from .set_business_account_name import SetBusinessAccountName
|
||||
from .set_business_account_profile_photo import SetBusinessAccountProfilePhoto
|
||||
from .set_business_account_username import SetBusinessAccountUsername
|
||||
from .set_chat_administrator_custom_title import SetChatAdministratorCustomTitle
|
||||
from .set_chat_description import SetChatDescription
|
||||
from .set_chat_menu_button import SetChatMenuButton
|
||||
from .set_chat_permissions import SetChatPermissions
|
||||
from .set_chat_photo import SetChatPhoto
|
||||
from .set_chat_sticker_set import SetChatStickerSet
|
||||
from .set_chat_title import SetChatTitle
|
||||
from .set_custom_emoji_sticker_set_thumbnail import SetCustomEmojiStickerSetThumbnail
|
||||
from .set_game_score import SetGameScore
|
||||
from .set_message_reaction import SetMessageReaction
|
||||
from .set_my_commands import SetMyCommands
|
||||
from .set_my_default_administrator_rights import SetMyDefaultAdministratorRights
|
||||
from .set_my_description import SetMyDescription
|
||||
from .set_my_name import SetMyName
|
||||
from .set_my_short_description import SetMyShortDescription
|
||||
from .set_passport_data_errors import SetPassportDataErrors
|
||||
from .set_sticker_emoji_list import SetStickerEmojiList
|
||||
from .set_sticker_keywords import SetStickerKeywords
|
||||
from .set_sticker_mask_position import SetStickerMaskPosition
|
||||
from .set_sticker_position_in_set import SetStickerPositionInSet
|
||||
from .set_sticker_set_thumbnail import SetStickerSetThumbnail
|
||||
from .set_sticker_set_title import SetStickerSetTitle
|
||||
from .set_user_emoji_status import SetUserEmojiStatus
|
||||
from .set_webhook import SetWebhook
|
||||
from .stop_message_live_location import StopMessageLiveLocation
|
||||
from .stop_poll import StopPoll
|
||||
from .transfer_business_account_stars import TransferBusinessAccountStars
|
||||
from .transfer_gift import TransferGift
|
||||
from .unban_chat_member import UnbanChatMember
|
||||
from .unban_chat_sender_chat import UnbanChatSenderChat
|
||||
from .unhide_general_forum_topic import UnhideGeneralForumTopic
|
||||
from .unpin_all_chat_messages import UnpinAllChatMessages
|
||||
from .unpin_all_forum_topic_messages import UnpinAllForumTopicMessages
|
||||
from .unpin_all_general_forum_topic_messages import UnpinAllGeneralForumTopicMessages
|
||||
from .unpin_chat_message import UnpinChatMessage
|
||||
from .upgrade_gift import UpgradeGift
|
||||
from .upload_sticker_file import UploadStickerFile
|
||||
from .verify_chat import VerifyChat
|
||||
from .verify_user import VerifyUser
|
||||
|
||||
__all__ = (
|
||||
"AddStickerToSet",
|
||||
"AnswerCallbackQuery",
|
||||
"AnswerInlineQuery",
|
||||
"AnswerPreCheckoutQuery",
|
||||
"AnswerShippingQuery",
|
||||
"AnswerWebAppQuery",
|
||||
"ApproveChatJoinRequest",
|
||||
"BanChatMember",
|
||||
"BanChatSenderChat",
|
||||
"Close",
|
||||
"CloseForumTopic",
|
||||
"CloseGeneralForumTopic",
|
||||
"ConvertGiftToStars",
|
||||
"CopyMessage",
|
||||
"CopyMessages",
|
||||
"CreateChatInviteLink",
|
||||
"CreateChatSubscriptionInviteLink",
|
||||
"CreateForumTopic",
|
||||
"CreateInvoiceLink",
|
||||
"CreateNewStickerSet",
|
||||
"DeclineChatJoinRequest",
|
||||
"DeleteBusinessMessages",
|
||||
"DeleteChatPhoto",
|
||||
"DeleteChatStickerSet",
|
||||
"DeleteForumTopic",
|
||||
"DeleteMessage",
|
||||
"DeleteMessages",
|
||||
"DeleteMyCommands",
|
||||
"DeleteStickerFromSet",
|
||||
"DeleteStickerSet",
|
||||
"DeleteStory",
|
||||
"DeleteWebhook",
|
||||
"EditChatInviteLink",
|
||||
"EditChatSubscriptionInviteLink",
|
||||
"EditForumTopic",
|
||||
"EditGeneralForumTopic",
|
||||
"EditMessageCaption",
|
||||
"EditMessageLiveLocation",
|
||||
"EditMessageMedia",
|
||||
"EditMessageReplyMarkup",
|
||||
"EditMessageText",
|
||||
"EditStory",
|
||||
"EditUserStarSubscription",
|
||||
"ExportChatInviteLink",
|
||||
"ForwardMessage",
|
||||
"ForwardMessages",
|
||||
"GetAvailableGifts",
|
||||
"GetBusinessAccountGifts",
|
||||
"GetBusinessAccountStarBalance",
|
||||
"GetBusinessConnection",
|
||||
"GetChat",
|
||||
"GetChatAdministrators",
|
||||
"GetChatMember",
|
||||
"GetChatMemberCount",
|
||||
"GetChatMenuButton",
|
||||
"GetCustomEmojiStickers",
|
||||
"GetFile",
|
||||
"GetForumTopicIconStickers",
|
||||
"GetGameHighScores",
|
||||
"GetMe",
|
||||
"GetMyCommands",
|
||||
"GetMyDefaultAdministratorRights",
|
||||
"GetMyDescription",
|
||||
"GetMyName",
|
||||
"GetMyShortDescription",
|
||||
"GetStarTransactions",
|
||||
"GetStickerSet",
|
||||
"GetUpdates",
|
||||
"GetUserChatBoosts",
|
||||
"GetUserProfilePhotos",
|
||||
"GetWebhookInfo",
|
||||
"GiftPremiumSubscription",
|
||||
"HideGeneralForumTopic",
|
||||
"LeaveChat",
|
||||
"LogOut",
|
||||
"PinChatMessage",
|
||||
"PostStory",
|
||||
"PromoteChatMember",
|
||||
"ReadBusinessMessage",
|
||||
"RefundStarPayment",
|
||||
"RemoveBusinessAccountProfilePhoto",
|
||||
"RemoveChatVerification",
|
||||
"RemoveUserVerification",
|
||||
"ReopenForumTopic",
|
||||
"ReopenGeneralForumTopic",
|
||||
"ReplaceStickerInSet",
|
||||
"Request",
|
||||
"Response",
|
||||
"RestrictChatMember",
|
||||
"RevokeChatInviteLink",
|
||||
"SavePreparedInlineMessage",
|
||||
"SendAnimation",
|
||||
"SendAudio",
|
||||
"SendChatAction",
|
||||
"SendContact",
|
||||
"SendDice",
|
||||
"SendDocument",
|
||||
"SendGame",
|
||||
"SendGift",
|
||||
"SendInvoice",
|
||||
"SendLocation",
|
||||
"SendMediaGroup",
|
||||
"SendMessage",
|
||||
"SendPaidMedia",
|
||||
"SendPhoto",
|
||||
"SendPoll",
|
||||
"SendSticker",
|
||||
"SendVenue",
|
||||
"SendVideo",
|
||||
"SendVideoNote",
|
||||
"SendVoice",
|
||||
"SetBusinessAccountBio",
|
||||
"SetBusinessAccountGiftSettings",
|
||||
"SetBusinessAccountName",
|
||||
"SetBusinessAccountProfilePhoto",
|
||||
"SetBusinessAccountUsername",
|
||||
"SetChatAdministratorCustomTitle",
|
||||
"SetChatDescription",
|
||||
"SetChatMenuButton",
|
||||
"SetChatPermissions",
|
||||
"SetChatPhoto",
|
||||
"SetChatStickerSet",
|
||||
"SetChatTitle",
|
||||
"SetCustomEmojiStickerSetThumbnail",
|
||||
"SetGameScore",
|
||||
"SetMessageReaction",
|
||||
"SetMyCommands",
|
||||
"SetMyDefaultAdministratorRights",
|
||||
"SetMyDescription",
|
||||
"SetMyName",
|
||||
"SetMyShortDescription",
|
||||
"SetPassportDataErrors",
|
||||
"SetStickerEmojiList",
|
||||
"SetStickerKeywords",
|
||||
"SetStickerMaskPosition",
|
||||
"SetStickerPositionInSet",
|
||||
"SetStickerSetThumbnail",
|
||||
"SetStickerSetTitle",
|
||||
"SetUserEmojiStatus",
|
||||
"SetWebhook",
|
||||
"StopMessageLiveLocation",
|
||||
"StopPoll",
|
||||
"TelegramMethod",
|
||||
"TransferBusinessAccountStars",
|
||||
"TransferGift",
|
||||
"UnbanChatMember",
|
||||
"UnbanChatSenderChat",
|
||||
"UnhideGeneralForumTopic",
|
||||
"UnpinAllChatMessages",
|
||||
"UnpinAllForumTopicMessages",
|
||||
"UnpinAllGeneralForumTopicMessages",
|
||||
"UnpinChatMessage",
|
||||
"UpgradeGift",
|
||||
"UploadStickerFile",
|
||||
"VerifyChat",
|
||||
"VerifyUser",
|
||||
)
|
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..types import InputSticker
|
||||
from .base import TelegramMethod
|
||||
|
||||
|
||||
class AddStickerToSet(TelegramMethod[bool]):
|
||||
"""
|
||||
Use this method to add a new sticker to a set created by the bot. Emoji sticker sets can have up to 200 stickers. Other sticker sets can have up to 120 stickers. Returns :code:`True` on success.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#addstickertoset
|
||||
"""
|
||||
|
||||
__returning__ = bool
|
||||
__api_method__ = "addStickerToSet"
|
||||
|
||||
user_id: int
|
||||
"""User identifier of sticker set owner"""
|
||||
name: str
|
||||
"""Sticker set name"""
|
||||
sticker: InputSticker
|
||||
"""A JSON-serialized object with information about the added sticker. If exactly the same sticker had already been added to the set, then the set isn't changed."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# DO NOT EDIT MANUALLY!!!
|
||||
# This section was auto-generated via `butcher`
|
||||
|
||||
def __init__(
|
||||
__pydantic__self__,
|
||||
*,
|
||||
user_id: int,
|
||||
name: str,
|
||||
sticker: InputSticker,
|
||||
**__pydantic_kwargs: Any,
|
||||
) -> None:
|
||||
# DO NOT EDIT MANUALLY!!!
|
||||
# This method was auto-generated via `butcher`
|
||||
# Is needed only for type checking and IDE support without any additional plugins
|
||||
|
||||
super().__init__(user_id=user_id, name=name, sticker=sticker, **__pydantic_kwargs)
|
@@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from .base import TelegramMethod
|
||||
|
||||
|
||||
class AnswerCallbackQuery(TelegramMethod[bool]):
|
||||
"""
|
||||
Use this method to send answers to callback queries sent from `inline keyboards <https://core.telegram.org/bots/features#inline-keyboards>`_. The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. On success, :code:`True` is returned.
|
||||
|
||||
Alternatively, the user can be redirected to the specified Game URL. For this option to work, you must first create a game for your bot via `@BotFather <https://t.me/botfather>`_ and accept the terms. Otherwise, you may use links like :code:`t.me/your_bot?start=XXXX` that open your bot with a parameter.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#answercallbackquery
|
||||
"""
|
||||
|
||||
__returning__ = bool
|
||||
__api_method__ = "answerCallbackQuery"
|
||||
|
||||
callback_query_id: str
|
||||
"""Unique identifier for the query to be answered"""
|
||||
text: Optional[str] = None
|
||||
"""Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters"""
|
||||
show_alert: Optional[bool] = None
|
||||
"""If :code:`True`, an alert will be shown by the client instead of a notification at the top of the chat screen. Defaults to *false*."""
|
||||
url: Optional[str] = None
|
||||
"""URL that will be opened by the user's client. If you have created a :class:`aiogram.types.game.Game` and accepted the conditions via `@BotFather <https://t.me/botfather>`_, specify the URL that opens your game - note that this will only work if the query comes from a `https://core.telegram.org/bots/api#inlinekeyboardbutton <https://core.telegram.org/bots/api#inlinekeyboardbutton>`_ *callback_game* button."""
|
||||
cache_time: Optional[int] = None
|
||||
"""The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# DO NOT EDIT MANUALLY!!!
|
||||
# This section was auto-generated via `butcher`
|
||||
|
||||
def __init__(
|
||||
__pydantic__self__,
|
||||
*,
|
||||
callback_query_id: str,
|
||||
text: Optional[str] = None,
|
||||
show_alert: Optional[bool] = None,
|
||||
url: Optional[str] = None,
|
||||
cache_time: Optional[int] = None,
|
||||
**__pydantic_kwargs: Any,
|
||||
) -> None:
|
||||
# DO NOT EDIT MANUALLY!!!
|
||||
# This method was auto-generated via `butcher`
|
||||
# Is needed only for type checking and IDE support without any additional plugins
|
||||
|
||||
super().__init__(
|
||||
callback_query_id=callback_query_id,
|
||||
text=text,
|
||||
show_alert=show_alert,
|
||||
url=url,
|
||||
cache_time=cache_time,
|
||||
**__pydantic_kwargs,
|
||||
)
|
@@ -0,0 +1,77 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from ..types import InlineQueryResultsButton, InlineQueryResultUnion
|
||||
from .base import TelegramMethod
|
||||
|
||||
|
||||
class AnswerInlineQuery(TelegramMethod[bool]):
|
||||
"""
|
||||
Use this method to send answers to an inline query. On success, :code:`True` is returned.
|
||||
|
||||
No more than **50** results per query are allowed.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#answerinlinequery
|
||||
"""
|
||||
|
||||
__returning__ = bool
|
||||
__api_method__ = "answerInlineQuery"
|
||||
|
||||
inline_query_id: str
|
||||
"""Unique identifier for the answered query"""
|
||||
results: list[InlineQueryResultUnion]
|
||||
"""A JSON-serialized array of results for the inline query"""
|
||||
cache_time: Optional[int] = None
|
||||
"""The maximum amount of time in seconds that the result of the inline query may be cached on the server. Defaults to 300."""
|
||||
is_personal: Optional[bool] = None
|
||||
"""Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query."""
|
||||
next_offset: Optional[str] = None
|
||||
"""Pass the offset that a client should send in the next query with the same text to receive more results. Pass an empty string if there are no more results or if you don't support pagination. Offset length can't exceed 64 bytes."""
|
||||
button: Optional[InlineQueryResultsButton] = None
|
||||
"""A JSON-serialized object describing a button to be shown above inline query results"""
|
||||
switch_pm_parameter: Optional[str] = Field(None, json_schema_extra={"deprecated": True})
|
||||
"""`Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed.
|
||||
|
||||
.. deprecated:: API:6.7
|
||||
https://core.telegram.org/bots/api-changelog#april-21-2023"""
|
||||
switch_pm_text: Optional[str] = Field(None, json_schema_extra={"deprecated": True})
|
||||
"""If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter *switch_pm_parameter*
|
||||
|
||||
.. deprecated:: API:6.7
|
||||
https://core.telegram.org/bots/api-changelog#april-21-2023"""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# DO NOT EDIT MANUALLY!!!
|
||||
# This section was auto-generated via `butcher`
|
||||
|
||||
def __init__(
|
||||
__pydantic__self__,
|
||||
*,
|
||||
inline_query_id: str,
|
||||
results: list[InlineQueryResultUnion],
|
||||
cache_time: Optional[int] = None,
|
||||
is_personal: Optional[bool] = None,
|
||||
next_offset: Optional[str] = None,
|
||||
button: Optional[InlineQueryResultsButton] = None,
|
||||
switch_pm_parameter: Optional[str] = None,
|
||||
switch_pm_text: Optional[str] = None,
|
||||
**__pydantic_kwargs: Any,
|
||||
) -> None:
|
||||
# DO NOT EDIT MANUALLY!!!
|
||||
# This method was auto-generated via `butcher`
|
||||
# Is needed only for type checking and IDE support without any additional plugins
|
||||
|
||||
super().__init__(
|
||||
inline_query_id=inline_query_id,
|
||||
results=results,
|
||||
cache_time=cache_time,
|
||||
is_personal=is_personal,
|
||||
next_offset=next_offset,
|
||||
button=button,
|
||||
switch_pm_parameter=switch_pm_parameter,
|
||||
switch_pm_text=switch_pm_text,
|
||||
**__pydantic_kwargs,
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user