mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-06 07:50:32 +00:00
Add Black and Flake8
This commit is contained in:
parent
5b9d82f1ca
commit
7c51b1a7d7
124 changed files with 5841 additions and 3772 deletions
12
.flake8
Normal file
12
.flake8
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[flake8]
|
||||||
|
max-line-length = 80
|
||||||
|
select = C,E,F,W,B,B950
|
||||||
|
ignore = E501,W503,E203
|
||||||
|
exclude =
|
||||||
|
.git
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
venv
|
||||||
|
docs
|
||||||
|
*.egg-info
|
||||||
|
experiment.py
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
[](https://pypi.python.org/pypi/aiogram)
|
[](https://pypi.python.org/pypi/aiogram)
|
||||||
[](https://core.telegram.org/bots/api)
|
[](https://core.telegram.org/bots/api)
|
||||||
[](http://aiogram.readthedocs.io/en/latest/?badge=latest)
|
[](http://aiogram.readthedocs.io/en/latest/?badge=latest)
|
||||||
|
[](https://github.com/python/black)
|
||||||
[](https://github.com/aiogram/aiogram/issues)
|
[](https://github.com/aiogram/aiogram/issues)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,13 @@ AIOGramBot
|
||||||
:alt: Telegram Bot API
|
:alt: Telegram Bot API
|
||||||
|
|
||||||
.. image:: https://img.shields.io/readthedocs/pip/stable.svg?style=flat-square
|
.. image:: https://img.shields.io/readthedocs/pip/stable.svg?style=flat-square
|
||||||
:target: http://aiogram.readthedocs.io/en/latest/?badge=latest
|
:target: http://aiogram.readthedocs.io/en/latest/?badge=latest?style=flat-square
|
||||||
:alt: Documentation Status
|
:alt: Documentation Status
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square
|
||||||
|
:target: https://github.com/python/black
|
||||||
|
:alt: Code style: Black
|
||||||
|
|
||||||
.. image:: https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square
|
.. image:: https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square
|
||||||
:target: https://github.com/aiogram/aiogram/issues
|
:target: https://github.com/aiogram/aiogram/issues
|
||||||
:alt: Github issues
|
:alt: Github issues
|
||||||
|
|
|
||||||
|
|
@ -17,26 +17,26 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
uvloop = None
|
uvloop = None
|
||||||
else:
|
else:
|
||||||
if 'DISABLE_UVLOOP' not in os.environ:
|
if "DISABLE_UVLOOP" not in os.environ:
|
||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Bot',
|
"Bot",
|
||||||
'Dispatcher',
|
"Dispatcher",
|
||||||
'__api_version__',
|
"__api_version__",
|
||||||
'__version__',
|
"__version__",
|
||||||
'bot',
|
"bot",
|
||||||
'contrib',
|
"contrib",
|
||||||
'dispatcher',
|
"dispatcher",
|
||||||
'exceptions',
|
"exceptions",
|
||||||
'executor',
|
"executor",
|
||||||
'filters',
|
"filters",
|
||||||
'helper',
|
"helper",
|
||||||
'md',
|
"md",
|
||||||
'middlewares',
|
"middlewares",
|
||||||
'types',
|
"types",
|
||||||
'utils'
|
"utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = '2.2.1.dev1'
|
__version__ = "2.2.1.dev1"
|
||||||
__api_version__ = '4.3'
|
__api_version__ = "4.3"
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class SysInfo:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def python(self):
|
def python(self):
|
||||||
return sys.version.replace('\n', '')
|
return sys.version.replace("\n", "")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def aiogram(self):
|
def aiogram(self):
|
||||||
|
|
@ -57,27 +57,27 @@ class SysInfo:
|
||||||
return aiohttp.__version__
|
return aiohttp.__version__
|
||||||
|
|
||||||
def collect(self):
|
def collect(self):
|
||||||
yield f'{self.python_implementation}: {self.python}'
|
yield f"{self.python_implementation}: {self.python}"
|
||||||
yield f'OS: {self.os}'
|
yield f"OS: {self.os}"
|
||||||
yield f'aiogram: {self.aiogram}'
|
yield f"aiogram: {self.aiogram}"
|
||||||
yield f'aiohttp: {self.aiohttp}'
|
yield f"aiohttp: {self.aiohttp}"
|
||||||
|
|
||||||
uvloop = self.uvloop
|
uvloop = self.uvloop
|
||||||
if uvloop:
|
if uvloop:
|
||||||
yield f'uvloop: {uvloop}'
|
yield f"uvloop: {uvloop}"
|
||||||
|
|
||||||
yield f'JSON mode: {json.mode}'
|
yield f"JSON mode: {json.mode}"
|
||||||
|
|
||||||
rapidjson = self.rapidjson
|
rapidjson = self.rapidjson
|
||||||
if rapidjson:
|
if rapidjson:
|
||||||
yield f'rapidjson: {rapidjson}'
|
yield f"rapidjson: {rapidjson}"
|
||||||
ujson = self.ujson
|
ujson = self.ujson
|
||||||
if ujson:
|
if ujson:
|
||||||
yield f'ujson: {ujson}'
|
yield f"ujson: {ujson}"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '\n'.join(self.collect())
|
return "\n".join(self.collect())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
print(SysInfo())
|
print(SysInfo())
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,4 @@ from . import api
|
||||||
from .base import BaseBot
|
from .base import BaseBot
|
||||||
from .bot import Bot
|
from .bot import Bot
|
||||||
|
|
||||||
__all__ = [
|
__all__ = ["BaseBot", "Bot", "api"]
|
||||||
'BaseBot',
|
|
||||||
'Bot',
|
|
||||||
'api'
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from ..utils import json
|
||||||
from ..utils.helper import Helper, HelperMode, Item
|
from ..utils.helper import Helper, HelperMode, Item
|
||||||
|
|
||||||
# Main aiogram logger
|
# Main aiogram logger
|
||||||
log = logging.getLogger('aiogram')
|
log = logging.getLogger("aiogram")
|
||||||
|
|
||||||
# API Url's
|
# API Url's
|
||||||
API_URL = "https://api.telegram.org/bot{token}/{method}"
|
API_URL = "https://api.telegram.org/bot{token}/{method}"
|
||||||
|
|
@ -25,11 +25,11 @@ def check_token(token: str) -> bool:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if any(x.isspace() for x in token):
|
if any(x.isspace() for x in token):
|
||||||
raise exceptions.ValidationError('Token is invalid!')
|
raise exceptions.ValidationError("Token is invalid!")
|
||||||
|
|
||||||
left, sep, right = token.partition(':')
|
left, sep, right = token.partition(":")
|
||||||
if (not sep) or (not left.isdigit()) or (len(left) < 3):
|
if (not sep) or (not left.isdigit()) or (len(left) < 3):
|
||||||
raise exceptions.ValidationError('Token is invalid!')
|
raise exceptions.ValidationError("Token is invalid!")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -51,19 +51,21 @@ def check_result(method_name: str, content_type: str, status_code: int, body: st
|
||||||
"""
|
"""
|
||||||
log.debug('Response for %s: [%d] "%r"', method_name, status_code, body)
|
log.debug('Response for %s: [%d] "%r"', method_name, status_code, body)
|
||||||
|
|
||||||
if content_type != 'application/json':
|
if content_type != "application/json":
|
||||||
raise exceptions.NetworkError(f"Invalid response with content type {content_type}: \"{body}\"")
|
raise exceptions.NetworkError(
|
||||||
|
f'Invalid response with content type {content_type}: "{body}"'
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result_json = json.loads(body)
|
result_json = json.loads(body)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
result_json = {}
|
result_json = {}
|
||||||
|
|
||||||
description = result_json.get('description') or body
|
description = result_json.get("description") or body
|
||||||
parameters = types.ResponseParameters(**result_json.get('parameters', {}) or {})
|
parameters = types.ResponseParameters(**result_json.get("parameters", {}) or {})
|
||||||
|
|
||||||
if HTTPStatus.OK <= status_code <= HTTPStatus.IM_USED:
|
if HTTPStatus.OK <= status_code <= HTTPStatus.IM_USED:
|
||||||
return result_json.get('result')
|
return result_json.get("result")
|
||||||
elif parameters.retry_after:
|
elif parameters.retry_after:
|
||||||
raise exceptions.RetryAfter(parameters.retry_after)
|
raise exceptions.RetryAfter(parameters.retry_after)
|
||||||
elif parameters.migrate_to_chat_id:
|
elif parameters.migrate_to_chat_id:
|
||||||
|
|
@ -77,10 +79,12 @@ def check_result(method_name: str, content_type: str, status_code: int, body: st
|
||||||
elif status_code in [HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN]:
|
elif status_code in [HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN]:
|
||||||
exceptions.Unauthorized.detect(description)
|
exceptions.Unauthorized.detect(description)
|
||||||
elif status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
|
elif status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
|
||||||
raise exceptions.NetworkError('File too large for uploading. '
|
raise exceptions.NetworkError(
|
||||||
'Check telegram api limits https://core.telegram.org/bots/api#senddocument')
|
"File too large for uploading. "
|
||||||
|
"Check telegram api limits https://core.telegram.org/bots/api#senddocument"
|
||||||
|
)
|
||||||
elif status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
|
elif status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
|
||||||
if 'restart' in description:
|
if "restart" in description:
|
||||||
raise exceptions.RestartingTelegram()
|
raise exceptions.RestartingTelegram()
|
||||||
raise exceptions.TelegramAPIError(description)
|
raise exceptions.TelegramAPIError(description)
|
||||||
raise exceptions.TelegramAPIError(f"{description} [{status_code}]")
|
raise exceptions.TelegramAPIError(f"{description} [{status_code}]")
|
||||||
|
|
@ -95,9 +99,13 @@ async def make_request(session, token, method, data=None, files=None, **kwargs):
|
||||||
req = compose_data(data, files)
|
req = compose_data(data, files)
|
||||||
try:
|
try:
|
||||||
async with session.post(url, data=req, **kwargs) as response:
|
async with session.post(url, data=req, **kwargs) as response:
|
||||||
return check_result(method, response.content_type, response.status, await response.text())
|
return check_result(
|
||||||
|
method, response.content_type, response.status, await response.text()
|
||||||
|
)
|
||||||
except aiohttp.ClientError as e:
|
except aiohttp.ClientError as e:
|
||||||
raise exceptions.NetworkError(f"aiohttp client throws an error: {e.__class__.__name__}: {e}")
|
raise exceptions.NetworkError(
|
||||||
|
f"aiohttp client throws an error: {e.__class__.__name__}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def guess_filename(obj):
|
def guess_filename(obj):
|
||||||
|
|
@ -107,8 +115,8 @@ def guess_filename(obj):
|
||||||
:param obj:
|
:param obj:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
name = getattr(obj, 'name', None)
|
name = getattr(obj, "name", None)
|
||||||
if name and isinstance(name, str) and name[0] != '<' and name[-1] != '>':
|
if name and isinstance(name, str) and name[0] != "<" and name[-1] != ">":
|
||||||
return os.path.basename(name)
|
return os.path.basename(name)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -132,7 +140,7 @@ def compose_data(params=None, files=None):
|
||||||
if len(f) == 2:
|
if len(f) == 2:
|
||||||
filename, fileobj = f
|
filename, fileobj = f
|
||||||
else:
|
else:
|
||||||
raise ValueError('Tuple must have exactly 2 elements: filename, fileobj')
|
raise ValueError("Tuple must have exactly 2 elements: filename, fileobj")
|
||||||
elif isinstance(f, types.InputFile):
|
elif isinstance(f, types.InputFile):
|
||||||
filename, fileobj = f.filename, f.file
|
filename, fileobj = f.filename, f.file
|
||||||
else:
|
else:
|
||||||
|
|
@ -149,6 +157,7 @@ class Methods(Helper):
|
||||||
|
|
||||||
List is updated to Bot API 4.3
|
List is updated to Bot API 4.3
|
||||||
"""
|
"""
|
||||||
|
|
||||||
mode = HelperMode.lowerCamelCase
|
mode = HelperMode.lowerCamelCase
|
||||||
|
|
||||||
# Getting Updates
|
# Getting Updates
|
||||||
|
|
|
||||||
|
|
@ -20,19 +20,22 @@ class BaseBot:
|
||||||
"""
|
"""
|
||||||
Base class for bot. It's raw bot.
|
Base class for bot. It's raw bot.
|
||||||
"""
|
"""
|
||||||
_ctx_timeout = ContextVar('TelegramRequestTimeout')
|
|
||||||
_ctx_token = ContextVar('BotDifferentToken')
|
_ctx_timeout = ContextVar("TelegramRequestTimeout")
|
||||||
|
_ctx_token = ContextVar("BotDifferentToken")
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
token: base.String,
|
token: base.String,
|
||||||
loop: Optional[Union[asyncio.BaseEventLoop, asyncio.AbstractEventLoop]] = None,
|
loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||||
connections_limit: Optional[base.Integer] = None,
|
connections_limit: Optional[base.Integer] = None,
|
||||||
proxy: Optional[base.String] = None,
|
proxy: Optional[base.String] = None,
|
||||||
proxy_auth: Optional[aiohttp.BasicAuth] = None,
|
proxy_auth: Optional[aiohttp.BasicAuth] = None,
|
||||||
validate_token: Optional[base.Boolean] = True,
|
validate_token: Optional[base.Boolean] = True,
|
||||||
parse_mode: typing.Optional[base.String] = None,
|
parse_mode: typing.Optional[base.String] = None,
|
||||||
timeout: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]] = None
|
timeout: typing.Optional[
|
||||||
|
typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]
|
||||||
|
] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Instructions how to get Bot token is found here: https://core.telegram.org/bots#3-how-do-i-create-a-bot
|
Instructions how to get Bot token is found here: https://core.telegram.org/bots#3-how-do-i-create-a-bot
|
||||||
|
|
@ -72,7 +75,9 @@ class BaseBot:
|
||||||
# aiohttp main session
|
# aiohttp main session
|
||||||
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
||||||
|
|
||||||
if isinstance(proxy, str) and (proxy.startswith('socks5://') or proxy.startswith('socks4://')):
|
if isinstance(proxy, str) and (
|
||||||
|
proxy.startswith("socks5://") or proxy.startswith("socks4://")
|
||||||
|
):
|
||||||
from aiohttp_socks import SocksConnector
|
from aiohttp_socks import SocksConnector
|
||||||
from aiohttp_socks.helpers import parse_socks_url
|
from aiohttp_socks.helpers import parse_socks_url
|
||||||
|
|
||||||
|
|
@ -83,25 +88,36 @@ class BaseBot:
|
||||||
if not password:
|
if not password:
|
||||||
password = proxy_auth.password
|
password = proxy_auth.password
|
||||||
|
|
||||||
connector = SocksConnector(socks_ver=socks_ver, host=host, port=port,
|
connector = SocksConnector(
|
||||||
username=username, password=password,
|
socks_ver=socks_ver,
|
||||||
limit=connections_limit, ssl_context=ssl_context,
|
host=host,
|
||||||
rdns=True, loop=self.loop)
|
port=port,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
limit=connections_limit,
|
||||||
|
ssl_context=ssl_context,
|
||||||
|
rdns=True,
|
||||||
|
loop=self.loop,
|
||||||
|
)
|
||||||
|
|
||||||
self.proxy = None
|
self.proxy = None
|
||||||
self.proxy_auth = None
|
self.proxy_auth = None
|
||||||
else:
|
else:
|
||||||
connector = aiohttp.TCPConnector(limit=connections_limit, ssl=ssl_context, loop=self.loop)
|
connector = aiohttp.TCPConnector(
|
||||||
|
limit=connections_limit, ssl=ssl_context, loop=self.loop
|
||||||
|
)
|
||||||
self._timeout = None
|
self._timeout = None
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
self.session = aiohttp.ClientSession(connector=connector, loop=self.loop, json_serialize=json.dumps)
|
self.session = aiohttp.ClientSession(
|
||||||
|
connector=connector, loop=self.loop, json_serialize=json.dumps
|
||||||
|
)
|
||||||
|
|
||||||
self.parse_mode = parse_mode
|
self.parse_mode = parse_mode
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _prepare_timeout(
|
def _prepare_timeout(
|
||||||
value: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]]
|
value: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]]
|
||||||
) -> typing.Optional[aiohttp.ClientTimeout]:
|
) -> typing.Optional[aiohttp.ClientTimeout]:
|
||||||
if value is None or isinstance(value, aiohttp.ClientTimeout):
|
if value is None or isinstance(value, aiohttp.ClientTimeout):
|
||||||
return value
|
return value
|
||||||
|
|
@ -123,7 +139,9 @@ class BaseBot:
|
||||||
self.timeout = None
|
self.timeout = None
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def request_timeout(self, timeout: typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]):
|
def request_timeout(
|
||||||
|
self, timeout: typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Context manager implements opportunity to change request timeout in current context
|
Context manager implements opportunity to change request timeout in current context
|
||||||
|
|
||||||
|
|
@ -162,9 +180,13 @@ class BaseBot:
|
||||||
"""
|
"""
|
||||||
await self.session.close()
|
await self.session.close()
|
||||||
|
|
||||||
async def request(self, method: base.String,
|
async def request(
|
||||||
data: Optional[Dict] = None,
|
self,
|
||||||
files: Optional[Dict] = None, **kwargs) -> Union[List, Dict, base.Boolean]:
|
method: base.String,
|
||||||
|
data: Optional[Dict] = None,
|
||||||
|
files: Optional[Dict] = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> Union[List, Dict, base.Boolean]:
|
||||||
"""
|
"""
|
||||||
Make an request to Telegram Bot API
|
Make an request to Telegram Bot API
|
||||||
|
|
||||||
|
|
@ -180,14 +202,26 @@ class BaseBot:
|
||||||
:rtype: Union[List, Dict]
|
:rtype: Union[List, Dict]
|
||||||
:raise: :obj:`aiogram.exceptions.TelegramApiError`
|
:raise: :obj:`aiogram.exceptions.TelegramApiError`
|
||||||
"""
|
"""
|
||||||
return await api.make_request(self.session, self.__token, method, data, files,
|
return await api.make_request(
|
||||||
proxy=self.proxy, proxy_auth=self.proxy_auth, timeout=self.timeout, **kwargs)
|
self.session,
|
||||||
|
self.__token,
|
||||||
|
method,
|
||||||
|
data,
|
||||||
|
files,
|
||||||
|
proxy=self.proxy,
|
||||||
|
proxy_auth=self.proxy_auth,
|
||||||
|
timeout=self.timeout,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
async def download_file(self, file_path: base.String,
|
async def download_file(
|
||||||
destination: Optional[base.InputFile] = None,
|
self,
|
||||||
timeout: Optional[base.Integer] = sentinel,
|
file_path: base.String,
|
||||||
chunk_size: Optional[base.Integer] = 65536,
|
destination: Optional[base.InputFile] = None,
|
||||||
seek: Optional[base.Boolean] = True) -> Union[io.BytesIO, io.FileIO]:
|
timeout: Optional[base.Integer] = sentinel,
|
||||||
|
chunk_size: Optional[base.Integer] = 65536,
|
||||||
|
seek: Optional[base.Boolean] = True,
|
||||||
|
) -> Union[io.BytesIO, io.FileIO]:
|
||||||
"""
|
"""
|
||||||
Download file by file_path to destination
|
Download file by file_path to destination
|
||||||
|
|
||||||
|
|
@ -207,8 +241,10 @@ class BaseBot:
|
||||||
|
|
||||||
url = self.get_file_url(file_path)
|
url = self.get_file_url(file_path)
|
||||||
|
|
||||||
dest = destination if isinstance(destination, io.IOBase) else open(destination, 'wb')
|
dest = destination if isinstance(destination, io.IOBase) else open(destination, "wb")
|
||||||
async with self.session.get(url, timeout=timeout, proxy=self.proxy, proxy_auth=self.proxy_auth) as response:
|
async with self.session.get(
|
||||||
|
url, timeout=timeout, proxy=self.proxy, proxy_auth=self.proxy_auth
|
||||||
|
) as response:
|
||||||
while True:
|
while True:
|
||||||
chunk = await response.content.read(chunk_size)
|
chunk = await response.content.read(chunk_size)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
|
|
@ -247,19 +283,19 @@ class BaseBot:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parse_mode(self):
|
def parse_mode(self):
|
||||||
return getattr(self, '_parse_mode', None)
|
return getattr(self, "_parse_mode", None)
|
||||||
|
|
||||||
@parse_mode.setter
|
@parse_mode.setter
|
||||||
def parse_mode(self, value):
|
def parse_mode(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
setattr(self, '_parse_mode', None)
|
setattr(self, "_parse_mode", None)
|
||||||
else:
|
else:
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
raise TypeError(f"Parse mode must be str, not {type(value)}")
|
raise TypeError(f"Parse mode must be str, not {type(value)}")
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
if value not in ParseMode.all():
|
if value not in ParseMode.all():
|
||||||
raise ValueError(f"Parse mode must be one of {ParseMode.all()}")
|
raise ValueError(f"Parse mode must be one of {ParseMode.all()}")
|
||||||
setattr(self, '_parse_mode', value)
|
setattr(self, "_parse_mode", value)
|
||||||
|
|
||||||
@parse_mode.deleter
|
@parse_mode.deleter
|
||||||
def parse_mode(self):
|
def parse_mode(self):
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -36,11 +36,11 @@ class JSONStorage(_FileStorage):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def read(self, path: pathlib.Path):
|
def read(self, path: pathlib.Path):
|
||||||
with path.open('r') as f:
|
with path.open("r") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
def write(self, path: pathlib.Path):
|
def write(self, path: pathlib.Path):
|
||||||
with path.open('w') as f:
|
with path.open("w") as f:
|
||||||
return json.dump(self.data, f, indent=4)
|
return json.dump(self.data, f, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -50,9 +50,9 @@ class PickleStorage(_FileStorage):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def read(self, path: pathlib.Path):
|
def read(self, path: pathlib.Path):
|
||||||
with path.open('rb') as f:
|
with path.open("rb") as f:
|
||||||
return pickle.load(f)
|
return pickle.load(f)
|
||||||
|
|
||||||
def write(self, path: pathlib.Path):
|
def write(self, path: pathlib.Path):
|
||||||
with path.open('wb') as f:
|
with path.open("wb") as f:
|
||||||
return pickle.dump(self.data, f, protocol=pickle.HIGHEST_PROTOCOL)
|
return pickle.dump(self.data, f, protocol=pickle.HIGHEST_PROTOCOL)
|
||||||
|
|
|
||||||
|
|
@ -26,51 +26,70 @@ class MemoryStorage(BaseStorage):
|
||||||
if chat_id not in self.data:
|
if chat_id not in self.data:
|
||||||
self.data[chat_id] = {}
|
self.data[chat_id] = {}
|
||||||
if user_id not in self.data[chat_id]:
|
if user_id not in self.data[chat_id]:
|
||||||
self.data[chat_id][user_id] = {'state': None, 'data': {}, 'bucket': {}}
|
self.data[chat_id][user_id] = {"state": None, "data": {}, "bucket": {}}
|
||||||
|
|
||||||
return chat_id, user_id
|
return chat_id, user_id
|
||||||
|
|
||||||
async def get_state(self, *,
|
async def get_state(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Optional[str]:
|
||||||
chat, user = self.resolve_address(chat=chat, user=user)
|
chat, user = self.resolve_address(chat=chat, user=user)
|
||||||
return self.data[chat][user]['state']
|
return self.data[chat][user]["state"]
|
||||||
|
|
||||||
async def get_data(self, *,
|
async def get_data(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
default: typing.Optional[str] = None) -> typing.Dict:
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
chat, user = self.resolve_address(chat=chat, user=user)
|
chat, user = self.resolve_address(chat=chat, user=user)
|
||||||
return copy.deepcopy(self.data[chat][user]['data'])
|
return copy.deepcopy(self.data[chat][user]["data"])
|
||||||
|
|
||||||
async def update_data(self, *,
|
async def update_data(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
data: typing.Dict = None, **kwargs):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
if data is None:
|
if data is None:
|
||||||
data = {}
|
data = {}
|
||||||
chat, user = self.resolve_address(chat=chat, user=user)
|
chat, user = self.resolve_address(chat=chat, user=user)
|
||||||
self.data[chat][user]['data'].update(data, **kwargs)
|
self.data[chat][user]["data"].update(data, **kwargs)
|
||||||
|
|
||||||
async def set_state(self, *,
|
async def set_state(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
state: typing.AnyStr = None):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
state: typing.AnyStr = None,
|
||||||
|
):
|
||||||
chat, user = self.resolve_address(chat=chat, user=user)
|
chat, user = self.resolve_address(chat=chat, user=user)
|
||||||
self.data[chat][user]['state'] = state
|
self.data[chat][user]["state"] = state
|
||||||
|
|
||||||
async def set_data(self, *,
|
async def set_data(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
data: typing.Dict = None):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
):
|
||||||
chat, user = self.resolve_address(chat=chat, user=user)
|
chat, user = self.resolve_address(chat=chat, user=user)
|
||||||
self.data[chat][user]['data'] = copy.deepcopy(data)
|
self.data[chat][user]["data"] = copy.deepcopy(data)
|
||||||
|
|
||||||
async def reset_state(self, *,
|
async def reset_state(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
with_data: typing.Optional[bool] = True):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
with_data: typing.Optional[bool] = True,
|
||||||
|
):
|
||||||
await self.set_state(chat=chat, user=user, state=None)
|
await self.set_state(chat=chat, user=user, state=None)
|
||||||
if with_data:
|
if with_data:
|
||||||
await self.set_data(chat=chat, user=user, data={})
|
await self.set_data(chat=chat, user=user, data={})
|
||||||
|
|
@ -78,25 +97,35 @@ class MemoryStorage(BaseStorage):
|
||||||
def has_bucket(self):
|
def has_bucket(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_bucket(self, *,
|
async def get_bucket(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
default: typing.Optional[dict] = None) -> typing.Dict:
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[dict] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
chat, user = self.resolve_address(chat=chat, user=user)
|
chat, user = self.resolve_address(chat=chat, user=user)
|
||||||
return copy.deepcopy(self.data[chat][user]['bucket'])
|
return copy.deepcopy(self.data[chat][user]["bucket"])
|
||||||
|
|
||||||
async def set_bucket(self, *,
|
async def set_bucket(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
bucket: typing.Dict = None):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None,
|
||||||
|
):
|
||||||
chat, user = self.resolve_address(chat=chat, user=user)
|
chat, user = self.resolve_address(chat=chat, user=user)
|
||||||
self.data[chat][user]['bucket'] = copy.deepcopy(bucket)
|
self.data[chat][user]["bucket"] = copy.deepcopy(bucket)
|
||||||
|
|
||||||
async def update_bucket(self, *,
|
async def update_bucket(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
bucket: typing.Dict = None, **kwargs):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
if bucket is None:
|
if bucket is None:
|
||||||
bucket = {}
|
bucket = {}
|
||||||
chat, user = self.resolve_address(chat=chat, user=user)
|
chat, user = self.resolve_address(chat=chat, user=user)
|
||||||
self.data[chat][user]['bucket'].update(bucket, **kwargs)
|
self.data[chat][user]["bucket"].update(bucket, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ import aioredis
|
||||||
from ...dispatcher.storage import BaseStorage
|
from ...dispatcher.storage import BaseStorage
|
||||||
from ...utils import json
|
from ...utils import json
|
||||||
|
|
||||||
STATE_KEY = 'state'
|
STATE_KEY = "state"
|
||||||
STATE_DATA_KEY = 'data'
|
STATE_DATA_KEY = "data"
|
||||||
STATE_BUCKET_KEY = 'bucket'
|
STATE_BUCKET_KEY = "bucket"
|
||||||
|
|
||||||
|
|
||||||
class RedisStorage(BaseStorage):
|
class RedisStorage(BaseStorage):
|
||||||
|
|
@ -36,7 +36,9 @@ class RedisStorage(BaseStorage):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host='localhost', port=6379, db=None, password=None, ssl=None, loop=None, **kwargs):
|
def __init__(
|
||||||
|
self, host="localhost", port=6379, db=None, password=None, ssl=None, loop=None, **kwargs
|
||||||
|
):
|
||||||
self._host = host
|
self._host = host
|
||||||
self._port = port
|
self._port = port
|
||||||
self._db = db
|
self._db = db
|
||||||
|
|
@ -68,15 +70,22 @@ class RedisStorage(BaseStorage):
|
||||||
# Use thread-safe asyncio Lock because this method without that is not safe
|
# Use thread-safe asyncio Lock because this method without that is not safe
|
||||||
async with self._connection_lock:
|
async with self._connection_lock:
|
||||||
if self._redis is None:
|
if self._redis is None:
|
||||||
self._redis = await aioredis.create_connection((self._host, self._port),
|
self._redis = await aioredis.create_connection(
|
||||||
db=self._db, password=self._password, ssl=self._ssl,
|
(self._host, self._port),
|
||||||
loop=self._loop,
|
db=self._db,
|
||||||
**self._kwargs)
|
password=self._password,
|
||||||
|
ssl=self._ssl,
|
||||||
|
loop=self._loop,
|
||||||
|
**self._kwargs,
|
||||||
|
)
|
||||||
return self._redis
|
return self._redis
|
||||||
|
|
||||||
async def get_record(self, *,
|
async def get_record(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None) -> typing.Dict:
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
"""
|
"""
|
||||||
Get record from storage
|
Get record from storage
|
||||||
|
|
||||||
|
|
@ -88,13 +97,20 @@ class RedisStorage(BaseStorage):
|
||||||
addr = f"fsm:{chat}:{user}"
|
addr = f"fsm:{chat}:{user}"
|
||||||
|
|
||||||
conn = await self.redis()
|
conn = await self.redis()
|
||||||
data = await conn.execute('GET', addr)
|
data = await conn.execute("GET", addr)
|
||||||
if data is None:
|
if data is None:
|
||||||
return {'state': None, 'data': {}}
|
return {"state": None, "data": {}}
|
||||||
return json.loads(data)
|
return json.loads(data)
|
||||||
|
|
||||||
async def set_record(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_record(
|
||||||
state=None, data=None, bucket=None):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
state=None,
|
||||||
|
data=None,
|
||||||
|
bucket=None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Write record to storage
|
Write record to storage
|
||||||
|
|
||||||
|
|
@ -113,39 +129,65 @@ class RedisStorage(BaseStorage):
|
||||||
chat, user = self.check_address(chat=chat, user=user)
|
chat, user = self.check_address(chat=chat, user=user)
|
||||||
addr = f"fsm:{chat}:{user}"
|
addr = f"fsm:{chat}:{user}"
|
||||||
|
|
||||||
record = {'state': state, 'data': data, 'bucket': bucket}
|
record = {"state": state, "data": data, "bucket": bucket}
|
||||||
|
|
||||||
conn = await self.redis()
|
conn = await self.redis()
|
||||||
await conn.execute('SET', addr, json.dumps(record))
|
await conn.execute("SET", addr, json.dumps(record))
|
||||||
|
|
||||||
async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_state(
|
||||||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Optional[str]:
|
||||||
record = await self.get_record(chat=chat, user=user)
|
record = await self.get_record(chat=chat, user=user)
|
||||||
return record['state']
|
return record["state"]
|
||||||
|
|
||||||
async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_data(
|
||||||
default: typing.Optional[str] = None) -> typing.Dict:
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
record = await self.get_record(chat=chat, user=user)
|
record = await self.get_record(chat=chat, user=user)
|
||||||
return record['data']
|
return record["data"]
|
||||||
|
|
||||||
async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_state(
|
||||||
state: typing.Optional[typing.AnyStr] = None):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
state: typing.Optional[typing.AnyStr] = None,
|
||||||
|
):
|
||||||
record = await self.get_record(chat=chat, user=user)
|
record = await self.get_record(chat=chat, user=user)
|
||||||
await self.set_record(chat=chat, user=user, state=state, data=record['data'])
|
await self.set_record(chat=chat, user=user, state=state, data=record["data"])
|
||||||
|
|
||||||
async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_data(
|
||||||
data: typing.Dict = None):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
):
|
||||||
record = await self.get_record(chat=chat, user=user)
|
record = await self.get_record(chat=chat, user=user)
|
||||||
await self.set_record(chat=chat, user=user, state=record['state'], data=data)
|
await self.set_record(chat=chat, user=user, state=record["state"], data=data)
|
||||||
|
|
||||||
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def update_data(
|
||||||
data: typing.Dict = None, **kwargs):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
if data is None:
|
if data is None:
|
||||||
data = {}
|
data = {}
|
||||||
record = await self.get_record(chat=chat, user=user)
|
record = await self.get_record(chat=chat, user=user)
|
||||||
record_data = record.get('data', {})
|
record_data = record.get("data", {})
|
||||||
record_data.update(data, **kwargs)
|
record_data.update(data, **kwargs)
|
||||||
await self.set_record(chat=chat, user=user, state=record['state'], data=record_data)
|
await self.set_record(chat=chat, user=user, state=record["state"], data=record_data)
|
||||||
|
|
||||||
async def get_states_list(self) -> typing.List[typing.Tuple[int]]:
|
async def get_states_list(self) -> typing.List[typing.Tuple[int]]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -156,9 +198,9 @@ class RedisStorage(BaseStorage):
|
||||||
conn = await self.redis()
|
conn = await self.redis()
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
keys = await conn.execute('KEYS', 'fsm:*')
|
keys = await conn.execute("KEYS", "fsm:*")
|
||||||
for item in keys:
|
for item in keys:
|
||||||
*_, chat, user = item.decode('utf-8').split(':')
|
*_, chat, user = item.decode("utf-8").split(":")
|
||||||
result.append((chat, user))
|
result.append((chat, user))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
@ -173,33 +215,52 @@ class RedisStorage(BaseStorage):
|
||||||
conn = await self.redis()
|
conn = await self.redis()
|
||||||
|
|
||||||
if full:
|
if full:
|
||||||
await conn.execute('FLUSHDB')
|
await conn.execute("FLUSHDB")
|
||||||
else:
|
else:
|
||||||
keys = await conn.execute('KEYS', 'fsm:*')
|
keys = await conn.execute("KEYS", "fsm:*")
|
||||||
await conn.execute('DEL', *keys)
|
await conn.execute("DEL", *keys)
|
||||||
|
|
||||||
def has_bucket(self):
|
def has_bucket(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_bucket(
|
||||||
default: typing.Optional[str] = None) -> typing.Dict:
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
record = await self.get_record(chat=chat, user=user)
|
record = await self.get_record(chat=chat, user=user)
|
||||||
return record.get('bucket', {})
|
return record.get("bucket", {})
|
||||||
|
|
||||||
async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_bucket(
|
||||||
bucket: typing.Dict = None):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None,
|
||||||
|
):
|
||||||
record = await self.get_record(chat=chat, user=user)
|
record = await self.get_record(chat=chat, user=user)
|
||||||
await self.set_record(chat=chat, user=user, state=record['state'], data=record['data'], bucket=bucket)
|
await self.set_record(
|
||||||
|
chat=chat, user=user, state=record["state"], data=record["data"], bucket=bucket
|
||||||
|
)
|
||||||
|
|
||||||
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
|
async def update_bucket(
|
||||||
user: typing.Union[str, int, None] = None,
|
self,
|
||||||
bucket: typing.Dict = None, **kwargs):
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
record = await self.get_record(chat=chat, user=user)
|
record = await self.get_record(chat=chat, user=user)
|
||||||
record_bucket = record.get('bucket', {})
|
record_bucket = record.get("bucket", {})
|
||||||
if bucket is None:
|
if bucket is None:
|
||||||
bucket = {}
|
bucket = {}
|
||||||
record_bucket.update(bucket, **kwargs)
|
record_bucket.update(bucket, **kwargs)
|
||||||
await self.set_record(chat=chat, user=user, state=record['state'], data=record_bucket, bucket=bucket)
|
await self.set_record(
|
||||||
|
chat=chat, user=user, state=record["state"], data=record_bucket, bucket=bucket
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RedisStorage2(BaseStorage):
|
class RedisStorage2(BaseStorage):
|
||||||
|
|
@ -223,8 +284,18 @@ class RedisStorage2(BaseStorage):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host='localhost', port=6379, db=None, password=None, ssl=None,
|
def __init__(
|
||||||
pool_size=10, loop=None, prefix='fsm', **kwargs):
|
self,
|
||||||
|
host="localhost",
|
||||||
|
port=6379,
|
||||||
|
db=None,
|
||||||
|
password=None,
|
||||||
|
ssl=None,
|
||||||
|
pool_size=10,
|
||||||
|
loop=None,
|
||||||
|
prefix="fsm",
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
self._host = host
|
self._host = host
|
||||||
self._port = port
|
self._port = port
|
||||||
self._db = db
|
self._db = db
|
||||||
|
|
@ -247,14 +318,20 @@ class RedisStorage2(BaseStorage):
|
||||||
# Use thread-safe asyncio Lock because this method without that is not safe
|
# Use thread-safe asyncio Lock because this method without that is not safe
|
||||||
async with self._connection_lock:
|
async with self._connection_lock:
|
||||||
if self._redis is None:
|
if self._redis is None:
|
||||||
self._redis = await aioredis.create_redis_pool((self._host, self._port),
|
self._redis = await aioredis.create_redis_pool(
|
||||||
db=self._db, password=self._password, ssl=self._ssl,
|
(self._host, self._port),
|
||||||
minsize=1, maxsize=self._pool_size,
|
db=self._db,
|
||||||
loop=self._loop, **self._kwargs)
|
password=self._password,
|
||||||
|
ssl=self._ssl,
|
||||||
|
minsize=1,
|
||||||
|
maxsize=self._pool_size,
|
||||||
|
loop=self._loop,
|
||||||
|
**self._kwargs,
|
||||||
|
)
|
||||||
return self._redis
|
return self._redis
|
||||||
|
|
||||||
def generate_key(self, *parts):
|
def generate_key(self, *parts):
|
||||||
return ':'.join(self._prefix + tuple(map(str, parts)))
|
return ":".join(self._prefix + tuple(map(str, parts)))
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
async with self._connection_lock:
|
async with self._connection_lock:
|
||||||
|
|
@ -269,25 +346,40 @@ class RedisStorage2(BaseStorage):
|
||||||
return await self._redis.wait_closed()
|
return await self._redis.wait_closed()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_state(
|
||||||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Optional[str]:
|
||||||
chat, user = self.check_address(chat=chat, user=user)
|
chat, user = self.check_address(chat=chat, user=user)
|
||||||
key = self.generate_key(chat, user, STATE_KEY)
|
key = self.generate_key(chat, user, STATE_KEY)
|
||||||
redis = await self.redis()
|
redis = await self.redis()
|
||||||
return await redis.get(key, encoding='utf8') or None
|
return await redis.get(key, encoding="utf8") or None
|
||||||
|
|
||||||
async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_data(
|
||||||
default: typing.Optional[dict] = None) -> typing.Dict:
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[dict] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
chat, user = self.check_address(chat=chat, user=user)
|
chat, user = self.check_address(chat=chat, user=user)
|
||||||
key = self.generate_key(chat, user, STATE_DATA_KEY)
|
key = self.generate_key(chat, user, STATE_DATA_KEY)
|
||||||
redis = await self.redis()
|
redis = await self.redis()
|
||||||
raw_result = await redis.get(key, encoding='utf8')
|
raw_result = await redis.get(key, encoding="utf8")
|
||||||
if raw_result:
|
if raw_result:
|
||||||
return json.loads(raw_result)
|
return json.loads(raw_result)
|
||||||
return default or {}
|
return default or {}
|
||||||
|
|
||||||
async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_state(
|
||||||
state: typing.Optional[typing.AnyStr] = None):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
state: typing.Optional[typing.AnyStr] = None,
|
||||||
|
):
|
||||||
chat, user = self.check_address(chat=chat, user=user)
|
chat, user = self.check_address(chat=chat, user=user)
|
||||||
key = self.generate_key(chat, user, STATE_KEY)
|
key = self.generate_key(chat, user, STATE_KEY)
|
||||||
redis = await self.redis()
|
redis = await self.redis()
|
||||||
|
|
@ -296,15 +388,26 @@ class RedisStorage2(BaseStorage):
|
||||||
else:
|
else:
|
||||||
await redis.set(key, state)
|
await redis.set(key, state)
|
||||||
|
|
||||||
async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_data(
|
||||||
data: typing.Dict = None):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
):
|
||||||
chat, user = self.check_address(chat=chat, user=user)
|
chat, user = self.check_address(chat=chat, user=user)
|
||||||
key = self.generate_key(chat, user, STATE_DATA_KEY)
|
key = self.generate_key(chat, user, STATE_DATA_KEY)
|
||||||
redis = await self.redis()
|
redis = await self.redis()
|
||||||
await redis.set(key, json.dumps(data))
|
await redis.set(key, json.dumps(data))
|
||||||
|
|
||||||
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def update_data(
|
||||||
data: typing.Dict = None, **kwargs):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
if data is None:
|
if data is None:
|
||||||
data = {}
|
data = {}
|
||||||
temp_data = await self.get_data(chat=chat, user=user, default={})
|
temp_data = await self.get_data(chat=chat, user=user, default={})
|
||||||
|
|
@ -314,26 +417,41 @@ class RedisStorage2(BaseStorage):
|
||||||
def has_bucket(self):
|
def has_bucket(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_bucket(
|
||||||
default: typing.Optional[dict] = None) -> typing.Dict:
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[dict] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
chat, user = self.check_address(chat=chat, user=user)
|
chat, user = self.check_address(chat=chat, user=user)
|
||||||
key = self.generate_key(chat, user, STATE_BUCKET_KEY)
|
key = self.generate_key(chat, user, STATE_BUCKET_KEY)
|
||||||
redis = await self.redis()
|
redis = await self.redis()
|
||||||
raw_result = await redis.get(key, encoding='utf8')
|
raw_result = await redis.get(key, encoding="utf8")
|
||||||
if raw_result:
|
if raw_result:
|
||||||
return json.loads(raw_result)
|
return json.loads(raw_result)
|
||||||
return default or {}
|
return default or {}
|
||||||
|
|
||||||
async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_bucket(
|
||||||
bucket: typing.Dict = None):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None,
|
||||||
|
):
|
||||||
chat, user = self.check_address(chat=chat, user=user)
|
chat, user = self.check_address(chat=chat, user=user)
|
||||||
key = self.generate_key(chat, user, STATE_BUCKET_KEY)
|
key = self.generate_key(chat, user, STATE_BUCKET_KEY)
|
||||||
redis = await self.redis()
|
redis = await self.redis()
|
||||||
await redis.set(key, json.dumps(bucket))
|
await redis.set(key, json.dumps(bucket))
|
||||||
|
|
||||||
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
|
async def update_bucket(
|
||||||
user: typing.Union[str, int, None] = None,
|
self,
|
||||||
bucket: typing.Dict = None, **kwargs):
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
if bucket is None:
|
if bucket is None:
|
||||||
bucket = {}
|
bucket = {}
|
||||||
temp_bucket = await self.get_bucket(chat=chat, user=user)
|
temp_bucket = await self.get_bucket(chat=chat, user=user)
|
||||||
|
|
@ -352,7 +470,7 @@ class RedisStorage2(BaseStorage):
|
||||||
if full:
|
if full:
|
||||||
await conn.flushdb()
|
await conn.flushdb()
|
||||||
else:
|
else:
|
||||||
keys = await conn.keys(self.generate_key('*'))
|
keys = await conn.keys(self.generate_key("*"))
|
||||||
await conn.delete(*keys)
|
await conn.delete(*keys)
|
||||||
|
|
||||||
async def get_states_list(self) -> typing.List[typing.Tuple[int]]:
|
async def get_states_list(self) -> typing.List[typing.Tuple[int]]:
|
||||||
|
|
@ -364,9 +482,9 @@ class RedisStorage2(BaseStorage):
|
||||||
conn = await self.redis()
|
conn = await self.redis()
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
keys = await conn.keys(self.generate_key('*', '*', STATE_KEY), encoding='utf8')
|
keys = await conn.keys(self.generate_key("*", "*", STATE_KEY), encoding="utf8")
|
||||||
for item in keys:
|
for item in keys:
|
||||||
*_, chat, user, _ = item.split(':')
|
*_, chat, user, _ = item.split(":")
|
||||||
result.append((chat, user))
|
result.append((chat, user))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
@ -388,7 +506,7 @@ async def migrate_redis1_to_redis2(storage1: RedisStorage, storage2: RedisStorag
|
||||||
if not isinstance(storage2, RedisStorage):
|
if not isinstance(storage2, RedisStorage):
|
||||||
raise TypeError(f"{type(storage2)} is not RedisStorage instance.")
|
raise TypeError(f"{type(storage2)} is not RedisStorage instance.")
|
||||||
|
|
||||||
log = logging.getLogger('aiogram.RedisStorage')
|
log = logging.getLogger("aiogram.RedisStorage")
|
||||||
|
|
||||||
for chat, user in await storage1.get_states_list():
|
for chat, user in await storage1.get_states_list():
|
||||||
state = await storage1.get_state(chat=chat, user=user)
|
state = await storage1.get_state(chat=chat, user=user)
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ from rethinkdb.asyncio_net.net_asyncio import Connection
|
||||||
|
|
||||||
from ...dispatcher.storage import BaseStorage
|
from ...dispatcher.storage import BaseStorage
|
||||||
|
|
||||||
__all__ = ['RethinkDBStorage']
|
__all__ = ["RethinkDBStorage"]
|
||||||
|
|
||||||
r = rethinkdb.RethinkDB()
|
r = rethinkdb.RethinkDB()
|
||||||
r.set_loop_type('asyncio')
|
r.set_loop_type("asyncio")
|
||||||
|
|
||||||
|
|
||||||
class RethinkDBStorage(BaseStorage):
|
class RethinkDBStorage(BaseStorage):
|
||||||
|
|
@ -32,17 +32,19 @@ class RethinkDBStorage(BaseStorage):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(
|
||||||
host: str = 'localhost',
|
self,
|
||||||
port: int = 28015,
|
host: str = "localhost",
|
||||||
db: str = 'aiogram',
|
port: int = 28015,
|
||||||
table: str = 'aiogram',
|
db: str = "aiogram",
|
||||||
auth_key: typing.Optional[str] = None,
|
table: str = "aiogram",
|
||||||
user: typing.Optional[str] = None,
|
auth_key: typing.Optional[str] = None,
|
||||||
password: typing.Optional[str] = None,
|
user: typing.Optional[str] = None,
|
||||||
timeout: int = 20,
|
password: typing.Optional[str] = None,
|
||||||
ssl: typing.Optional[dict] = None,
|
timeout: int = 20,
|
||||||
loop: typing.Optional[asyncio.AbstractEventLoop] = None):
|
ssl: typing.Optional[dict] = None,
|
||||||
|
loop: typing.Optional[asyncio.AbstractEventLoop] = None,
|
||||||
|
):
|
||||||
self._host = host
|
self._host = host
|
||||||
self._port = port
|
self._port = port
|
||||||
self._db = db
|
self._db = db
|
||||||
|
|
@ -61,15 +63,17 @@ class RethinkDBStorage(BaseStorage):
|
||||||
Get or create a connection.
|
Get or create a connection.
|
||||||
"""
|
"""
|
||||||
if self._conn is None:
|
if self._conn is None:
|
||||||
self._conn = await r.connect(host=self._host,
|
self._conn = await r.connect(
|
||||||
port=self._port,
|
host=self._host,
|
||||||
db=self._db,
|
port=self._port,
|
||||||
auth_key=self._auth_key,
|
db=self._db,
|
||||||
user=self._user,
|
auth_key=self._auth_key,
|
||||||
password=self._password,
|
user=self._user,
|
||||||
timeout=self._timeout,
|
password=self._password,
|
||||||
ssl=self._ssl,
|
timeout=self._timeout,
|
||||||
io_loop=self._loop)
|
ssl=self._ssl,
|
||||||
|
io_loop=self._loop,
|
||||||
|
)
|
||||||
return self._conn
|
return self._conn
|
||||||
|
|
||||||
@contextlib.asynccontextmanager
|
@contextlib.asynccontextmanager
|
||||||
|
|
@ -90,64 +94,126 @@ class RethinkDBStorage(BaseStorage):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_state(
|
||||||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Optional[str]:
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
async with self.connection() as conn:
|
async with self.connection() as conn:
|
||||||
return await r.table(self._table).get(chat)[user]['state'].default(default or None).run(conn)
|
return (
|
||||||
|
await r.table(self._table)
|
||||||
|
.get(chat)[user]["state"]
|
||||||
|
.default(default or None)
|
||||||
|
.run(conn)
|
||||||
|
)
|
||||||
|
|
||||||
async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_data(
|
||||||
default: typing.Optional[str] = None) -> typing.Dict:
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
async with self.connection() as conn:
|
async with self.connection() as conn:
|
||||||
return await r.table(self._table).get(chat)[user]['data'].default(default or {}).run(conn)
|
return (
|
||||||
|
await r.table(self._table).get(chat)[user]["data"].default(default or {}).run(conn)
|
||||||
|
)
|
||||||
|
|
||||||
async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_state(
|
||||||
state: typing.Optional[typing.AnyStr] = None):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
state: typing.Optional[typing.AnyStr] = None,
|
||||||
|
):
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
async with self.connection() as conn:
|
async with self.connection() as conn:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'state': state}}, conflict="update").run(conn)
|
await r.table(self._table).insert(
|
||||||
|
{"id": chat, user: {"state": state}}, conflict="update"
|
||||||
|
).run(conn)
|
||||||
|
|
||||||
async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_data(
|
||||||
data: typing.Dict = None):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
):
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
async with self.connection() as conn:
|
async with self.connection() as conn:
|
||||||
if await r.table(self._table).get(chat).run(conn):
|
if await r.table(self._table).get(chat).run(conn):
|
||||||
await r.table(self._table).get(chat).update({user: {'data': r.literal(data)}}).run(conn)
|
await r.table(self._table).get(chat).update({user: {"data": r.literal(data)}}).run(
|
||||||
|
conn
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'data': data}}).run(conn)
|
await r.table(self._table).insert({"id": chat, user: {"data": data}}).run(conn)
|
||||||
|
|
||||||
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def update_data(
|
||||||
data: typing.Dict = None,
|
self,
|
||||||
**kwargs):
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
async with self.connection() as conn:
|
async with self.connection() as conn:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'data': data}}, conflict="update").run(conn)
|
await r.table(self._table).insert(
|
||||||
|
{"id": chat, user: {"data": data}}, conflict="update"
|
||||||
|
).run(conn)
|
||||||
|
|
||||||
def has_bucket(self):
|
def has_bucket(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_bucket(
|
||||||
default: typing.Optional[dict] = None) -> typing.Dict:
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[dict] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
async with self.connection() as conn:
|
async with self.connection() as conn:
|
||||||
return await r.table(self._table).get(chat)[user]['bucket'].default(default or {}).run(conn)
|
return (
|
||||||
|
await r.table(self._table)
|
||||||
|
.get(chat)[user]["bucket"]
|
||||||
|
.default(default or {})
|
||||||
|
.run(conn)
|
||||||
|
)
|
||||||
|
|
||||||
async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_bucket(
|
||||||
bucket: typing.Dict = None):
|
self,
|
||||||
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None,
|
||||||
|
):
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
async with self.connection() as conn:
|
async with self.connection() as conn:
|
||||||
if await r.table(self._table).get(chat).run(conn):
|
if await r.table(self._table).get(chat).run(conn):
|
||||||
await r.table(self._table).get(chat).update({user: {'bucket': r.literal(bucket)}}).run(conn)
|
await r.table(self._table).get(chat).update(
|
||||||
|
{user: {"bucket": r.literal(bucket)}}
|
||||||
|
).run(conn)
|
||||||
else:
|
else:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'bucket': bucket}}).run(conn)
|
await r.table(self._table).insert({"id": chat, user: {"bucket": bucket}}).run(conn)
|
||||||
|
|
||||||
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
|
async def update_bucket(
|
||||||
user: typing.Union[str, int, None] = None, bucket: typing.Dict = None,
|
self,
|
||||||
**kwargs):
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
async with self.connection() as conn:
|
async with self.connection() as conn:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'bucket': bucket}}, conflict="update").run(conn)
|
await r.table(self._table).insert(
|
||||||
|
{"id": chat, user: {"bucket": bucket}}, conflict="update"
|
||||||
|
).run(conn)
|
||||||
|
|
||||||
async def get_states_list(self) -> typing.List[typing.Tuple[int, int]]:
|
async def get_states_list(self) -> typing.List[typing.Tuple[int, int]]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -161,7 +227,7 @@ class RethinkDBStorage(BaseStorage):
|
||||||
items = (await r.table(self._table).run(conn)).items
|
items = (await r.table(self._table).run(conn)).items
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
chat = int(item.pop('id'))
|
chat = int(item.pop("id"))
|
||||||
for key in item.keys():
|
for key in item.keys():
|
||||||
user = int(key)
|
user = int(key)
|
||||||
result.append((chat, user))
|
result.append((chat, user))
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,11 @@ class EnvironmentMiddleware(BaseMiddleware):
|
||||||
|
|
||||||
def update_data(self, data):
|
def update_data(self, data):
|
||||||
dp = self.manager.dispatcher
|
dp = self.manager.dispatcher
|
||||||
data.update(
|
data.update(bot=dp.bot, dispatcher=dp, loop=dp.loop)
|
||||||
bot=dp.bot,
|
|
||||||
dispatcher=dp,
|
|
||||||
loop=dp.loop
|
|
||||||
)
|
|
||||||
if self.context:
|
if self.context:
|
||||||
data.update(self.context)
|
data.update(self.context)
|
||||||
|
|
||||||
async def trigger(self, action, args):
|
async def trigger(self, action, args):
|
||||||
if 'error' not in action and action.startswith('pre_process_'):
|
if "error" not in action and action.startswith("pre_process_"):
|
||||||
self.update_data(args[-1])
|
self.update_data(args[-1])
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from aiogram.dispatcher.storage import FSMContext
|
||||||
|
|
||||||
|
|
||||||
class FSMMiddleware(LifetimeControllerMiddleware):
|
class FSMMiddleware(LifetimeControllerMiddleware):
|
||||||
skip_patterns = ['error', 'update']
|
skip_patterns = ["error", "update"]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(FSMMiddleware, self).__init__()
|
super(FSMMiddleware, self).__init__()
|
||||||
|
|
@ -14,10 +14,10 @@ class FSMMiddleware(LifetimeControllerMiddleware):
|
||||||
|
|
||||||
async def pre_process(self, obj, data, *args):
|
async def pre_process(self, obj, data, *args):
|
||||||
proxy = await FSMSStorageProxy.create(self.manager.dispatcher.current_state())
|
proxy = await FSMSStorageProxy.create(self.manager.dispatcher.current_state())
|
||||||
data['state_data'] = proxy
|
data["state_data"] = proxy
|
||||||
|
|
||||||
async def post_process(self, obj, data, *args):
|
async def post_process(self, obj, data, *args):
|
||||||
proxy = data.get('state_data', None)
|
proxy = data.get("state_data", None)
|
||||||
if isinstance(proxy, FSMSStorageProxy):
|
if isinstance(proxy, FSMSStorageProxy):
|
||||||
await proxy.save()
|
await proxy.save()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ class I18nMiddleware(BaseMiddleware):
|
||||||
>>> _ = i18n = I18nMiddleware(DOMAIN_NAME, LOCALES_DIR)
|
>>> _ = i18n = I18nMiddleware(DOMAIN_NAME, LOCALES_DIR)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ctx_locale = ContextVar('ctx_user_locale', default=None)
|
ctx_locale = ContextVar("ctx_user_locale", default=None)
|
||||||
|
|
||||||
def __init__(self, domain, path=None, default='en'):
|
def __init__(self, domain, path=None, default="en"):
|
||||||
"""
|
"""
|
||||||
:param domain: domain
|
:param domain: domain
|
||||||
:param path: path where located all *.mo files
|
:param path: path where located all *.mo files
|
||||||
|
|
@ -34,7 +34,7 @@ class I18nMiddleware(BaseMiddleware):
|
||||||
super(I18nMiddleware, self).__init__()
|
super(I18nMiddleware, self).__init__()
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
path = os.path.join(os.getcwd(), 'locales')
|
path = os.path.join(os.getcwd(), "locales")
|
||||||
|
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
@ -53,12 +53,12 @@ class I18nMiddleware(BaseMiddleware):
|
||||||
for name in os.listdir(self.path):
|
for name in os.listdir(self.path):
|
||||||
if not os.path.isdir(os.path.join(self.path, name)):
|
if not os.path.isdir(os.path.join(self.path, name)):
|
||||||
continue
|
continue
|
||||||
mo_path = os.path.join(self.path, name, 'LC_MESSAGES', self.domain + '.mo')
|
mo_path = os.path.join(self.path, name, "LC_MESSAGES", self.domain + ".mo")
|
||||||
|
|
||||||
if os.path.exists(mo_path):
|
if os.path.exists(mo_path):
|
||||||
with open(mo_path, 'rb') as fp:
|
with open(mo_path, "rb") as fp:
|
||||||
translations[name] = gettext.GNUTranslations(fp)
|
translations[name] = gettext.GNUTranslations(fp)
|
||||||
elif os.path.exists(mo_path[:-2] + 'po'):
|
elif os.path.exists(mo_path[:-2] + "po"):
|
||||||
raise RuntimeError(f"Found locale '{name} but this language is not compiled!")
|
raise RuntimeError(f"Found locale '{name} but this language is not compiled!")
|
||||||
|
|
||||||
return translations
|
return translations
|
||||||
|
|
@ -134,7 +134,7 @@ class I18nMiddleware(BaseMiddleware):
|
||||||
|
|
||||||
if locale:
|
if locale:
|
||||||
*_, data = args
|
*_, data = args
|
||||||
language = data['locale'] = locale.language
|
language = data["locale"] = locale.language
|
||||||
return language
|
return language
|
||||||
|
|
||||||
async def trigger(self, action, args):
|
async def trigger(self, action, args):
|
||||||
|
|
@ -145,9 +145,7 @@ class I18nMiddleware(BaseMiddleware):
|
||||||
:param args: event arguments
|
:param args: event arguments
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if 'update' not in action \
|
if "update" not in action and "error" not in action and action.startswith("pre_process"):
|
||||||
and 'error' not in action \
|
|
||||||
and action.startswith('pre_process'):
|
|
||||||
locale = await self.get_user_locale(action, args)
|
locale = await self.get_user_locale(action, args)
|
||||||
self.ctx_locale.set(locale)
|
self.ctx_locale.set(locale)
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import logging
|
||||||
from aiogram import types
|
from aiogram import types
|
||||||
from aiogram.dispatcher.middlewares import BaseMiddleware
|
from aiogram.dispatcher.middlewares import BaseMiddleware
|
||||||
|
|
||||||
HANDLED_STR = ['Unhandled', 'Handled']
|
HANDLED_STR = ["Unhandled", "Handled"]
|
||||||
|
|
||||||
|
|
||||||
class LoggingMiddleware(BaseMiddleware):
|
class LoggingMiddleware(BaseMiddleware):
|
||||||
|
|
@ -18,123 +18,181 @@ class LoggingMiddleware(BaseMiddleware):
|
||||||
super(LoggingMiddleware, self).__init__()
|
super(LoggingMiddleware, self).__init__()
|
||||||
|
|
||||||
def check_timeout(self, obj):
|
def check_timeout(self, obj):
|
||||||
start = obj.conf.get('_start', None)
|
start = obj.conf.get("_start", None)
|
||||||
if start:
|
if start:
|
||||||
del obj.conf['_start']
|
del obj.conf["_start"]
|
||||||
return round((time.time() - start) * 1000)
|
return round((time.time() - start) * 1000)
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
async def on_pre_process_update(self, update: types.Update, data: dict):
|
async def on_pre_process_update(self, update: types.Update, data: dict):
|
||||||
update.conf['_start'] = time.time()
|
update.conf["_start"] = time.time()
|
||||||
self.logger.debug(f"Received update [ID:{update.update_id}]")
|
self.logger.debug(f"Received update [ID:{update.update_id}]")
|
||||||
|
|
||||||
async def on_post_process_update(self, update: types.Update, result, data: dict):
|
async def on_post_process_update(self, update: types.Update, result, data: dict):
|
||||||
timeout = self.check_timeout(update)
|
timeout = self.check_timeout(update)
|
||||||
if timeout > 0:
|
if timeout > 0:
|
||||||
self.logger.info(f"Process update [ID:{update.update_id}]: [success] (in {timeout} ms)")
|
self.logger.info(
|
||||||
|
f"Process update [ID:{update.update_id}]: [success] (in {timeout} ms)"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_pre_process_message(self, message: types.Message, data: dict):
|
async def on_pre_process_message(self, message: types.Message, data: dict):
|
||||||
self.logger.info(f"Received message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]")
|
self.logger.info(
|
||||||
|
f"Received message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_post_process_message(self, message: types.Message, results, data: dict):
|
async def on_post_process_message(self, message: types.Message, results, data: dict):
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self.logger.debug(
|
||||||
f"message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]")
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
|
f"message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_pre_process_edited_message(self, edited_message, data: dict):
|
async def on_pre_process_edited_message(self, edited_message, data: dict):
|
||||||
self.logger.info(f"Received edited message [ID:{edited_message.message_id}] "
|
self.logger.info(
|
||||||
f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]")
|
f"Received edited message [ID:{edited_message.message_id}] "
|
||||||
|
f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_post_process_edited_message(self, edited_message, results, data: dict):
|
async def on_post_process_edited_message(self, edited_message, results, data: dict):
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self.logger.debug(
|
||||||
f"edited message [ID:{edited_message.message_id}] "
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]")
|
f"edited message [ID:{edited_message.message_id}] "
|
||||||
|
f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_pre_process_channel_post(self, channel_post: types.Message, data: dict):
|
async def on_pre_process_channel_post(self, channel_post: types.Message, data: dict):
|
||||||
self.logger.info(f"Received channel post [ID:{channel_post.message_id}] "
|
self.logger.info(
|
||||||
f"in channel [ID:{channel_post.chat.id}]")
|
f"Received channel post [ID:{channel_post.message_id}] "
|
||||||
|
f"in channel [ID:{channel_post.chat.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_post_process_channel_post(self, channel_post: types.Message, results, data: dict):
|
async def on_post_process_channel_post(self, channel_post: types.Message, results, data: dict):
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self.logger.debug(
|
||||||
f"channel post [ID:{channel_post.message_id}] "
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
f"in chat [{channel_post.chat.type}:{channel_post.chat.id}]")
|
f"channel post [ID:{channel_post.message_id}] "
|
||||||
|
f"in chat [{channel_post.chat.type}:{channel_post.chat.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_pre_process_edited_channel_post(self, edited_channel_post: types.Message, data: dict):
|
async def on_pre_process_edited_channel_post(
|
||||||
self.logger.info(f"Received edited channel post [ID:{edited_channel_post.message_id}] "
|
self, edited_channel_post: types.Message, data: dict
|
||||||
f"in channel [ID:{edited_channel_post.chat.id}]")
|
):
|
||||||
|
self.logger.info(
|
||||||
|
f"Received edited channel post [ID:{edited_channel_post.message_id}] "
|
||||||
|
f"in channel [ID:{edited_channel_post.chat.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_post_process_edited_channel_post(self, edited_channel_post: types.Message, results, data: dict):
|
async def on_post_process_edited_channel_post(
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self, edited_channel_post: types.Message, results, data: dict
|
||||||
f"edited channel post [ID:{edited_channel_post.message_id}] "
|
):
|
||||||
f"in channel [ID:{edited_channel_post.chat.id}]")
|
self.logger.debug(
|
||||||
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
|
f"edited channel post [ID:{edited_channel_post.message_id}] "
|
||||||
|
f"in channel [ID:{edited_channel_post.chat.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_pre_process_inline_query(self, inline_query: types.InlineQuery, data: dict):
|
async def on_pre_process_inline_query(self, inline_query: types.InlineQuery, data: dict):
|
||||||
self.logger.info(f"Received inline query [ID:{inline_query.id}] "
|
self.logger.info(
|
||||||
f"from user [ID:{inline_query.from_user.id}]")
|
f"Received inline query [ID:{inline_query.id}] "
|
||||||
|
f"from user [ID:{inline_query.from_user.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_post_process_inline_query(self, inline_query: types.InlineQuery, results, data: dict):
|
async def on_post_process_inline_query(
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self, inline_query: types.InlineQuery, results, data: dict
|
||||||
f"inline query [ID:{inline_query.id}] "
|
):
|
||||||
f"from user [ID:{inline_query.from_user.id}]")
|
self.logger.debug(
|
||||||
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
|
f"inline query [ID:{inline_query.id}] "
|
||||||
|
f"from user [ID:{inline_query.from_user.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_pre_process_chosen_inline_result(self, chosen_inline_result: types.ChosenInlineResult, data: dict):
|
async def on_pre_process_chosen_inline_result(
|
||||||
self.logger.info(f"Received chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] "
|
self, chosen_inline_result: types.ChosenInlineResult, data: dict
|
||||||
f"from user [ID:{chosen_inline_result.from_user.id}] "
|
):
|
||||||
f"result [ID:{chosen_inline_result.result_id}]")
|
self.logger.info(
|
||||||
|
f"Received chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] "
|
||||||
|
f"from user [ID:{chosen_inline_result.from_user.id}] "
|
||||||
|
f"result [ID:{chosen_inline_result.result_id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_post_process_chosen_inline_result(self, chosen_inline_result, results, data: dict):
|
async def on_post_process_chosen_inline_result(
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self, chosen_inline_result, results, data: dict
|
||||||
f"chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] "
|
):
|
||||||
f"from user [ID:{chosen_inline_result.from_user.id}] "
|
self.logger.debug(
|
||||||
f"result [ID:{chosen_inline_result.result_id}]")
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
|
f"chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] "
|
||||||
|
f"from user [ID:{chosen_inline_result.from_user.id}] "
|
||||||
|
f"result [ID:{chosen_inline_result.result_id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_pre_process_callback_query(self, callback_query: types.CallbackQuery, data: dict):
|
async def on_pre_process_callback_query(self, callback_query: types.CallbackQuery, data: dict):
|
||||||
if callback_query.message:
|
if callback_query.message:
|
||||||
if callback_query.message.from_user:
|
if callback_query.message.from_user:
|
||||||
self.logger.info(f"Received callback query [ID:{callback_query.id}] "
|
self.logger.info(
|
||||||
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}] "
|
f"Received callback query [ID:{callback_query.id}] "
|
||||||
f"from user [ID:{callback_query.message.from_user.id}]")
|
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}] "
|
||||||
|
f"from user [ID:{callback_query.message.from_user.id}]"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.logger.info(f"Received callback query [ID:{callback_query.id}] "
|
self.logger.info(
|
||||||
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]")
|
f"Received callback query [ID:{callback_query.id}] "
|
||||||
|
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.logger.info(f"Received callback query [ID:{callback_query.id}] "
|
self.logger.info(
|
||||||
f"from inline message [ID:{callback_query.inline_message_id}] "
|
f"Received callback query [ID:{callback_query.id}] "
|
||||||
f"from user [ID:{callback_query.from_user.id}]")
|
f"from inline message [ID:{callback_query.inline_message_id}] "
|
||||||
|
f"from user [ID:{callback_query.from_user.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_post_process_callback_query(self, callback_query, results, data: dict):
|
async def on_post_process_callback_query(self, callback_query, results, data: dict):
|
||||||
if callback_query.message:
|
if callback_query.message:
|
||||||
if callback_query.message.from_user:
|
if callback_query.message.from_user:
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self.logger.debug(
|
||||||
f"callback query [ID:{callback_query.id}] "
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}] "
|
f"callback query [ID:{callback_query.id}] "
|
||||||
f"from user [ID:{callback_query.message.from_user.id}]")
|
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}] "
|
||||||
|
f"from user [ID:{callback_query.message.from_user.id}]"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self.logger.debug(
|
||||||
f"callback query [ID:{callback_query.id}] "
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]")
|
f"callback query [ID:{callback_query.id}] "
|
||||||
|
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self.logger.debug(
|
||||||
f"callback query [ID:{callback_query.id}] "
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
f"from inline message [ID:{callback_query.inline_message_id}] "
|
f"callback query [ID:{callback_query.id}] "
|
||||||
f"from user [ID:{callback_query.from_user.id}]")
|
f"from inline message [ID:{callback_query.inline_message_id}] "
|
||||||
|
f"from user [ID:{callback_query.from_user.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_pre_process_shipping_query(self, shipping_query: types.ShippingQuery, data: dict):
|
async def on_pre_process_shipping_query(self, shipping_query: types.ShippingQuery, data: dict):
|
||||||
self.logger.info(f"Received shipping query [ID:{shipping_query.id}] "
|
self.logger.info(
|
||||||
f"from user [ID:{shipping_query.from_user.id}]")
|
f"Received shipping query [ID:{shipping_query.id}] "
|
||||||
|
f"from user [ID:{shipping_query.from_user.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_post_process_shipping_query(self, shipping_query, results, data: dict):
|
async def on_post_process_shipping_query(self, shipping_query, results, data: dict):
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self.logger.debug(
|
||||||
f"shipping query [ID:{shipping_query.id}] "
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
f"from user [ID:{shipping_query.from_user.id}]")
|
f"shipping query [ID:{shipping_query.id}] "
|
||||||
|
f"from user [ID:{shipping_query.from_user.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_pre_process_pre_checkout_query(self, pre_checkout_query: types.PreCheckoutQuery, data: dict):
|
async def on_pre_process_pre_checkout_query(
|
||||||
self.logger.info(f"Received pre-checkout query [ID:{pre_checkout_query.id}] "
|
self, pre_checkout_query: types.PreCheckoutQuery, data: dict
|
||||||
f"from user [ID:{pre_checkout_query.from_user.id}]")
|
):
|
||||||
|
self.logger.info(
|
||||||
|
f"Received pre-checkout query [ID:{pre_checkout_query.id}] "
|
||||||
|
f"from user [ID:{pre_checkout_query.from_user.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_post_process_pre_checkout_query(self, pre_checkout_query, results, data: dict):
|
async def on_post_process_pre_checkout_query(self, pre_checkout_query, results, data: dict):
|
||||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
self.logger.debug(
|
||||||
f"pre-checkout query [ID:{pre_checkout_query.id}] "
|
f"{HANDLED_STR[bool(len(results))]} "
|
||||||
f"from user [ID:{pre_checkout_query.from_user.id}]")
|
f"pre-checkout query [ID:{pre_checkout_query.id}] "
|
||||||
|
f"from user [ID:{pre_checkout_query.from_user.id}]"
|
||||||
|
)
|
||||||
|
|
||||||
async def on_pre_process_error(self, update, error, data: dict):
|
async def on_pre_process_error(self, update, error, data: dict):
|
||||||
timeout = self.check_timeout(update)
|
timeout = self.check_timeout(update)
|
||||||
|
|
@ -168,7 +226,7 @@ class LoggingFilter(logging.Filter):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name='', prefix='tg', include_content=False):
|
def __init__(self, name="", prefix="tg", include_content=False):
|
||||||
"""
|
"""
|
||||||
:param name:
|
:param name:
|
||||||
:param prefix: prefix for all records
|
:param prefix: prefix for all records
|
||||||
|
|
@ -200,34 +258,34 @@ class LoggingFilter(logging.Filter):
|
||||||
:param update:
|
:param update:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
yield 'update_id', update.update_id
|
yield "update_id", update.update_id
|
||||||
|
|
||||||
if update.message:
|
if update.message:
|
||||||
yield 'update_type', 'message'
|
yield "update_type", "message"
|
||||||
yield from self.process_message(update.message)
|
yield from self.process_message(update.message)
|
||||||
if update.edited_message:
|
if update.edited_message:
|
||||||
yield 'update_type', 'edited_message'
|
yield "update_type", "edited_message"
|
||||||
yield from self.process_message(update.edited_message)
|
yield from self.process_message(update.edited_message)
|
||||||
if update.channel_post:
|
if update.channel_post:
|
||||||
yield 'update_type', 'channel_post'
|
yield "update_type", "channel_post"
|
||||||
yield from self.process_message(update.channel_post)
|
yield from self.process_message(update.channel_post)
|
||||||
if update.edited_channel_post:
|
if update.edited_channel_post:
|
||||||
yield 'update_type', 'edited_channel_post'
|
yield "update_type", "edited_channel_post"
|
||||||
yield from self.process_message(update.edited_channel_post)
|
yield from self.process_message(update.edited_channel_post)
|
||||||
if update.inline_query:
|
if update.inline_query:
|
||||||
yield 'update_type', 'inline_query'
|
yield "update_type", "inline_query"
|
||||||
yield from self.process_inline_query(update.inline_query)
|
yield from self.process_inline_query(update.inline_query)
|
||||||
if update.chosen_inline_result:
|
if update.chosen_inline_result:
|
||||||
yield 'update_type', 'chosen_inline_result'
|
yield "update_type", "chosen_inline_result"
|
||||||
yield from self.process_chosen_inline_result(update.chosen_inline_result)
|
yield from self.process_chosen_inline_result(update.chosen_inline_result)
|
||||||
if update.callback_query:
|
if update.callback_query:
|
||||||
yield 'update_type', 'callback_query'
|
yield "update_type", "callback_query"
|
||||||
yield from self.process_callback_query(update.callback_query)
|
yield from self.process_callback_query(update.callback_query)
|
||||||
if update.shipping_query:
|
if update.shipping_query:
|
||||||
yield 'update_type', 'shipping_query'
|
yield "update_type", "shipping_query"
|
||||||
yield from self.process_shipping_query(update.shipping_query)
|
yield from self.process_shipping_query(update.shipping_query)
|
||||||
if update.pre_checkout_query:
|
if update.pre_checkout_query:
|
||||||
yield 'update_type', 'pre_checkout_query'
|
yield "update_type", "pre_checkout_query"
|
||||||
yield from self.process_pre_checkout_query(update.pre_checkout_query)
|
yield from self.process_pre_checkout_query(update.pre_checkout_query)
|
||||||
|
|
||||||
def make_prefix(self, prefix, iterable):
|
def make_prefix(self, prefix, iterable):
|
||||||
|
|
@ -254,11 +312,11 @@ class LoggingFilter(logging.Filter):
|
||||||
if not user:
|
if not user:
|
||||||
return
|
return
|
||||||
|
|
||||||
yield 'user_id', user.id
|
yield "user_id", user.id
|
||||||
if self.include_content:
|
if self.include_content:
|
||||||
yield 'user_full_name', user.full_name
|
yield "user_full_name", user.full_name
|
||||||
if user.username:
|
if user.username:
|
||||||
yield 'user_name', f"@{user.username}"
|
yield "user_name", f"@{user.username}"
|
||||||
|
|
||||||
def process_chat(self, chat: types.Chat):
|
def process_chat(self, chat: types.Chat):
|
||||||
"""
|
"""
|
||||||
|
|
@ -270,15 +328,15 @@ class LoggingFilter(logging.Filter):
|
||||||
if not chat:
|
if not chat:
|
||||||
return
|
return
|
||||||
|
|
||||||
yield 'chat_id', chat.id
|
yield "chat_id", chat.id
|
||||||
yield 'chat_type', chat.type
|
yield "chat_type", chat.type
|
||||||
if self.include_content:
|
if self.include_content:
|
||||||
yield 'chat_title', chat.full_name
|
yield "chat_title", chat.full_name
|
||||||
if chat.username:
|
if chat.username:
|
||||||
yield 'chat_name', f"@{chat.username}"
|
yield "chat_name", f"@{chat.username}"
|
||||||
|
|
||||||
def process_message(self, message: types.Message):
|
def process_message(self, message: types.Message):
|
||||||
yield 'message_content_type', message.content_type
|
yield "message_content_type", message.content_type
|
||||||
yield from self.process_user(message.from_user)
|
yield from self.process_user(message.from_user)
|
||||||
yield from self.process_chat(message.chat)
|
yield from self.process_chat(message.chat)
|
||||||
|
|
||||||
|
|
@ -286,82 +344,84 @@ class LoggingFilter(logging.Filter):
|
||||||
return
|
return
|
||||||
|
|
||||||
if message.reply_to_message:
|
if message.reply_to_message:
|
||||||
yield from self.make_prefix('reply_to', self.process_message(message.reply_to_message))
|
yield from self.make_prefix("reply_to", self.process_message(message.reply_to_message))
|
||||||
if message.forward_from:
|
if message.forward_from:
|
||||||
yield from self.make_prefix('forward_from', self.process_user(message.forward_from))
|
yield from self.make_prefix("forward_from", self.process_user(message.forward_from))
|
||||||
if message.forward_from_chat:
|
if message.forward_from_chat:
|
||||||
yield from self.make_prefix('forward_from_chat', self.process_chat(message.forward_from_chat))
|
yield from self.make_prefix(
|
||||||
|
"forward_from_chat", self.process_chat(message.forward_from_chat)
|
||||||
|
)
|
||||||
if message.forward_from_message_id:
|
if message.forward_from_message_id:
|
||||||
yield 'message_forward_from_message_id', message.forward_from_message_id
|
yield "message_forward_from_message_id", message.forward_from_message_id
|
||||||
if message.forward_date:
|
if message.forward_date:
|
||||||
yield 'message_forward_date', message.forward_date
|
yield "message_forward_date", message.forward_date
|
||||||
if message.edit_date:
|
if message.edit_date:
|
||||||
yield 'message_edit_date', message.edit_date
|
yield "message_edit_date", message.edit_date
|
||||||
if message.media_group_id:
|
if message.media_group_id:
|
||||||
yield 'message_media_group_id', message.media_group_id
|
yield "message_media_group_id", message.media_group_id
|
||||||
if message.author_signature:
|
if message.author_signature:
|
||||||
yield 'message_author_signature', message.author_signature
|
yield "message_author_signature", message.author_signature
|
||||||
|
|
||||||
if message.text:
|
if message.text:
|
||||||
yield 'text', message.text or message.caption
|
yield "text", message.text or message.caption
|
||||||
yield 'html_text', message.html_text
|
yield "html_text", message.html_text
|
||||||
elif message.audio:
|
elif message.audio:
|
||||||
yield 'audio', message.audio.file_id
|
yield "audio", message.audio.file_id
|
||||||
elif message.animation:
|
elif message.animation:
|
||||||
yield 'animation', message.animation.file_id
|
yield "animation", message.animation.file_id
|
||||||
elif message.document:
|
elif message.document:
|
||||||
yield 'document', message.document.file_id
|
yield "document", message.document.file_id
|
||||||
elif message.game:
|
elif message.game:
|
||||||
yield 'game', message.game.title
|
yield "game", message.game.title
|
||||||
elif message.photo:
|
elif message.photo:
|
||||||
yield 'photo', message.photo[-1].file_id
|
yield "photo", message.photo[-1].file_id
|
||||||
elif message.sticker:
|
elif message.sticker:
|
||||||
yield 'sticker', message.sticker.file_id
|
yield "sticker", message.sticker.file_id
|
||||||
elif message.video:
|
elif message.video:
|
||||||
yield 'video', message.video.file_id
|
yield "video", message.video.file_id
|
||||||
elif message.video_note:
|
elif message.video_note:
|
||||||
yield 'video_note', message.video_note.file_id
|
yield "video_note", message.video_note.file_id
|
||||||
elif message.voice:
|
elif message.voice:
|
||||||
yield 'voice', message.voice.file_id
|
yield "voice", message.voice.file_id
|
||||||
elif message.contact:
|
elif message.contact:
|
||||||
yield 'contact_full_name', message.contact.full_name
|
yield "contact_full_name", message.contact.full_name
|
||||||
yield 'contact_phone_number', message.contact.phone_number
|
yield "contact_phone_number", message.contact.phone_number
|
||||||
elif message.venue:
|
elif message.venue:
|
||||||
yield 'venue_address', message.venue.address
|
yield "venue_address", message.venue.address
|
||||||
yield 'location_latitude', message.venue.location.latitude
|
yield "location_latitude", message.venue.location.latitude
|
||||||
yield 'location_longitude', message.venue.location.longitude
|
yield "location_longitude", message.venue.location.longitude
|
||||||
elif message.location:
|
elif message.location:
|
||||||
yield 'location_latitude', message.location.latitude
|
yield "location_latitude", message.location.latitude
|
||||||
yield 'location_longitude', message.location.longitude
|
yield "location_longitude", message.location.longitude
|
||||||
elif message.new_chat_members:
|
elif message.new_chat_members:
|
||||||
yield 'new_chat_members', [user.id for user in message.new_chat_members]
|
yield "new_chat_members", [user.id for user in message.new_chat_members]
|
||||||
elif message.left_chat_member:
|
elif message.left_chat_member:
|
||||||
yield 'left_chat_member', [user.id for user in message.new_chat_members]
|
yield "left_chat_member", [user.id for user in message.new_chat_members]
|
||||||
elif message.invoice:
|
elif message.invoice:
|
||||||
yield 'invoice_title', message.invoice.title
|
yield "invoice_title", message.invoice.title
|
||||||
yield 'invoice_description', message.invoice.description
|
yield "invoice_description", message.invoice.description
|
||||||
yield 'invoice_start_parameter', message.invoice.start_parameter
|
yield "invoice_start_parameter", message.invoice.start_parameter
|
||||||
yield 'invoice_currency', message.invoice.currency
|
yield "invoice_currency", message.invoice.currency
|
||||||
yield 'invoice_total_amount', message.invoice.total_amount
|
yield "invoice_total_amount", message.invoice.total_amount
|
||||||
elif message.successful_payment:
|
elif message.successful_payment:
|
||||||
yield 'successful_payment_currency', message.successful_payment.currency
|
yield "successful_payment_currency", message.successful_payment.currency
|
||||||
yield 'successful_payment_total_amount', message.successful_payment.total_amount
|
yield "successful_payment_total_amount", message.successful_payment.total_amount
|
||||||
yield 'successful_payment_invoice_payload', message.successful_payment.invoice_payload
|
yield "successful_payment_invoice_payload", message.successful_payment.invoice_payload
|
||||||
yield 'successful_payment_shipping_option_id', message.successful_payment.shipping_option_id
|
yield "successful_payment_shipping_option_id", message.successful_payment.shipping_option_id
|
||||||
yield 'successful_payment_telegram_payment_charge_id', message.successful_payment.telegram_payment_charge_id
|
yield "successful_payment_telegram_payment_charge_id", message.successful_payment.telegram_payment_charge_id
|
||||||
yield 'successful_payment_provider_payment_charge_id', message.successful_payment.provider_payment_charge_id
|
yield "successful_payment_provider_payment_charge_id", message.successful_payment.provider_payment_charge_id
|
||||||
elif message.connected_website:
|
elif message.connected_website:
|
||||||
yield 'connected_website', message.connected_website
|
yield "connected_website", message.connected_website
|
||||||
elif message.migrate_from_chat_id:
|
elif message.migrate_from_chat_id:
|
||||||
yield 'migrate_from_chat_id', message.migrate_from_chat_id
|
yield "migrate_from_chat_id", message.migrate_from_chat_id
|
||||||
elif message.migrate_to_chat_id:
|
elif message.migrate_to_chat_id:
|
||||||
yield 'migrate_to_chat_id', message.migrate_to_chat_id
|
yield "migrate_to_chat_id", message.migrate_to_chat_id
|
||||||
elif message.pinned_message:
|
elif message.pinned_message:
|
||||||
yield from self.make_prefix('pinned_message', message.pinned_message)
|
yield from self.make_prefix("pinned_message", message.pinned_message)
|
||||||
elif message.new_chat_title:
|
elif message.new_chat_title:
|
||||||
yield 'new_chat_title', message.new_chat_title
|
yield "new_chat_title", message.new_chat_title
|
||||||
elif message.new_chat_photo:
|
elif message.new_chat_photo:
|
||||||
yield 'new_chat_photo', message.new_chat_photo[-1].file_id
|
yield "new_chat_photo", message.new_chat_photo[-1].file_id
|
||||||
# elif message.delete_chat_photo:
|
# elif message.delete_chat_photo:
|
||||||
# yield 'delete_chat_photo', message.delete_chat_photo
|
# yield 'delete_chat_photo', message.delete_chat_photo
|
||||||
# elif message.group_chat_created:
|
# elif message.group_chat_created:
|
||||||
|
|
@ -370,53 +430,55 @@ class LoggingFilter(logging.Filter):
|
||||||
# yield 'passport_data', message.passport_data
|
# yield 'passport_data', message.passport_data
|
||||||
|
|
||||||
def process_inline_query(self, inline_query: types.InlineQuery):
|
def process_inline_query(self, inline_query: types.InlineQuery):
|
||||||
yield 'inline_query_id', inline_query.id
|
yield "inline_query_id", inline_query.id
|
||||||
yield from self.process_user(inline_query.from_user)
|
yield from self.process_user(inline_query.from_user)
|
||||||
|
|
||||||
if self.include_content:
|
if self.include_content:
|
||||||
yield 'inline_query_text', inline_query.query
|
yield "inline_query_text", inline_query.query
|
||||||
if inline_query.location:
|
if inline_query.location:
|
||||||
yield 'location_latitude', inline_query.location.latitude
|
yield "location_latitude", inline_query.location.latitude
|
||||||
yield 'location_longitude', inline_query.location.longitude
|
yield "location_longitude", inline_query.location.longitude
|
||||||
if inline_query.offset:
|
if inline_query.offset:
|
||||||
yield 'inline_query_offset', inline_query.offset
|
yield "inline_query_offset", inline_query.offset
|
||||||
|
|
||||||
def process_chosen_inline_result(self, chosen_inline_result: types.ChosenInlineResult):
|
def process_chosen_inline_result(self, chosen_inline_result: types.ChosenInlineResult):
|
||||||
yield 'chosen_inline_result_id', chosen_inline_result.result_id
|
yield "chosen_inline_result_id", chosen_inline_result.result_id
|
||||||
yield from self.process_user(chosen_inline_result.from_user)
|
yield from self.process_user(chosen_inline_result.from_user)
|
||||||
|
|
||||||
if self.include_content:
|
if self.include_content:
|
||||||
yield 'inline_query_text', chosen_inline_result.query
|
yield "inline_query_text", chosen_inline_result.query
|
||||||
if chosen_inline_result.location:
|
if chosen_inline_result.location:
|
||||||
yield 'location_latitude', chosen_inline_result.location.latitude
|
yield "location_latitude", chosen_inline_result.location.latitude
|
||||||
yield 'location_longitude', chosen_inline_result.location.longitude
|
yield "location_longitude", chosen_inline_result.location.longitude
|
||||||
|
|
||||||
def process_callback_query(self, callback_query: types.CallbackQuery):
|
def process_callback_query(self, callback_query: types.CallbackQuery):
|
||||||
yield from self.process_user(callback_query.from_user)
|
yield from self.process_user(callback_query.from_user)
|
||||||
yield 'callback_query_data', callback_query.data
|
yield "callback_query_data", callback_query.data
|
||||||
|
|
||||||
if callback_query.message:
|
if callback_query.message:
|
||||||
yield from self.make_prefix('callback_query_message', self.process_message(callback_query.message))
|
yield from self.make_prefix(
|
||||||
|
"callback_query_message", self.process_message(callback_query.message)
|
||||||
|
)
|
||||||
if callback_query.inline_message_id:
|
if callback_query.inline_message_id:
|
||||||
yield 'callback_query_inline_message_id', callback_query.inline_message_id
|
yield "callback_query_inline_message_id", callback_query.inline_message_id
|
||||||
if callback_query.chat_instance:
|
if callback_query.chat_instance:
|
||||||
yield 'callback_query_chat_instance', callback_query.chat_instance
|
yield "callback_query_chat_instance", callback_query.chat_instance
|
||||||
if callback_query.game_short_name:
|
if callback_query.game_short_name:
|
||||||
yield 'callback_query_game_short_name', callback_query.game_short_name
|
yield "callback_query_game_short_name", callback_query.game_short_name
|
||||||
|
|
||||||
def process_shipping_query(self, shipping_query: types.ShippingQuery):
|
def process_shipping_query(self, shipping_query: types.ShippingQuery):
|
||||||
yield 'shipping_query_id', shipping_query.id
|
yield "shipping_query_id", shipping_query.id
|
||||||
yield from self.process_user(shipping_query.from_user)
|
yield from self.process_user(shipping_query.from_user)
|
||||||
|
|
||||||
if self.include_content:
|
if self.include_content:
|
||||||
yield 'shipping_query_invoice_payload', shipping_query.invoice_payload
|
yield "shipping_query_invoice_payload", shipping_query.invoice_payload
|
||||||
|
|
||||||
def process_pre_checkout_query(self, pre_checkout_query: types.PreCheckoutQuery):
|
def process_pre_checkout_query(self, pre_checkout_query: types.PreCheckoutQuery):
|
||||||
yield 'pre_checkout_query_id', pre_checkout_query.id
|
yield "pre_checkout_query_id", pre_checkout_query.id
|
||||||
yield from self.process_user(pre_checkout_query.from_user)
|
yield from self.process_user(pre_checkout_query.from_user)
|
||||||
|
|
||||||
if self.include_content:
|
if self.include_content:
|
||||||
yield 'pre_checkout_query_currency', pre_checkout_query.currency
|
yield "pre_checkout_query_currency", pre_checkout_query.currency
|
||||||
yield 'pre_checkout_query_total_amount', pre_checkout_query.total_amount
|
yield "pre_checkout_query_total_amount", pre_checkout_query.total_amount
|
||||||
yield 'pre_checkout_query_invoice_payload', pre_checkout_query.invoice_payload
|
yield "pre_checkout_query_invoice_payload", pre_checkout_query.invoice_payload
|
||||||
yield 'pre_checkout_query_shipping_option_id', pre_checkout_query.shipping_option_id
|
yield "pre_checkout_query_shipping_option_id", pre_checkout_query.shipping_option_id
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ from . import webhook
|
||||||
from .dispatcher import Dispatcher, FSMContext, DEFAULT_RATE_LIMIT
|
from .dispatcher import Dispatcher, FSMContext, DEFAULT_RATE_LIMIT
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'DEFAULT_RATE_LIMIT',
|
"DEFAULT_RATE_LIMIT",
|
||||||
'Dispatcher',
|
"Dispatcher",
|
||||||
'FSMContext',
|
"FSMContext",
|
||||||
'filters',
|
"filters",
|
||||||
'handler',
|
"handler",
|
||||||
'middlewares',
|
"middlewares",
|
||||||
'storage',
|
"storage",
|
||||||
'webhook'
|
"webhook",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,29 @@ import typing
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from aiohttp.helpers import sentinel
|
from aiohttp.helpers import sentinel
|
||||||
|
|
||||||
from .filters import Command, ContentTypeFilter, ExceptionsFilter, FiltersFactory, HashTag, Regexp, \
|
from .filters import (
|
||||||
RegexpCommandsFilter, StateFilter, Text
|
Command,
|
||||||
|
ContentTypeFilter,
|
||||||
|
ExceptionsFilter,
|
||||||
|
FiltersFactory,
|
||||||
|
HashTag,
|
||||||
|
Regexp,
|
||||||
|
RegexpCommandsFilter,
|
||||||
|
StateFilter,
|
||||||
|
Text,
|
||||||
|
)
|
||||||
from .handler import Handler
|
from .handler import Handler
|
||||||
from .middlewares import MiddlewareManager
|
from .middlewares import MiddlewareManager
|
||||||
from .storage import BaseStorage, DELTA, DisabledStorage, EXCEEDED_COUNT, FSMContext, \
|
from .storage import (
|
||||||
LAST_CALL, RATE_LIMIT, RESULT
|
BaseStorage,
|
||||||
|
DELTA,
|
||||||
|
DisabledStorage,
|
||||||
|
EXCEEDED_COUNT,
|
||||||
|
FSMContext,
|
||||||
|
LAST_CALL,
|
||||||
|
RATE_LIMIT,
|
||||||
|
RESULT,
|
||||||
|
)
|
||||||
from .webhook import BaseResponse
|
from .webhook import BaseResponse
|
||||||
from .. import types
|
from .. import types
|
||||||
from ..bot import Bot
|
from ..bot import Bot
|
||||||
|
|
@ -22,7 +39,7 @@ from ..utils.mixins import ContextInstanceMixin, DataMixin
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_RATE_LIMIT = .1
|
DEFAULT_RATE_LIMIT = 0.1
|
||||||
|
|
||||||
|
|
||||||
class Dispatcher(DataMixin, ContextInstanceMixin):
|
class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
|
|
@ -33,13 +50,21 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
inline queries, chosen inline results, callback queries, shipping queries, pre-checkout queries.
|
inline queries, chosen inline results, callback queries, shipping queries, pre-checkout queries.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, bot, loop=None, storage: typing.Optional[BaseStorage] = None,
|
def __init__(
|
||||||
run_tasks_by_default: bool = False,
|
self,
|
||||||
throttling_rate_limit=DEFAULT_RATE_LIMIT, no_throttle_error=False,
|
bot,
|
||||||
filters_factory=None):
|
loop=None,
|
||||||
|
storage: typing.Optional[BaseStorage] = None,
|
||||||
|
run_tasks_by_default: bool = False,
|
||||||
|
throttling_rate_limit=DEFAULT_RATE_LIMIT,
|
||||||
|
no_throttle_error=False,
|
||||||
|
filters_factory=None,
|
||||||
|
):
|
||||||
|
|
||||||
if not isinstance(bot, Bot):
|
if not isinstance(bot, Bot):
|
||||||
raise TypeError(f"Argument 'bot' must be an instance of Bot, not '{type(bot).__name__}'")
|
raise TypeError(
|
||||||
|
f"Argument 'bot' must be an instance of Bot, not '{type(bot).__name__}'"
|
||||||
|
)
|
||||||
|
|
||||||
if loop is None:
|
if loop is None:
|
||||||
loop = bot.loop
|
loop = bot.loop
|
||||||
|
|
@ -57,18 +82,18 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
self.no_throttle_error = no_throttle_error
|
self.no_throttle_error = no_throttle_error
|
||||||
|
|
||||||
self.filters_factory: FiltersFactory = filters_factory
|
self.filters_factory: FiltersFactory = filters_factory
|
||||||
self.updates_handler = Handler(self, middleware_key='update')
|
self.updates_handler = Handler(self, middleware_key="update")
|
||||||
self.message_handlers = Handler(self, middleware_key='message')
|
self.message_handlers = Handler(self, middleware_key="message")
|
||||||
self.edited_message_handlers = Handler(self, middleware_key='edited_message')
|
self.edited_message_handlers = Handler(self, middleware_key="edited_message")
|
||||||
self.channel_post_handlers = Handler(self, middleware_key='channel_post')
|
self.channel_post_handlers = Handler(self, middleware_key="channel_post")
|
||||||
self.edited_channel_post_handlers = Handler(self, middleware_key='edited_channel_post')
|
self.edited_channel_post_handlers = Handler(self, middleware_key="edited_channel_post")
|
||||||
self.inline_query_handlers = Handler(self, middleware_key='inline_query')
|
self.inline_query_handlers = Handler(self, middleware_key="inline_query")
|
||||||
self.chosen_inline_result_handlers = Handler(self, middleware_key='chosen_inline_result')
|
self.chosen_inline_result_handlers = Handler(self, middleware_key="chosen_inline_result")
|
||||||
self.callback_query_handlers = Handler(self, middleware_key='callback_query')
|
self.callback_query_handlers = Handler(self, middleware_key="callback_query")
|
||||||
self.shipping_query_handlers = Handler(self, middleware_key='shipping_query')
|
self.shipping_query_handlers = Handler(self, middleware_key="shipping_query")
|
||||||
self.pre_checkout_query_handlers = Handler(self, middleware_key='pre_checkout_query')
|
self.pre_checkout_query_handlers = Handler(self, middleware_key="pre_checkout_query")
|
||||||
self.poll_handlers = Handler(self, middleware_key='poll')
|
self.poll_handlers = Handler(self, middleware_key="poll")
|
||||||
self.errors_handlers = Handler(self, once=False, middleware_key='error')
|
self.errors_handlers = Handler(self, once=False, middleware_key="error")
|
||||||
|
|
||||||
self.middleware = MiddlewareManager(self)
|
self.middleware = MiddlewareManager(self)
|
||||||
|
|
||||||
|
|
@ -83,37 +108,57 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
def _setup_filters(self):
|
def _setup_filters(self):
|
||||||
filters_factory = self.filters_factory
|
filters_factory = self.filters_factory
|
||||||
|
|
||||||
filters_factory.bind(StateFilter, exclude_event_handlers=[
|
filters_factory.bind(
|
||||||
self.errors_handlers,
|
StateFilter, exclude_event_handlers=[self.errors_handlers, self.poll_handlers]
|
||||||
self.poll_handlers
|
)
|
||||||
])
|
filters_factory.bind(
|
||||||
filters_factory.bind(ContentTypeFilter, event_handlers=[
|
ContentTypeFilter,
|
||||||
self.message_handlers, self.edited_message_handlers,
|
event_handlers=[
|
||||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
self.message_handlers,
|
||||||
]),
|
self.edited_message_handlers,
|
||||||
filters_factory.bind(Command, event_handlers=[
|
self.channel_post_handlers,
|
||||||
self.message_handlers, self.edited_message_handlers
|
self.edited_channel_post_handlers,
|
||||||
])
|
],
|
||||||
filters_factory.bind(Text, event_handlers=[
|
),
|
||||||
self.message_handlers, self.edited_message_handlers,
|
filters_factory.bind(
|
||||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
Command, event_handlers=[self.message_handlers, self.edited_message_handlers]
|
||||||
self.callback_query_handlers, self.poll_handlers
|
)
|
||||||
])
|
filters_factory.bind(
|
||||||
filters_factory.bind(HashTag, event_handlers=[
|
Text,
|
||||||
self.message_handlers, self.edited_message_handlers,
|
event_handlers=[
|
||||||
self.channel_post_handlers, self.edited_channel_post_handlers
|
self.message_handlers,
|
||||||
])
|
self.edited_message_handlers,
|
||||||
filters_factory.bind(Regexp, event_handlers=[
|
self.channel_post_handlers,
|
||||||
self.message_handlers, self.edited_message_handlers,
|
self.edited_channel_post_handlers,
|
||||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
self.callback_query_handlers,
|
||||||
self.callback_query_handlers, self.poll_handlers
|
self.poll_handlers,
|
||||||
])
|
],
|
||||||
filters_factory.bind(RegexpCommandsFilter, event_handlers=[
|
)
|
||||||
self.message_handlers, self.edited_message_handlers
|
filters_factory.bind(
|
||||||
])
|
HashTag,
|
||||||
filters_factory.bind(ExceptionsFilter, event_handlers=[
|
event_handlers=[
|
||||||
self.errors_handlers
|
self.message_handlers,
|
||||||
])
|
self.edited_message_handlers,
|
||||||
|
self.channel_post_handlers,
|
||||||
|
self.edited_channel_post_handlers,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
filters_factory.bind(
|
||||||
|
Regexp,
|
||||||
|
event_handlers=[
|
||||||
|
self.message_handlers,
|
||||||
|
self.edited_message_handlers,
|
||||||
|
self.channel_post_handlers,
|
||||||
|
self.edited_channel_post_handlers,
|
||||||
|
self.callback_query_handlers,
|
||||||
|
self.poll_handlers,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
filters_factory.bind(
|
||||||
|
RegexpCommandsFilter,
|
||||||
|
event_handlers=[self.message_handlers, self.edited_message_handlers],
|
||||||
|
)
|
||||||
|
filters_factory.bind(ExceptionsFilter, event_handlers=[self.errors_handlers])
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.stop_polling()
|
self.stop_polling()
|
||||||
|
|
@ -209,13 +254,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
|
|
||||||
return await self.bot.delete_webhook()
|
return await self.bot.delete_webhook()
|
||||||
|
|
||||||
async def start_polling(self,
|
async def start_polling(
|
||||||
timeout=20,
|
self,
|
||||||
relax=0.1,
|
timeout=20,
|
||||||
limit=None,
|
relax=0.1,
|
||||||
reset_webhook=None,
|
limit=None,
|
||||||
fast: typing.Optional[bool] = True,
|
reset_webhook=None,
|
||||||
error_sleep: int = 5):
|
fast: typing.Optional[bool] = True,
|
||||||
|
error_sleep: int = 5,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Start long-polling
|
Start long-polling
|
||||||
|
|
||||||
|
|
@ -227,9 +274,9 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if self._polling:
|
if self._polling:
|
||||||
raise RuntimeError('Polling already started')
|
raise RuntimeError("Polling already started")
|
||||||
|
|
||||||
log.info('Start polling.')
|
log.info("Start polling.")
|
||||||
|
|
||||||
# context.set_value(MODE, LONG_POLLING)
|
# context.set_value(MODE, LONG_POLLING)
|
||||||
Dispatcher.set_current(self)
|
Dispatcher.set_current(self)
|
||||||
|
|
@ -245,16 +292,20 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
try:
|
try:
|
||||||
current_request_timeout = self.bot.timeout
|
current_request_timeout = self.bot.timeout
|
||||||
if current_request_timeout is not sentinel and timeout is not None:
|
if current_request_timeout is not sentinel and timeout is not None:
|
||||||
request_timeout = aiohttp.ClientTimeout(total=current_request_timeout.total + timeout or 1)
|
request_timeout = aiohttp.ClientTimeout(
|
||||||
|
total=current_request_timeout.total + timeout or 1
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
request_timeout = None
|
request_timeout = None
|
||||||
|
|
||||||
while self._polling:
|
while self._polling:
|
||||||
try:
|
try:
|
||||||
with self.bot.request_timeout(request_timeout):
|
with self.bot.request_timeout(request_timeout):
|
||||||
updates = await self.bot.get_updates(limit=limit, offset=offset, timeout=timeout)
|
updates = await self.bot.get_updates(
|
||||||
|
limit=limit, offset=offset, timeout=timeout
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
log.exception('Cause exception while getting updates.')
|
log.exception("Cause exception while getting updates.")
|
||||||
await asyncio.sleep(error_sleep)
|
await asyncio.sleep(error_sleep)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -269,7 +320,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._close_waiter._set_result(None)
|
self._close_waiter._set_result(None)
|
||||||
log.warning('Polling is stopped.')
|
log.warning("Polling is stopped.")
|
||||||
|
|
||||||
async def _process_polling_updates(self, updates, fast: typing.Optional[bool] = True):
|
async def _process_polling_updates(self, updates, fast: typing.Optional[bool] = True):
|
||||||
"""
|
"""
|
||||||
|
|
@ -288,7 +339,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
try:
|
try:
|
||||||
asyncio.gather(*need_to_call)
|
asyncio.gather(*need_to_call)
|
||||||
except TelegramAPIError:
|
except TelegramAPIError:
|
||||||
log.exception('Cause exception while processing updates.')
|
log.exception("Cause exception while processing updates.")
|
||||||
|
|
||||||
def stop_polling(self):
|
def stop_polling(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -296,8 +347,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if hasattr(self, '_polling') and self._polling:
|
if hasattr(self, "_polling") and self._polling:
|
||||||
log.info('Stop polling...')
|
log.info("Stop polling...")
|
||||||
self._polling = False
|
self._polling = False
|
||||||
|
|
||||||
async def wait_closed(self):
|
async def wait_closed(self):
|
||||||
|
|
@ -316,8 +367,17 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
return self._polling
|
return self._polling
|
||||||
|
|
||||||
def register_message_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None,
|
def register_message_handler(
|
||||||
state=None, run_task=None, **kwargs):
|
self,
|
||||||
|
callback,
|
||||||
|
*custom_filters,
|
||||||
|
commands=None,
|
||||||
|
regexp=None,
|
||||||
|
content_types=None,
|
||||||
|
state=None,
|
||||||
|
run_task=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register handler for message
|
Register handler for message
|
||||||
|
|
||||||
|
|
@ -343,17 +403,27 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:param state:
|
:param state:
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
filters_set = self.filters_factory.resolve(self.message_handlers,
|
filters_set = self.filters_factory.resolve(
|
||||||
*custom_filters,
|
self.message_handlers,
|
||||||
commands=commands,
|
*custom_filters,
|
||||||
regexp=regexp,
|
commands=commands,
|
||||||
content_types=content_types,
|
regexp=regexp,
|
||||||
state=state,
|
content_types=content_types,
|
||||||
**kwargs)
|
state=state,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
self.message_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
self.message_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||||
|
|
||||||
def message_handler(self, *custom_filters, commands=None, regexp=None, content_types=None, state=None,
|
def message_handler(
|
||||||
run_task=None, **kwargs):
|
self,
|
||||||
|
*custom_filters,
|
||||||
|
commands=None,
|
||||||
|
regexp=None,
|
||||||
|
content_types=None,
|
||||||
|
state=None,
|
||||||
|
run_task=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Decorator for message handler
|
Decorator for message handler
|
||||||
|
|
||||||
|
|
@ -424,15 +494,31 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_message_handler(callback, *custom_filters,
|
self.register_message_handler(
|
||||||
commands=commands, regexp=regexp, content_types=content_types,
|
callback,
|
||||||
state=state, run_task=run_task, **kwargs)
|
*custom_filters,
|
||||||
|
commands=commands,
|
||||||
|
regexp=regexp,
|
||||||
|
content_types=content_types,
|
||||||
|
state=state,
|
||||||
|
run_task=run_task,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def register_edited_message_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None,
|
def register_edited_message_handler(
|
||||||
state=None, run_task=None, **kwargs):
|
self,
|
||||||
|
callback,
|
||||||
|
*custom_filters,
|
||||||
|
commands=None,
|
||||||
|
regexp=None,
|
||||||
|
content_types=None,
|
||||||
|
state=None,
|
||||||
|
run_task=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register handler for edited message
|
Register handler for edited message
|
||||||
|
|
||||||
|
|
@ -446,17 +532,29 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
filters_set = self.filters_factory.resolve(self.edited_message_handlers,
|
filters_set = self.filters_factory.resolve(
|
||||||
*custom_filters,
|
self.edited_message_handlers,
|
||||||
commands=commands,
|
*custom_filters,
|
||||||
regexp=regexp,
|
commands=commands,
|
||||||
content_types=content_types,
|
regexp=regexp,
|
||||||
state=state,
|
content_types=content_types,
|
||||||
**kwargs)
|
state=state,
|
||||||
self.edited_message_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
**kwargs,
|
||||||
|
)
|
||||||
|
self.edited_message_handlers.register(
|
||||||
|
self._wrap_async_task(callback, run_task), filters_set
|
||||||
|
)
|
||||||
|
|
||||||
def edited_message_handler(self, *custom_filters, commands=None, regexp=None, content_types=None,
|
def edited_message_handler(
|
||||||
state=None, run_task=None, **kwargs):
|
self,
|
||||||
|
*custom_filters,
|
||||||
|
commands=None,
|
||||||
|
regexp=None,
|
||||||
|
content_types=None,
|
||||||
|
state=None,
|
||||||
|
run_task=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Decorator for edited message handler
|
Decorator for edited message handler
|
||||||
|
|
||||||
|
|
@ -479,14 +577,31 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_edited_message_handler(callback, *custom_filters, commands=commands, regexp=regexp,
|
self.register_edited_message_handler(
|
||||||
content_types=content_types, state=state, run_task=run_task, **kwargs)
|
callback,
|
||||||
|
*custom_filters,
|
||||||
|
commands=commands,
|
||||||
|
regexp=regexp,
|
||||||
|
content_types=content_types,
|
||||||
|
state=state,
|
||||||
|
run_task=run_task,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def register_channel_post_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None,
|
def register_channel_post_handler(
|
||||||
state=None, run_task=None, **kwargs):
|
self,
|
||||||
|
callback,
|
||||||
|
*custom_filters,
|
||||||
|
commands=None,
|
||||||
|
regexp=None,
|
||||||
|
content_types=None,
|
||||||
|
state=None,
|
||||||
|
run_task=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register handler for channel post
|
Register handler for channel post
|
||||||
|
|
||||||
|
|
@ -500,17 +615,27 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
filters_set = self.filters_factory.resolve(self.channel_post_handlers,
|
filters_set = self.filters_factory.resolve(
|
||||||
*custom_filters,
|
self.channel_post_handlers,
|
||||||
commands=commands,
|
*custom_filters,
|
||||||
regexp=regexp,
|
commands=commands,
|
||||||
content_types=content_types,
|
regexp=regexp,
|
||||||
state=state,
|
content_types=content_types,
|
||||||
**kwargs)
|
state=state,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
self.channel_post_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
self.channel_post_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||||
|
|
||||||
def channel_post_handler(self, *custom_filters, commands=None, regexp=None, content_types=None,
|
def channel_post_handler(
|
||||||
state=None, run_task=None, **kwargs):
|
self,
|
||||||
|
*custom_filters,
|
||||||
|
commands=None,
|
||||||
|
regexp=None,
|
||||||
|
content_types=None,
|
||||||
|
state=None,
|
||||||
|
run_task=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Decorator for channel post handler
|
Decorator for channel post handler
|
||||||
|
|
||||||
|
|
@ -525,14 +650,31 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_channel_post_handler(callback, *custom_filters, commands=commands, regexp=regexp,
|
self.register_channel_post_handler(
|
||||||
content_types=content_types, state=state, run_task=run_task, **kwargs)
|
callback,
|
||||||
|
*custom_filters,
|
||||||
|
commands=commands,
|
||||||
|
regexp=regexp,
|
||||||
|
content_types=content_types,
|
||||||
|
state=state,
|
||||||
|
run_task=run_task,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def register_edited_channel_post_handler(self, callback, *custom_filters, commands=None, regexp=None,
|
def register_edited_channel_post_handler(
|
||||||
content_types=None, state=None, run_task=None, **kwargs):
|
self,
|
||||||
|
callback,
|
||||||
|
*custom_filters,
|
||||||
|
commands=None,
|
||||||
|
regexp=None,
|
||||||
|
content_types=None,
|
||||||
|
state=None,
|
||||||
|
run_task=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register handler for edited channel post
|
Register handler for edited channel post
|
||||||
|
|
||||||
|
|
@ -546,17 +688,29 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
filters_set = self.filters_factory.resolve(self.edited_message_handlers,
|
filters_set = self.filters_factory.resolve(
|
||||||
*custom_filters,
|
self.edited_message_handlers,
|
||||||
commands=commands,
|
*custom_filters,
|
||||||
regexp=regexp,
|
commands=commands,
|
||||||
content_types=content_types,
|
regexp=regexp,
|
||||||
state=state,
|
content_types=content_types,
|
||||||
**kwargs)
|
state=state,
|
||||||
self.edited_channel_post_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
**kwargs,
|
||||||
|
)
|
||||||
|
self.edited_channel_post_handlers.register(
|
||||||
|
self._wrap_async_task(callback, run_task), filters_set
|
||||||
|
)
|
||||||
|
|
||||||
def edited_channel_post_handler(self, *custom_filters, commands=None, regexp=None, content_types=None,
|
def edited_channel_post_handler(
|
||||||
state=None, run_task=None, **kwargs):
|
self,
|
||||||
|
*custom_filters,
|
||||||
|
commands=None,
|
||||||
|
regexp=None,
|
||||||
|
content_types=None,
|
||||||
|
state=None,
|
||||||
|
run_task=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Decorator for edited channel post handler
|
Decorator for edited channel post handler
|
||||||
|
|
||||||
|
|
@ -571,14 +725,23 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_edited_channel_post_handler(callback, *custom_filters, commands=commands, regexp=regexp,
|
self.register_edited_channel_post_handler(
|
||||||
content_types=content_types, state=state, run_task=run_task,
|
callback,
|
||||||
**kwargs)
|
*custom_filters,
|
||||||
|
commands=commands,
|
||||||
|
regexp=regexp,
|
||||||
|
content_types=content_types,
|
||||||
|
state=state,
|
||||||
|
run_task=run_task,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def register_inline_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs):
|
def register_inline_handler(
|
||||||
|
self, callback, *custom_filters, state=None, run_task=None, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register handler for inline query
|
Register handler for inline query
|
||||||
|
|
||||||
|
|
@ -597,10 +760,9 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
if custom_filters is None:
|
if custom_filters is None:
|
||||||
custom_filters = []
|
custom_filters = []
|
||||||
filters_set = self.filters_factory.resolve(self.inline_query_handlers,
|
filters_set = self.filters_factory.resolve(
|
||||||
*custom_filters,
|
self.inline_query_handlers, *custom_filters, state=state, **kwargs
|
||||||
state=state,
|
)
|
||||||
**kwargs)
|
|
||||||
self.inline_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
self.inline_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||||
|
|
||||||
def inline_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
def inline_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
||||||
|
|
@ -622,12 +784,16 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_inline_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs)
|
self.register_inline_handler(
|
||||||
|
callback, *custom_filters, state=state, run_task=run_task, **kwargs
|
||||||
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def register_chosen_inline_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs):
|
def register_chosen_inline_handler(
|
||||||
|
self, callback, *custom_filters, state=None, run_task=None, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register handler for chosen inline query
|
Register handler for chosen inline query
|
||||||
|
|
||||||
|
|
@ -646,11 +812,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
if custom_filters is None:
|
if custom_filters is None:
|
||||||
custom_filters = []
|
custom_filters = []
|
||||||
filters_set = self.filters_factory.resolve(self.chosen_inline_result_handlers,
|
filters_set = self.filters_factory.resolve(
|
||||||
*custom_filters,
|
self.chosen_inline_result_handlers, *custom_filters, state=state, **kwargs
|
||||||
state=state,
|
)
|
||||||
**kwargs)
|
self.chosen_inline_result_handlers.register(
|
||||||
self.chosen_inline_result_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
self._wrap_async_task(callback, run_task), filters_set
|
||||||
|
)
|
||||||
|
|
||||||
def chosen_inline_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
def chosen_inline_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -671,12 +838,16 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_chosen_inline_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs)
|
self.register_chosen_inline_handler(
|
||||||
|
callback, *custom_filters, state=state, run_task=run_task, **kwargs
|
||||||
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def register_callback_query_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs):
|
def register_callback_query_handler(
|
||||||
|
self, callback, *custom_filters, state=None, run_task=None, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register handler for callback query
|
Register handler for callback query
|
||||||
|
|
||||||
|
|
@ -692,11 +863,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:param run_task: run callback in task (no wait results)
|
:param run_task: run callback in task (no wait results)
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
"""
|
"""
|
||||||
filters_set = self.filters_factory.resolve(self.callback_query_handlers,
|
filters_set = self.filters_factory.resolve(
|
||||||
*custom_filters,
|
self.callback_query_handlers, *custom_filters, state=state, **kwargs
|
||||||
state=state,
|
)
|
||||||
**kwargs)
|
self.callback_query_handlers.register(
|
||||||
self.callback_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
self._wrap_async_task(callback, run_task), filters_set
|
||||||
|
)
|
||||||
|
|
||||||
def callback_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
def callback_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -716,13 +888,16 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_callback_query_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs)
|
self.register_callback_query_handler(
|
||||||
|
callback, *custom_filters, state=state, run_task=run_task, **kwargs
|
||||||
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def register_shipping_query_handler(self, callback, *custom_filters, state=None, run_task=None,
|
def register_shipping_query_handler(
|
||||||
**kwargs):
|
self, callback, *custom_filters, state=None, run_task=None, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register handler for shipping query
|
Register handler for shipping query
|
||||||
|
|
||||||
|
|
@ -738,11 +913,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:param run_task: run callback in task (no wait results)
|
:param run_task: run callback in task (no wait results)
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
"""
|
"""
|
||||||
filters_set = self.filters_factory.resolve(self.shipping_query_handlers,
|
filters_set = self.filters_factory.resolve(
|
||||||
*custom_filters,
|
self.shipping_query_handlers, *custom_filters, state=state, **kwargs
|
||||||
state=state,
|
)
|
||||||
**kwargs)
|
self.shipping_query_handlers.register(
|
||||||
self.shipping_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
self._wrap_async_task(callback, run_task), filters_set
|
||||||
|
)
|
||||||
|
|
||||||
def shipping_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
def shipping_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -762,12 +938,16 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_shipping_query_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs)
|
self.register_shipping_query_handler(
|
||||||
|
callback, *custom_filters, state=state, run_task=run_task, **kwargs
|
||||||
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def register_pre_checkout_query_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs):
|
def register_pre_checkout_query_handler(
|
||||||
|
self, callback, *custom_filters, state=None, run_task=None, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register handler for pre-checkout query
|
Register handler for pre-checkout query
|
||||||
|
|
||||||
|
|
@ -783,11 +963,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:param run_task: run callback in task (no wait results)
|
:param run_task: run callback in task (no wait results)
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
"""
|
"""
|
||||||
filters_set = self.filters_factory.resolve(self.pre_checkout_query_handlers,
|
filters_set = self.filters_factory.resolve(
|
||||||
*custom_filters,
|
self.pre_checkout_query_handlers, *custom_filters, state=state, **kwargs
|
||||||
state=state,
|
)
|
||||||
**kwargs)
|
self.pre_checkout_query_handlers.register(
|
||||||
self.pre_checkout_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
self._wrap_async_task(callback, run_task), filters_set
|
||||||
|
)
|
||||||
|
|
||||||
def pre_checkout_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
def pre_checkout_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -807,27 +988,27 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_pre_checkout_query_handler(callback, *custom_filters, state=state, run_task=run_task,
|
self.register_pre_checkout_query_handler(
|
||||||
**kwargs)
|
callback, *custom_filters, state=state, run_task=run_task, **kwargs
|
||||||
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def register_poll_handler(self, callback, *custom_filters, run_task=None, **kwargs):
|
def register_poll_handler(self, callback, *custom_filters, run_task=None, **kwargs):
|
||||||
filters_set = self.filters_factory.resolve(self.poll_handlers,
|
filters_set = self.filters_factory.resolve(self.poll_handlers, *custom_filters, **kwargs)
|
||||||
*custom_filters,
|
|
||||||
**kwargs)
|
|
||||||
self.poll_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
self.poll_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||||
|
|
||||||
def poll_handler(self, *custom_filters, run_task=None, **kwargs):
|
def poll_handler(self, *custom_filters, run_task=None, **kwargs):
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_poll_handler(callback, *custom_filters, run_task=run_task,
|
self.register_poll_handler(callback, *custom_filters, run_task=run_task, **kwargs)
|
||||||
**kwargs)
|
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def register_errors_handler(self, callback, *custom_filters, exception=None, run_task=None, **kwargs):
|
def register_errors_handler(
|
||||||
|
self, callback, *custom_filters, exception=None, run_task=None, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register handler for errors
|
Register handler for errors
|
||||||
|
|
||||||
|
|
@ -835,10 +1016,9 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:param exception: you can make handler for specific errors type
|
:param exception: you can make handler for specific errors type
|
||||||
:param run_task: run callback in task (no wait results)
|
:param run_task: run callback in task (no wait results)
|
||||||
"""
|
"""
|
||||||
filters_set = self.filters_factory.resolve(self.errors_handlers,
|
filters_set = self.filters_factory.resolve(
|
||||||
*custom_filters,
|
self.errors_handlers, *custom_filters, exception=exception, **kwargs
|
||||||
exception=exception,
|
)
|
||||||
**kwargs)
|
|
||||||
self.errors_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
self.errors_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||||
|
|
||||||
def errors_handler(self, *custom_filters, exception=None, run_task=None, **kwargs):
|
def errors_handler(self, *custom_filters, exception=None, run_task=None, **kwargs):
|
||||||
|
|
@ -851,15 +1031,22 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(callback):
|
def decorator(callback):
|
||||||
self.register_errors_handler(self._wrap_async_task(callback, run_task),
|
self.register_errors_handler(
|
||||||
*custom_filters, exception=exception, **kwargs)
|
self._wrap_async_task(callback, run_task),
|
||||||
|
*custom_filters,
|
||||||
|
exception=exception,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def current_state(self, *,
|
def current_state(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None) -> FSMContext:
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
) -> FSMContext:
|
||||||
"""
|
"""
|
||||||
Get current state for user in chat as context
|
Get current state for user in chat as context
|
||||||
|
|
||||||
|
|
@ -897,7 +1084,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:return: bool
|
:return: bool
|
||||||
"""
|
"""
|
||||||
if not self.storage.has_bucket():
|
if not self.storage.has_bucket():
|
||||||
raise RuntimeError('This storage does not provide Leaky Bucket')
|
raise RuntimeError("This storage does not provide Leaky Bucket")
|
||||||
|
|
||||||
if no_error is None:
|
if no_error is None:
|
||||||
no_error = self.no_throttle_error
|
no_error = self.no_throttle_error
|
||||||
|
|
@ -951,7 +1138,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if not self.storage.has_bucket():
|
if not self.storage.has_bucket():
|
||||||
raise RuntimeError('This storage does not provide Leaky Bucket')
|
raise RuntimeError("This storage does not provide Leaky Bucket")
|
||||||
|
|
||||||
if user is None and chat is None:
|
if user is None and chat is None:
|
||||||
user = types.User.get_current()
|
user = types.User.get_current()
|
||||||
|
|
@ -971,7 +1158,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if not self.storage.has_bucket():
|
if not self.storage.has_bucket():
|
||||||
raise RuntimeError('This storage does not provide Leaky Bucket')
|
raise RuntimeError("This storage does not provide Leaky Bucket")
|
||||||
|
|
||||||
if user is None and chat is None:
|
if user is None and chat is None:
|
||||||
user = types.User.get_current()
|
user = types.User.get_current()
|
||||||
|
|
@ -979,7 +1166,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
|
|
||||||
bucket = await self.storage.get_bucket(chat=chat, user=user)
|
bucket = await self.storage.get_bucket(chat=chat, user=user)
|
||||||
if bucket and key in bucket:
|
if bucket and key in bucket:
|
||||||
del bucket['key']
|
del bucket["key"]
|
||||||
await self.storage.set_bucket(chat=chat, user=user, bucket=bucket)
|
await self.storage.set_bucket(chat=chat, user=user, bucket=bucket)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
@ -1005,8 +1192,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
try:
|
try:
|
||||||
response = task.result()
|
response = task.result()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.loop.create_task(
|
self.loop.create_task(self.errors_handlers.notify(types.Update.get_current(), e))
|
||||||
self.errors_handlers.notify(types.Update.get_current(), e))
|
|
||||||
else:
|
else:
|
||||||
if isinstance(response, BaseResponse):
|
if isinstance(response, BaseResponse):
|
||||||
self.loop.create_task(response.execute_response(self.bot))
|
self.loop.create_task(response.execute_response(self.bot))
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,51 @@
|
||||||
from .builtin import Command, CommandHelp, CommandPrivacy, CommandSettings, CommandStart, ContentTypeFilter, \
|
from .builtin import (
|
||||||
ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, Text
|
Command,
|
||||||
|
CommandHelp,
|
||||||
|
CommandPrivacy,
|
||||||
|
CommandSettings,
|
||||||
|
CommandStart,
|
||||||
|
ContentTypeFilter,
|
||||||
|
ExceptionsFilter,
|
||||||
|
HashTag,
|
||||||
|
Regexp,
|
||||||
|
RegexpCommandsFilter,
|
||||||
|
StateFilter,
|
||||||
|
Text,
|
||||||
|
)
|
||||||
from .factory import FiltersFactory
|
from .factory import FiltersFactory
|
||||||
from .filters import AbstractFilter, BoundFilter, Filter, FilterNotPassed, FilterRecord, execute_filter, \
|
from .filters import (
|
||||||
check_filters, get_filter_spec, get_filters_spec
|
AbstractFilter,
|
||||||
|
BoundFilter,
|
||||||
|
Filter,
|
||||||
|
FilterNotPassed,
|
||||||
|
FilterRecord,
|
||||||
|
execute_filter,
|
||||||
|
check_filters,
|
||||||
|
get_filter_spec,
|
||||||
|
get_filters_spec,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AbstractFilter',
|
"AbstractFilter",
|
||||||
'BoundFilter',
|
"BoundFilter",
|
||||||
'Command',
|
"Command",
|
||||||
'CommandStart',
|
"CommandStart",
|
||||||
'CommandHelp',
|
"CommandHelp",
|
||||||
'CommandPrivacy',
|
"CommandPrivacy",
|
||||||
'CommandSettings',
|
"CommandSettings",
|
||||||
'ContentTypeFilter',
|
"ContentTypeFilter",
|
||||||
'ExceptionsFilter',
|
"ExceptionsFilter",
|
||||||
'HashTag',
|
"HashTag",
|
||||||
'Filter',
|
"Filter",
|
||||||
'FilterNotPassed',
|
"FilterNotPassed",
|
||||||
'FilterRecord',
|
"FilterRecord",
|
||||||
'FiltersFactory',
|
"FiltersFactory",
|
||||||
'RegexpCommandsFilter',
|
"RegexpCommandsFilter",
|
||||||
'Regexp',
|
"Regexp",
|
||||||
'StateFilter',
|
"StateFilter",
|
||||||
'Text',
|
"Text",
|
||||||
'get_filter_spec',
|
"get_filter_spec",
|
||||||
'get_filters_spec',
|
"get_filters_spec",
|
||||||
'execute_filter',
|
"execute_filter",
|
||||||
'check_filters'
|
"check_filters",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,13 @@ class Command(Filter):
|
||||||
By default this filter is registered for messages and edited messages handlers.
|
By default this filter is registered for messages and edited messages handlers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, commands: Union[Iterable, str],
|
def __init__(
|
||||||
prefixes: Union[Iterable, str] = '/',
|
self,
|
||||||
ignore_case: bool = True,
|
commands: Union[Iterable, str],
|
||||||
ignore_mention: bool = False):
|
prefixes: Union[Iterable, str] = "/",
|
||||||
|
ignore_case: bool = True,
|
||||||
|
ignore_mention: bool = False,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Filter can be initialized from filters factory or by simply creating instance of this class.
|
Filter can be initialized from filters factory or by simply creating instance of this class.
|
||||||
|
|
||||||
|
|
@ -66,30 +69,38 @@ class Command(Filter):
|
||||||
:return: config or empty dict
|
:return: config or empty dict
|
||||||
"""
|
"""
|
||||||
config = {}
|
config = {}
|
||||||
if 'commands' in full_config:
|
if "commands" in full_config:
|
||||||
config['commands'] = full_config.pop('commands')
|
config["commands"] = full_config.pop("commands")
|
||||||
if config and 'commands_prefix' in full_config:
|
if config and "commands_prefix" in full_config:
|
||||||
config['prefixes'] = full_config.pop('commands_prefix')
|
config["prefixes"] = full_config.pop("commands_prefix")
|
||||||
if config and 'commands_ignore_mention' in full_config:
|
if config and "commands_ignore_mention" in full_config:
|
||||||
config['ignore_mention'] = full_config.pop('commands_ignore_mention')
|
config["ignore_mention"] = full_config.pop("commands_ignore_mention")
|
||||||
return config
|
return config
|
||||||
|
|
||||||
async def check(self, message: types.Message):
|
async def check(self, message: types.Message):
|
||||||
return await self.check_command(message, self.commands, self.prefixes, self.ignore_case, self.ignore_mention)
|
return await self.check_command(
|
||||||
|
message, self.commands, self.prefixes, self.ignore_case, self.ignore_mention
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def check_command(message: types.Message, commands, prefixes, ignore_case=True, ignore_mention=False):
|
async def check_command(
|
||||||
|
message: types.Message, commands, prefixes, ignore_case=True, ignore_mention=False
|
||||||
|
):
|
||||||
full_command = message.text.split()[0]
|
full_command = message.text.split()[0]
|
||||||
prefix, (command, _, mention) = full_command[0], full_command[1:].partition('@')
|
prefix, (command, _, mention) = (full_command[0], full_command[1:].partition("@"))
|
||||||
|
|
||||||
if not ignore_mention and mention and (await message.bot.me).username.lower() != mention.lower():
|
if (
|
||||||
|
not ignore_mention
|
||||||
|
and mention
|
||||||
|
and (await message.bot.me).username.lower() != mention.lower()
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
elif prefix not in prefixes:
|
elif prefix not in prefixes:
|
||||||
return False
|
return False
|
||||||
elif (command.lower() if ignore_case else command) not in commands:
|
elif (command.lower() if ignore_case else command) not in commands:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return {'command': Command.CommandObj(command=command, prefix=prefix, mention=mention)}
|
return {"command": Command.CommandObj(command=command, prefix=prefix, mention=mention)}
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CommandObj:
|
class CommandObj:
|
||||||
|
|
@ -100,9 +111,9 @@ class Command(Filter):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""Command prefix"""
|
"""Command prefix"""
|
||||||
prefix: str = '/'
|
prefix: str = "/"
|
||||||
"""Command without prefix and mention"""
|
"""Command without prefix and mention"""
|
||||||
command: str = ''
|
command: str = ""
|
||||||
"""Mention (if available)"""
|
"""Mention (if available)"""
|
||||||
mention: str = None
|
mention: str = None
|
||||||
"""Command argument"""
|
"""Command argument"""
|
||||||
|
|
@ -126,9 +137,9 @@ class Command(Filter):
|
||||||
"""
|
"""
|
||||||
line = self.prefix + self.command
|
line = self.prefix + self.command
|
||||||
if self.mentioned:
|
if self.mentioned:
|
||||||
line += '@' + self.mention
|
line += "@" + self.mention
|
||||||
if self.args:
|
if self.args:
|
||||||
line += ' ' + self.args
|
line += " " + self.args
|
||||||
return line
|
return line
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -149,7 +160,7 @@ class CommandStart(Command):
|
||||||
|
|
||||||
:param deep_link: string or compiled regular expression (by ``re.compile(...)``).
|
:param deep_link: string or compiled regular expression (by ``re.compile(...)``).
|
||||||
"""
|
"""
|
||||||
super(CommandStart, self).__init__(['start'])
|
super(CommandStart, self).__init__(["start"])
|
||||||
self.deep_link = deep_link
|
self.deep_link = deep_link
|
||||||
|
|
||||||
async def check(self, message: types.Message):
|
async def check(self, message: types.Message):
|
||||||
|
|
@ -167,7 +178,7 @@ class CommandStart(Command):
|
||||||
|
|
||||||
match = self.deep_link.match(message.get_args())
|
match = self.deep_link.match(message.get_args())
|
||||||
if match:
|
if match:
|
||||||
return {'deep_link': match}
|
return {"deep_link": match}
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return check
|
return check
|
||||||
|
|
@ -179,7 +190,7 @@ class CommandHelp(Command):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CommandHelp, self).__init__(['help'])
|
super(CommandHelp, self).__init__(["help"])
|
||||||
|
|
||||||
|
|
||||||
class CommandSettings(Command):
|
class CommandSettings(Command):
|
||||||
|
|
@ -188,7 +199,7 @@ class CommandSettings(Command):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CommandSettings, self).__init__(['settings'])
|
super(CommandSettings, self).__init__(["settings"])
|
||||||
|
|
||||||
|
|
||||||
class CommandPrivacy(Command):
|
class CommandPrivacy(Command):
|
||||||
|
|
@ -197,7 +208,7 @@ class CommandPrivacy(Command):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CommandPrivacy, self).__init__(['privacy'])
|
super(CommandPrivacy, self).__init__(["privacy"])
|
||||||
|
|
||||||
|
|
||||||
class Text(Filter):
|
class Text(Filter):
|
||||||
|
|
@ -205,12 +216,14 @@ class Text(Filter):
|
||||||
Simple text filter
|
Simple text filter
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(
|
||||||
equals: Optional[Union[str, LazyProxy]] = None,
|
self,
|
||||||
contains: Optional[Union[str, LazyProxy]] = None,
|
equals: Optional[Union[str, LazyProxy]] = None,
|
||||||
startswith: Optional[Union[str, LazyProxy]] = None,
|
contains: Optional[Union[str, LazyProxy]] = None,
|
||||||
endswith: Optional[Union[str, LazyProxy]] = None,
|
startswith: Optional[Union[str, LazyProxy]] = None,
|
||||||
ignore_case=False):
|
endswith: Optional[Union[str, LazyProxy]] = None,
|
||||||
|
ignore_case=False,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Check text for one of pattern. Only one mode can be used in one filter.
|
Check text for one of pattern. Only one mode can be used in one filter.
|
||||||
|
|
||||||
|
|
@ -223,11 +236,18 @@ class Text(Filter):
|
||||||
# Only one mode can be used. check it.
|
# Only one mode can be used. check it.
|
||||||
check = sum(map(bool, (equals, contains, startswith, endswith)))
|
check = sum(map(bool, (equals, contains, startswith, endswith)))
|
||||||
if check > 1:
|
if check > 1:
|
||||||
args = "' and '".join([arg[0] for arg in [('equals', equals),
|
args = "' and '".join(
|
||||||
('contains', contains),
|
[
|
||||||
('startswith', startswith),
|
arg[0]
|
||||||
('endswith', endswith)
|
for arg in [
|
||||||
] if arg[1]])
|
("equals", equals),
|
||||||
|
("contains", contains),
|
||||||
|
("startswith", startswith),
|
||||||
|
("endswith", endswith),
|
||||||
|
]
|
||||||
|
if arg[1]
|
||||||
|
]
|
||||||
|
)
|
||||||
raise ValueError(f"Arguments '{args}' cannot be used together.")
|
raise ValueError(f"Arguments '{args}' cannot be used together.")
|
||||||
elif check == 0:
|
elif check == 0:
|
||||||
raise ValueError(f"No one mode is specified!")
|
raise ValueError(f"No one mode is specified!")
|
||||||
|
|
@ -240,18 +260,18 @@ class Text(Filter):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, full_config: Dict[str, Any]):
|
def validate(cls, full_config: Dict[str, Any]):
|
||||||
if 'text' in full_config:
|
if "text" in full_config:
|
||||||
return {'equals': full_config.pop('text')}
|
return {"equals": full_config.pop("text")}
|
||||||
elif 'text_contains' in full_config:
|
elif "text_contains" in full_config:
|
||||||
return {'contains': full_config.pop('text_contains')}
|
return {"contains": full_config.pop("text_contains")}
|
||||||
elif 'text_startswith' in full_config:
|
elif "text_startswith" in full_config:
|
||||||
return {'startswith': full_config.pop('text_startswith')}
|
return {"startswith": full_config.pop("text_startswith")}
|
||||||
elif 'text_endswith' in full_config:
|
elif "text_endswith" in full_config:
|
||||||
return {'endswith': full_config.pop('text_endswith')}
|
return {"endswith": full_config.pop("text_endswith")}
|
||||||
|
|
||||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]):
|
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]):
|
||||||
if isinstance(obj, Message):
|
if isinstance(obj, Message):
|
||||||
text = obj.text or obj.caption or ''
|
text = obj.text or obj.caption or ""
|
||||||
if not text and obj.poll:
|
if not text and obj.poll:
|
||||||
text = obj.poll.question
|
text = obj.poll.question
|
||||||
elif isinstance(obj, CallbackQuery):
|
elif isinstance(obj, CallbackQuery):
|
||||||
|
|
@ -287,7 +307,7 @@ class HashTag(Filter):
|
||||||
|
|
||||||
def __init__(self, hashtags=None, cashtags=None):
|
def __init__(self, hashtags=None, cashtags=None):
|
||||||
if not hashtags and not cashtags:
|
if not hashtags and not cashtags:
|
||||||
raise ValueError('No one hashtag or cashtag is specified!')
|
raise ValueError("No one hashtag or cashtag is specified!")
|
||||||
|
|
||||||
if hashtags is None:
|
if hashtags is None:
|
||||||
hashtags = []
|
hashtags = []
|
||||||
|
|
@ -307,10 +327,10 @@ class HashTag(Filter):
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, full_config: Dict[str, Any]):
|
def validate(cls, full_config: Dict[str, Any]):
|
||||||
config = {}
|
config = {}
|
||||||
if 'hashtags' in full_config:
|
if "hashtags" in full_config:
|
||||||
config['hashtags'] = full_config.pop('hashtags')
|
config["hashtags"] = full_config.pop("hashtags")
|
||||||
if 'cashtags' in full_config:
|
if "cashtags" in full_config:
|
||||||
config['cashtags'] = full_config.pop('cashtags')
|
config["cashtags"] = full_config.pop("cashtags")
|
||||||
return config
|
return config
|
||||||
|
|
||||||
async def check(self, message: types.Message):
|
async def check(self, message: types.Message):
|
||||||
|
|
@ -324,9 +344,13 @@ class HashTag(Filter):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
hashtags, cashtags = self._get_tags(text, entities)
|
hashtags, cashtags = self._get_tags(text, entities)
|
||||||
if self.hashtags and set(hashtags) & set(self.hashtags) \
|
if (
|
||||||
or self.cashtags and set(cashtags) & set(self.cashtags):
|
self.hashtags
|
||||||
return {'hashtags': hashtags, 'cashtags': cashtags}
|
and set(hashtags) & set(self.hashtags)
|
||||||
|
or self.cashtags
|
||||||
|
and set(cashtags) & set(self.cashtags)
|
||||||
|
):
|
||||||
|
return {"hashtags": hashtags, "cashtags": cashtags}
|
||||||
|
|
||||||
def _get_tags(self, text, entities):
|
def _get_tags(self, text, entities):
|
||||||
hashtags = []
|
hashtags = []
|
||||||
|
|
@ -334,11 +358,11 @@ class HashTag(Filter):
|
||||||
|
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
if entity.type == types.MessageEntityType.HASHTAG:
|
if entity.type == types.MessageEntityType.HASHTAG:
|
||||||
value = entity.get_text(text).lstrip('#')
|
value = entity.get_text(text).lstrip("#")
|
||||||
hashtags.append(value)
|
hashtags.append(value)
|
||||||
|
|
||||||
elif entity.type == types.MessageEntityType.CASHTAG:
|
elif entity.type == types.MessageEntityType.CASHTAG:
|
||||||
value = entity.get_text(text).lstrip('$')
|
value = entity.get_text(text).lstrip("$")
|
||||||
cashtags.append(value)
|
cashtags.append(value)
|
||||||
|
|
||||||
return hashtags, cashtags
|
return hashtags, cashtags
|
||||||
|
|
@ -356,12 +380,12 @@ class Regexp(Filter):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, full_config: Dict[str, Any]):
|
def validate(cls, full_config: Dict[str, Any]):
|
||||||
if 'regexp' in full_config:
|
if "regexp" in full_config:
|
||||||
return {'regexp': full_config.pop('regexp')}
|
return {"regexp": full_config.pop("regexp")}
|
||||||
|
|
||||||
async def check(self, obj: Union[Message, CallbackQuery]):
|
async def check(self, obj: Union[Message, CallbackQuery]):
|
||||||
if isinstance(obj, Message):
|
if isinstance(obj, Message):
|
||||||
content = obj.text or obj.caption or ''
|
content = obj.text or obj.caption or ""
|
||||||
if not content and obj.poll:
|
if not content and obj.poll:
|
||||||
content = obj.poll.question
|
content = obj.poll.question
|
||||||
elif isinstance(obj, CallbackQuery) and obj.data:
|
elif isinstance(obj, CallbackQuery) and obj.data:
|
||||||
|
|
@ -372,7 +396,7 @@ class Regexp(Filter):
|
||||||
match = self.regexp.search(content)
|
match = self.regexp.search(content)
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
return {'regexp': match}
|
return {"regexp": match}
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -381,17 +405,19 @@ class RegexpCommandsFilter(BoundFilter):
|
||||||
Check commands by regexp in message
|
Check commands by regexp in message
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = 'regexp_commands'
|
key = "regexp_commands"
|
||||||
|
|
||||||
def __init__(self, regexp_commands):
|
def __init__(self, regexp_commands):
|
||||||
self.regexp_commands = [re.compile(command, flags=re.IGNORECASE | re.MULTILINE) for command in regexp_commands]
|
self.regexp_commands = [
|
||||||
|
re.compile(command, flags=re.IGNORECASE | re.MULTILINE) for command in regexp_commands
|
||||||
|
]
|
||||||
|
|
||||||
async def check(self, message):
|
async def check(self, message):
|
||||||
if not message.is_command():
|
if not message.is_command():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
command = message.text.split()[0][1:]
|
command = message.text.split()[0][1:]
|
||||||
command, _, mention = command.partition('@')
|
command, _, mention = command.partition("@")
|
||||||
|
|
||||||
if mention and mention != (await message.bot.me).username:
|
if mention and mention != (await message.bot.me).username:
|
||||||
return False
|
return False
|
||||||
|
|
@ -399,7 +425,7 @@ class RegexpCommandsFilter(BoundFilter):
|
||||||
for command in self.regexp_commands:
|
for command in self.regexp_commands:
|
||||||
search = command.search(message.text)
|
search = command.search(message.text)
|
||||||
if search:
|
if search:
|
||||||
return {'regexp_command': search}
|
return {"regexp_command": search}
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -408,7 +434,7 @@ class ContentTypeFilter(BoundFilter):
|
||||||
Check message content type
|
Check message content type
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = 'content_types'
|
key = "content_types"
|
||||||
required = True
|
required = True
|
||||||
default = types.ContentTypes.TEXT
|
default = types.ContentTypes.TEXT
|
||||||
|
|
||||||
|
|
@ -416,18 +442,21 @@ class ContentTypeFilter(BoundFilter):
|
||||||
self.content_types = content_types
|
self.content_types = content_types
|
||||||
|
|
||||||
async def check(self, message):
|
async def check(self, message):
|
||||||
return types.ContentType.ANY in self.content_types or \
|
return (
|
||||||
message.content_type in self.content_types
|
types.ContentType.ANY in self.content_types
|
||||||
|
or message.content_type in self.content_types
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StateFilter(BoundFilter):
|
class StateFilter(BoundFilter):
|
||||||
"""
|
"""
|
||||||
Check user state
|
Check user state
|
||||||
"""
|
"""
|
||||||
key = 'state'
|
|
||||||
|
key = "state"
|
||||||
required = True
|
required = True
|
||||||
|
|
||||||
ctx_state = ContextVar('user_state')
|
ctx_state = ContextVar("user_state")
|
||||||
|
|
||||||
def __init__(self, dispatcher, state):
|
def __init__(self, dispatcher, state):
|
||||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||||
|
|
@ -435,7 +464,7 @@ class StateFilter(BoundFilter):
|
||||||
self.dispatcher = dispatcher
|
self.dispatcher = dispatcher
|
||||||
states = []
|
states = []
|
||||||
if not isinstance(state, (list, set, tuple, frozenset)) or state is None:
|
if not isinstance(state, (list, set, tuple, frozenset)) or state is None:
|
||||||
state = [state, ]
|
state = [state]
|
||||||
for item in state:
|
for item in state:
|
||||||
if isinstance(item, State):
|
if isinstance(item, State):
|
||||||
states.append(item.state)
|
states.append(item.state)
|
||||||
|
|
@ -446,11 +475,14 @@ class StateFilter(BoundFilter):
|
||||||
self.states = states
|
self.states = states
|
||||||
|
|
||||||
def get_target(self, obj):
|
def get_target(self, obj):
|
||||||
return getattr(getattr(obj, 'chat', None), 'id', None), getattr(getattr(obj, 'from_user', None), 'id', None)
|
return (
|
||||||
|
getattr(getattr(obj, "chat", None), "id", None),
|
||||||
|
getattr(getattr(obj, "from_user", None), "id", None),
|
||||||
|
)
|
||||||
|
|
||||||
async def check(self, obj):
|
async def check(self, obj):
|
||||||
if '*' in self.states:
|
if "*" in self.states:
|
||||||
return {'state': self.dispatcher.current_state()}
|
return {"state": self.dispatcher.current_state()}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
state = self.ctx_state.get()
|
state = self.ctx_state.get()
|
||||||
|
|
@ -461,11 +493,11 @@ class StateFilter(BoundFilter):
|
||||||
state = await self.dispatcher.storage.get_state(chat=chat, user=user)
|
state = await self.dispatcher.storage.get_state(chat=chat, user=user)
|
||||||
self.ctx_state.set(state)
|
self.ctx_state.set(state)
|
||||||
if state in self.states:
|
if state in self.states:
|
||||||
return {'state': self.dispatcher.current_state(), 'raw_state': state}
|
return {"state": self.dispatcher.current_state(), "raw_state": state}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if state in self.states:
|
if state in self.states:
|
||||||
return {'state': self.dispatcher.current_state(), 'raw_state': state}
|
return {"state": self.dispatcher.current_state(), "raw_state": state}
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -475,7 +507,7 @@ class ExceptionsFilter(BoundFilter):
|
||||||
Filter for exceptions
|
Filter for exceptions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = 'exception'
|
key = "exception"
|
||||||
|
|
||||||
def __init__(self, exception):
|
def __init__(self, exception):
|
||||||
self.exception = exception
|
self.exception = exception
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,13 @@ class FiltersFactory:
|
||||||
self._dispatcher = dispatcher
|
self._dispatcher = dispatcher
|
||||||
self._registered: typing.List[FilterRecord] = []
|
self._registered: typing.List[FilterRecord] = []
|
||||||
|
|
||||||
def bind(self, callback: typing.Union[typing.Callable, AbstractFilter],
|
def bind(
|
||||||
validator: typing.Optional[typing.Callable] = None,
|
self,
|
||||||
event_handlers: typing.Optional[typing.List[Handler]] = None,
|
callback: typing.Union[typing.Callable, AbstractFilter],
|
||||||
exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None):
|
validator: typing.Optional[typing.Callable] = None,
|
||||||
|
event_handlers: typing.Optional[typing.List[Handler]] = None,
|
||||||
|
exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register filter
|
Register filter
|
||||||
|
|
||||||
|
|
@ -38,8 +41,9 @@ class FiltersFactory:
|
||||||
if record.callback == callback:
|
if record.callback == callback:
|
||||||
self._registered.remove(record)
|
self._registered.remove(record)
|
||||||
|
|
||||||
def resolve(self, event_handler, *custom_filters, **full_config
|
def resolve(
|
||||||
) -> typing.List[typing.Union[typing.Callable, AbstractFilter]]:
|
self, event_handler, *custom_filters, **full_config
|
||||||
|
) -> typing.List[typing.Union[typing.Callable, AbstractFilter]]:
|
||||||
"""
|
"""
|
||||||
Resolve filters to filters-set
|
Resolve filters to filters-set
|
||||||
|
|
||||||
|
|
@ -49,8 +53,11 @@ class FiltersFactory:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
filters_set = []
|
filters_set = []
|
||||||
filters_set.extend(self._resolve_registered(event_handler,
|
filters_set.extend(
|
||||||
{k: v for k, v in full_config.items() if v is not None}))
|
self._resolve_registered(
|
||||||
|
event_handler, {k: v for k, v in full_config.items() if v is not None}
|
||||||
|
)
|
||||||
|
)
|
||||||
if custom_filters:
|
if custom_filters:
|
||||||
filters_set.extend(custom_filters)
|
filters_set.extend(custom_filters)
|
||||||
|
|
||||||
|
|
@ -70,4 +77,4 @@ class FiltersFactory:
|
||||||
yield filter_
|
yield filter_
|
||||||
|
|
||||||
if full_config:
|
if full_config:
|
||||||
raise NameError('Invalid filter name(s): \'' + '\', '.join(full_config.keys()) + '\'')
|
raise NameError("Invalid filter name(s): '" + "', ".join(full_config.keys()) + "'")
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,11 @@ def wrap_async(func):
|
||||||
async def async_wrapper(*args, **kwargs):
|
async def async_wrapper(*args, **kwargs):
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
if inspect.isawaitable(func) \
|
if (
|
||||||
or inspect.iscoroutinefunction(func) \
|
inspect.isawaitable(func)
|
||||||
or isinstance(func, AbstractFilter):
|
or inspect.iscoroutinefunction(func)
|
||||||
|
or isinstance(func, AbstractFilter)
|
||||||
|
):
|
||||||
return func
|
return func
|
||||||
return async_wrapper
|
return async_wrapper
|
||||||
|
|
||||||
|
|
@ -23,14 +25,16 @@ def wrap_async(func):
|
||||||
def get_filter_spec(dispatcher, filter_: callable):
|
def get_filter_spec(dispatcher, filter_: callable):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if not callable(filter_):
|
if not callable(filter_):
|
||||||
raise TypeError('Filter must be callable and/or awaitable!')
|
raise TypeError("Filter must be callable and/or awaitable!")
|
||||||
|
|
||||||
spec = inspect.getfullargspec(filter_)
|
spec = inspect.getfullargspec(filter_)
|
||||||
if 'dispatcher' in spec:
|
if "dispatcher" in spec:
|
||||||
kwargs['dispatcher'] = dispatcher
|
kwargs["dispatcher"] = dispatcher
|
||||||
if inspect.isawaitable(filter_) \
|
if (
|
||||||
or inspect.iscoroutinefunction(filter_) \
|
inspect.isawaitable(filter_)
|
||||||
or isinstance(filter_, AbstractFilter):
|
or inspect.iscoroutinefunction(filter_)
|
||||||
|
or isinstance(filter_, AbstractFilter)
|
||||||
|
):
|
||||||
return FilterObj(filter=filter_, kwargs=kwargs, is_async=True)
|
return FilterObj(filter=filter_, kwargs=kwargs, is_async=True)
|
||||||
else:
|
else:
|
||||||
return FilterObj(filter=filter_, kwargs=kwargs, is_async=False)
|
return FilterObj(filter=filter_, kwargs=kwargs, is_async=False)
|
||||||
|
|
@ -82,12 +86,17 @@ class FilterRecord:
|
||||||
Filters record for factory
|
Filters record for factory
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, callback: typing.Callable,
|
def __init__(
|
||||||
validator: typing.Optional[typing.Callable] = None,
|
self,
|
||||||
event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
|
callback: typing.Callable,
|
||||||
exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None):
|
validator: typing.Optional[typing.Callable] = None,
|
||||||
|
event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
|
||||||
|
exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
|
||||||
|
):
|
||||||
if event_handlers and exclude_event_handlers:
|
if event_handlers and exclude_event_handlers:
|
||||||
raise ValueError("'event_handlers' and 'exclude_event_handlers' arguments cannot be used together.")
|
raise ValueError(
|
||||||
|
"'event_handlers' and 'exclude_event_handlers' arguments cannot be used together."
|
||||||
|
)
|
||||||
|
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.event_handlers = event_handlers
|
self.event_handlers = event_handlers
|
||||||
|
|
@ -100,17 +109,17 @@ class FilterRecord:
|
||||||
elif issubclass(callback, AbstractFilter):
|
elif issubclass(callback, AbstractFilter):
|
||||||
self.resolver = callback.validate
|
self.resolver = callback.validate
|
||||||
else:
|
else:
|
||||||
raise RuntimeError('validator is required!')
|
raise RuntimeError("validator is required!")
|
||||||
|
|
||||||
def resolve(self, dispatcher, event_handler, full_config):
|
def resolve(self, dispatcher, event_handler, full_config):
|
||||||
if not self._check_event_handler(event_handler):
|
if not self._check_event_handler(event_handler):
|
||||||
return
|
return
|
||||||
config = self.resolver(full_config)
|
config = self.resolver(full_config)
|
||||||
if config:
|
if config:
|
||||||
if 'dispatcher' not in config:
|
if "dispatcher" not in config:
|
||||||
spec = inspect.getfullargspec(self.callback)
|
spec = inspect.getfullargspec(self.callback)
|
||||||
if 'dispatcher' in spec.args:
|
if "dispatcher" in spec.args:
|
||||||
config['dispatcher'] = dispatcher
|
config["dispatcher"] = dispatcher
|
||||||
|
|
||||||
for key in config:
|
for key in config:
|
||||||
if key in full_config:
|
if key in full_config:
|
||||||
|
|
@ -133,7 +142,9 @@ class AbstractFilter(abc.ABC):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
|
def validate(
|
||||||
|
cls, full_config: typing.Dict[str, typing.Any]
|
||||||
|
) -> typing.Optional[typing.Dict[str, typing.Any]]:
|
||||||
"""
|
"""
|
||||||
Validate and parse config.
|
Validate and parse config.
|
||||||
|
|
||||||
|
|
@ -184,7 +195,9 @@ class Filter(AbstractFilter):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
|
def validate(
|
||||||
|
cls, full_config: typing.Dict[str, typing.Any]
|
||||||
|
) -> typing.Optional[typing.Dict[str, typing.Any]]:
|
||||||
"""
|
"""
|
||||||
Here method ``validate`` is optional.
|
Here method ``validate`` is optional.
|
||||||
If you need to use filter from filters factory you need to override this method.
|
If you need to use filter from filters factory you need to override this method.
|
||||||
|
|
@ -228,7 +241,7 @@ class BoundFilter(Filter):
|
||||||
class _LogicFilter(Filter):
|
class _LogicFilter(Filter):
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, full_config: typing.Dict[str, typing.Any]):
|
def validate(cls, full_config: typing.Dict[str, typing.Any]):
|
||||||
raise ValueError('That filter can\'t be used in filters factory!')
|
raise ValueError("That filter can't be used in filters factory!")
|
||||||
|
|
||||||
|
|
||||||
class NotFilter(_LogicFilter):
|
class NotFilter(_LogicFilter):
|
||||||
|
|
@ -240,7 +253,6 @@ class NotFilter(_LogicFilter):
|
||||||
|
|
||||||
|
|
||||||
class AndFilter(_LogicFilter):
|
class AndFilter(_LogicFilter):
|
||||||
|
|
||||||
def __init__(self, *targets):
|
def __init__(self, *targets):
|
||||||
self.targets = list(wrap_async(target) for target in targets)
|
self.targets = list(wrap_async(target) for target in targets)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class State:
|
||||||
@property
|
@property
|
||||||
def group(self):
|
def group(self):
|
||||||
if not self._group:
|
if not self._group:
|
||||||
raise RuntimeError('This state is not in any group.')
|
raise RuntimeError("This state is not in any group.")
|
||||||
return self._group
|
return self._group
|
||||||
|
|
||||||
def get_root(self):
|
def get_root(self):
|
||||||
|
|
@ -27,19 +27,19 @@ class State:
|
||||||
def state(self):
|
def state(self):
|
||||||
if self._state is None:
|
if self._state is None:
|
||||||
return None
|
return None
|
||||||
elif self._state == '*':
|
elif self._state == "*":
|
||||||
return self._state
|
return self._state
|
||||||
elif self._group_name is None and self._group:
|
elif self._group_name is None and self._group:
|
||||||
group = self._group.__full_group_name__
|
group = self._group.__full_group_name__
|
||||||
elif self._group_name:
|
elif self._group_name:
|
||||||
group = self._group_name
|
group = self._group_name
|
||||||
else:
|
else:
|
||||||
group = '@'
|
group = "@"
|
||||||
return f"{group}:{self._state}"
|
return f"{group}:{self._state}"
|
||||||
|
|
||||||
def set_parent(self, group):
|
def set_parent(self, group):
|
||||||
if not issubclass(group, StatesGroup):
|
if not issubclass(group, StatesGroup):
|
||||||
raise ValueError('Group must be subclass of StatesGroup')
|
raise ValueError("Group must be subclass of StatesGroup")
|
||||||
self._group = group
|
self._group = group
|
||||||
|
|
||||||
def __set_name__(self, owner, name):
|
def __set_name__(self, owner, name):
|
||||||
|
|
@ -89,7 +89,7 @@ class StatesGroupMeta(type):
|
||||||
@property
|
@property
|
||||||
def __full_group_name__(cls):
|
def __full_group_name__(cls):
|
||||||
if cls._parent:
|
if cls._parent:
|
||||||
return cls._parent.__full_group_name__ + '.' + cls._group_name
|
return cls._parent.__full_group_name__ + "." + cls._group_name
|
||||||
return cls._group_name
|
return cls._group_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -195,4 +195,4 @@ class StatesGroup(metaclass=StatesGroupMeta):
|
||||||
|
|
||||||
|
|
||||||
default_state = State()
|
default_state = State()
|
||||||
any_state = State(state='*')
|
any_state = State(state="*")
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ from contextvars import ContextVar
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, Iterable
|
from typing import Optional, Iterable
|
||||||
|
|
||||||
ctx_data = ContextVar('ctx_handler_data')
|
ctx_data = ContextVar("ctx_handler_data")
|
||||||
current_handler = ContextVar('current_handler')
|
current_handler = ContextVar("current_handler")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -23,7 +23,7 @@ class CancelHandler(Exception):
|
||||||
|
|
||||||
|
|
||||||
def _get_spec(func: callable):
|
def _get_spec(func: callable):
|
||||||
while hasattr(func, '__wrapped__'): # Try to resolve decorated callbacks
|
while hasattr(func, "__wrapped__"): # Try to resolve decorated callbacks
|
||||||
func = func.__wrapped__
|
func = func.__wrapped__
|
||||||
|
|
||||||
spec = inspect.getfullargspec(func)
|
spec = inspect.getfullargspec(func)
|
||||||
|
|
@ -47,6 +47,7 @@ class Handler:
|
||||||
|
|
||||||
def register(self, handler, filters=None, index=None):
|
def register(self, handler, filters=None, index=None):
|
||||||
from .filters import get_filters_spec
|
from .filters import get_filters_spec
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Register callback
|
Register callback
|
||||||
|
|
||||||
|
|
@ -80,7 +81,7 @@ class Handler:
|
||||||
if handler is registered:
|
if handler is registered:
|
||||||
self.handlers.remove(handler_obj)
|
self.handlers.remove(handler_obj)
|
||||||
return True
|
return True
|
||||||
raise ValueError('This handler is not registered!')
|
raise ValueError("This handler is not registered!")
|
||||||
|
|
||||||
async def notify(self, *args):
|
async def notify(self, *args):
|
||||||
"""
|
"""
|
||||||
|
|
@ -98,7 +99,9 @@ class Handler:
|
||||||
|
|
||||||
if self.middleware_key:
|
if self.middleware_key:
|
||||||
try:
|
try:
|
||||||
await self.dispatcher.middleware.trigger(f"pre_process_{self.middleware_key}", args + (data,))
|
await self.dispatcher.middleware.trigger(
|
||||||
|
f"pre_process_{self.middleware_key}", args + (data,)
|
||||||
|
)
|
||||||
except CancelHandler: # Allow to cancel current event
|
except CancelHandler: # Allow to cancel current event
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
@ -112,7 +115,9 @@ class Handler:
|
||||||
ctx_token = current_handler.set(handler_obj.handler)
|
ctx_token = current_handler.set(handler_obj.handler)
|
||||||
try:
|
try:
|
||||||
if self.middleware_key:
|
if self.middleware_key:
|
||||||
await self.dispatcher.middleware.trigger(f"process_{self.middleware_key}", args + (data,))
|
await self.dispatcher.middleware.trigger(
|
||||||
|
f"process_{self.middleware_key}", args + (data,)
|
||||||
|
)
|
||||||
partial_data = _check_spec(handler_obj.spec, data)
|
partial_data = _check_spec(handler_obj.spec, data)
|
||||||
response = await handler_obj.handler(*args, **partial_data)
|
response = await handler_obj.handler(*args, **partial_data)
|
||||||
if response is not None:
|
if response is not None:
|
||||||
|
|
@ -127,8 +132,9 @@ class Handler:
|
||||||
current_handler.reset(ctx_token)
|
current_handler.reset(ctx_token)
|
||||||
finally:
|
finally:
|
||||||
if self.middleware_key:
|
if self.middleware_key:
|
||||||
await self.dispatcher.middleware.trigger(f"post_process_{self.middleware_key}",
|
await self.dispatcher.middleware.trigger(
|
||||||
args + (results, data,))
|
f"post_process_{self.middleware_key}", args + (results, data)
|
||||||
|
)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
log = logging.getLogger('aiogram.Middleware')
|
log = logging.getLogger("aiogram.Middleware")
|
||||||
|
|
||||||
|
|
||||||
class MiddlewareManager:
|
class MiddlewareManager:
|
||||||
|
|
@ -29,9 +29,11 @@ class MiddlewareManager:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if not isinstance(middleware, BaseMiddleware):
|
if not isinstance(middleware, BaseMiddleware):
|
||||||
raise TypeError(f"`middleware` must be an instance of BaseMiddleware, not {type(middleware)}")
|
raise TypeError(
|
||||||
|
f"`middleware` must be an instance of BaseMiddleware, not {type(middleware)}"
|
||||||
|
)
|
||||||
if middleware.is_configured():
|
if middleware.is_configured():
|
||||||
raise ValueError('That middleware is already used!')
|
raise ValueError("That middleware is already used!")
|
||||||
|
|
||||||
self.applications.append(middleware)
|
self.applications.append(middleware)
|
||||||
middleware.setup(self)
|
middleware.setup(self)
|
||||||
|
|
@ -67,7 +69,7 @@ class BaseMiddleware:
|
||||||
Instance of MiddlewareManager
|
Instance of MiddlewareManager
|
||||||
"""
|
"""
|
||||||
if self._manager is None:
|
if self._manager is None:
|
||||||
raise RuntimeError('Middleware is not configured!')
|
raise RuntimeError("Middleware is not configured!")
|
||||||
return self._manager
|
return self._manager
|
||||||
|
|
||||||
def setup(self, manager):
|
def setup(self, manager):
|
||||||
|
|
@ -119,9 +121,9 @@ class LifetimeControllerMiddleware(BaseMiddleware):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
obj, *args, data = args
|
obj, *args, data = args
|
||||||
if action.startswith('pre_process_'):
|
if action.startswith("pre_process_"):
|
||||||
await self.pre_process(obj, data, *args)
|
await self.pre_process(obj, data, *args)
|
||||||
elif action.startswith('post_process_'):
|
elif action.startswith("post_process_"):
|
||||||
await self.post_process(obj, data, *args)
|
await self.post_process(obj, data, *args)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ from ..utils.deprecated import warn_deprecated as warn
|
||||||
from ..utils.exceptions import FSMStorageWarning
|
from ..utils.exceptions import FSMStorageWarning
|
||||||
|
|
||||||
# Leak bucket
|
# Leak bucket
|
||||||
KEY = 'key'
|
KEY = "key"
|
||||||
LAST_CALL = 'called_at'
|
LAST_CALL = "called_at"
|
||||||
RATE_LIMIT = 'rate_limit'
|
RATE_LIMIT = "rate_limit"
|
||||||
RESULT = 'result'
|
RESULT = "result"
|
||||||
EXCEEDED_COUNT = 'exceeded'
|
EXCEEDED_COUNT = "exceeded"
|
||||||
DELTA = 'delta'
|
DELTA = "delta"
|
||||||
THROTTLE_MANAGER = '$throttle_manager'
|
THROTTLE_MANAGER = "$throttle_manager"
|
||||||
|
|
||||||
|
|
||||||
class BaseStorage:
|
class BaseStorage:
|
||||||
|
|
@ -38,9 +38,12 @@ class BaseStorage:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_address(cls, *,
|
def check_address(
|
||||||
chat: typing.Union[str, int, None] = None,
|
cls,
|
||||||
user: typing.Union[str, int, None] = None) -> (typing.Union[str, int], typing.Union[str, int]):
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
) -> (typing.Union[str, int], typing.Union[str, int]):
|
||||||
"""
|
"""
|
||||||
In all storage's methods chat or user is always required.
|
In all storage's methods chat or user is always required.
|
||||||
If one of them is not provided, you have to set missing value based on the provided one.
|
If one of them is not provided, you have to set missing value based on the provided one.
|
||||||
|
|
@ -52,7 +55,7 @@ class BaseStorage:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if chat is None and user is None:
|
if chat is None and user is None:
|
||||||
raise ValueError('`user` or `chat` parameter is required but no one is provided!')
|
raise ValueError("`user` or `chat` parameter is required but no one is provided!")
|
||||||
|
|
||||||
if user is None and chat is not None:
|
if user is None and chat is not None:
|
||||||
user = chat
|
user = chat
|
||||||
|
|
@ -60,10 +63,13 @@ class BaseStorage:
|
||||||
chat = user
|
chat = user
|
||||||
return chat, user
|
return chat, user
|
||||||
|
|
||||||
async def get_state(self, *,
|
async def get_state(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Optional[str]:
|
||||||
"""
|
"""
|
||||||
Get current state of user in chat. Return `default` if no record is found.
|
Get current state of user in chat. Return `default` if no record is found.
|
||||||
|
|
||||||
|
|
@ -77,10 +83,13 @@ class BaseStorage:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def get_data(self, *,
|
async def get_data(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
default: typing.Optional[typing.Dict] = None) -> typing.Dict:
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[typing.Dict] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
"""
|
"""
|
||||||
Get state-data for user in chat. Return `default` if no data is provided in storage.
|
Get state-data for user in chat. Return `default` if no data is provided in storage.
|
||||||
|
|
||||||
|
|
@ -94,10 +103,13 @@ class BaseStorage:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def set_state(self, *,
|
async def set_state(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
state: typing.Optional[typing.AnyStr] = None):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
state: typing.Optional[typing.AnyStr] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Set new state for user in chat
|
Set new state for user in chat
|
||||||
|
|
||||||
|
|
@ -110,10 +122,13 @@ class BaseStorage:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def set_data(self, *,
|
async def set_data(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
data: typing.Dict = None):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Set data for user in chat
|
Set data for user in chat
|
||||||
|
|
||||||
|
|
@ -126,11 +141,14 @@ class BaseStorage:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def update_data(self, *,
|
async def update_data(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
data: typing.Dict = None,
|
chat: typing.Union[str, int, None] = None,
|
||||||
**kwargs):
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Update data for user in chat
|
Update data for user in chat
|
||||||
|
|
||||||
|
|
@ -147,9 +165,12 @@ class BaseStorage:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def reset_data(self, *,
|
async def reset_data(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None):
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Reset data for user in chat.
|
Reset data for user in chat.
|
||||||
|
|
||||||
|
|
@ -162,10 +183,13 @@ class BaseStorage:
|
||||||
"""
|
"""
|
||||||
await self.set_data(chat=chat, user=user, data={})
|
await self.set_data(chat=chat, user=user, data={})
|
||||||
|
|
||||||
async def reset_state(self, *,
|
async def reset_state(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
with_data: typing.Optional[bool] = True):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
with_data: typing.Optional[bool] = True,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Reset state for user in chat.
|
Reset state for user in chat.
|
||||||
You may desire to use this method when finishing conversations.
|
You may desire to use this method when finishing conversations.
|
||||||
|
|
@ -183,9 +207,12 @@ class BaseStorage:
|
||||||
if with_data:
|
if with_data:
|
||||||
await self.set_data(chat=chat, user=user, data={})
|
await self.set_data(chat=chat, user=user, data={})
|
||||||
|
|
||||||
async def finish(self, *,
|
async def finish(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None):
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Finish conversation for user in chat.
|
Finish conversation for user in chat.
|
||||||
|
|
||||||
|
|
@ -201,10 +228,13 @@ class BaseStorage:
|
||||||
def has_bucket(self):
|
def has_bucket(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_bucket(self, *,
|
async def get_bucket(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
default: typing.Optional[dict] = None) -> typing.Dict:
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[dict] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
"""
|
"""
|
||||||
Get bucket for user in chat. Return `default` if no data is provided in storage.
|
Get bucket for user in chat. Return `default` if no data is provided in storage.
|
||||||
|
|
||||||
|
|
@ -218,10 +248,13 @@ class BaseStorage:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def set_bucket(self, *,
|
async def set_bucket(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
bucket: typing.Dict = None):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Set bucket for user in chat
|
Set bucket for user in chat
|
||||||
|
|
||||||
|
|
@ -234,11 +267,14 @@ class BaseStorage:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def update_bucket(self, *,
|
async def update_bucket(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
bucket: typing.Dict = None,
|
chat: typing.Union[str, int, None] = None,
|
||||||
**kwargs):
|
user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Update bucket for user in chat
|
Update bucket for user in chat
|
||||||
|
|
||||||
|
|
@ -255,9 +291,12 @@ class BaseStorage:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def reset_bucket(self, *,
|
async def reset_bucket(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None):
|
*,
|
||||||
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Reset bucket dor user in chat.
|
Reset bucket dor user in chat.
|
||||||
|
|
||||||
|
|
@ -292,7 +331,9 @@ class FSMContext:
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
async def get_state(self, default: typing.Optional[str] = None) -> typing.Optional[str]:
|
async def get_state(self, default: typing.Optional[str] = None) -> typing.Optional[str]:
|
||||||
return await self.storage.get_state(chat=self.chat, user=self.user, default=self._resolve_state(default))
|
return await self.storage.get_state(
|
||||||
|
chat=self.chat, user=self.user, default=self._resolve_state(default)
|
||||||
|
)
|
||||||
|
|
||||||
async def get_data(self, default: typing.Optional[str] = None) -> typing.Dict:
|
async def get_data(self, default: typing.Optional[str] = None) -> typing.Dict:
|
||||||
return await self.storage.get_data(chat=self.chat, user=self.user, default=default)
|
return await self.storage.get_data(chat=self.chat, user=self.user, default=default)
|
||||||
|
|
@ -301,7 +342,9 @@ class FSMContext:
|
||||||
await self.storage.update_data(chat=self.chat, user=self.user, data=data, **kwargs)
|
await self.storage.update_data(chat=self.chat, user=self.user, data=data, **kwargs)
|
||||||
|
|
||||||
async def set_state(self, state: typing.Union[typing.AnyStr, None] = None):
|
async def set_state(self, state: typing.Union[typing.AnyStr, None] = None):
|
||||||
await self.storage.set_state(chat=self.chat, user=self.user, state=self._resolve_state(state))
|
await self.storage.set_state(
|
||||||
|
chat=self.chat, user=self.user, state=self._resolve_state(state)
|
||||||
|
)
|
||||||
|
|
||||||
async def set_data(self, data: typing.Dict = None):
|
async def set_data(self, data: typing.Dict = None):
|
||||||
await self.storage.set_data(chat=self.chat, user=self.user, data=data)
|
await self.storage.set_data(chat=self.chat, user=self.user, data=data)
|
||||||
|
|
@ -338,7 +381,7 @@ class FSMContextProxy:
|
||||||
|
|
||||||
def _check_closed(self):
|
def _check_closed(self):
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise LookupError('Proxy is closed!')
|
raise LookupError("Proxy is closed!")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def create(cls, fsm_context: FSMContext):
|
async def create(cls, fsm_context: FSMContext):
|
||||||
|
|
@ -447,7 +490,7 @@ class FSMContextProxy:
|
||||||
readable_state = f"'{self.state}'" if self.state else "<default>"
|
readable_state = f"'{self.state}'" if self.state else "<default>"
|
||||||
result = f"{self.__class__.__name__} state = {readable_state}, data = {self._data}"
|
result = f"{self.__class__.__name__} state = {readable_state}, data = {self._data}"
|
||||||
if self._closed:
|
if self._closed:
|
||||||
result += ', closed = True'
|
result += ", closed = True"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -462,39 +505,58 @@ class DisabledStorage(BaseStorage):
|
||||||
async def wait_closed(self):
|
async def wait_closed(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_state(self, *,
|
async def get_state(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Optional[str]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_data(self, *,
|
async def get_data(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
default: typing.Optional[str] = None) -> typing.Dict:
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
default: typing.Optional[str] = None,
|
||||||
|
) -> typing.Dict:
|
||||||
self._warn()
|
self._warn()
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
async def update_data(self, *,
|
async def update_data(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
data: typing.Dict = None, **kwargs):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
self._warn()
|
self._warn()
|
||||||
|
|
||||||
async def set_state(self, *,
|
async def set_state(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
state: typing.Optional[typing.AnyStr] = None):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
state: typing.Optional[typing.AnyStr] = None,
|
||||||
|
):
|
||||||
self._warn()
|
self._warn()
|
||||||
|
|
||||||
async def set_data(self, *,
|
async def set_data(
|
||||||
chat: typing.Union[str, int, None] = None,
|
self,
|
||||||
user: typing.Union[str, int, None] = None,
|
*,
|
||||||
data: typing.Dict = None):
|
chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
|
):
|
||||||
self._warn()
|
self._warn()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _warn():
|
def _warn():
|
||||||
warn(f"You haven’t set any storage yet so no states and no data will be saved. \n"
|
warn(
|
||||||
f"You can connect MemoryStorage for debug purposes or non-essential data.",
|
f"You haven’t set any storage yet so no states and no data will be saved. \n"
|
||||||
FSMStorageWarning, 5)
|
f"You can connect MemoryStorage for debug purposes or non-essential data.",
|
||||||
|
FSMStorageWarning,
|
||||||
|
5,
|
||||||
|
)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -19,17 +19,46 @@ from .game import Game
|
||||||
from .game_high_score import GameHighScore
|
from .game_high_score import GameHighScore
|
||||||
from .inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup
|
from .inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
from .inline_query import InlineQuery
|
from .inline_query import InlineQuery
|
||||||
from .inline_query_result import InlineQueryResult, InlineQueryResultArticle, InlineQueryResultAudio, \
|
from .inline_query_result import (
|
||||||
InlineQueryResultCachedAudio, InlineQueryResultCachedDocument, InlineQueryResultCachedGif, \
|
InlineQueryResult,
|
||||||
InlineQueryResultCachedMpeg4Gif, InlineQueryResultCachedPhoto, InlineQueryResultCachedSticker, \
|
InlineQueryResultArticle,
|
||||||
InlineQueryResultCachedVideo, InlineQueryResultCachedVoice, InlineQueryResultContact, InlineQueryResultDocument, \
|
InlineQueryResultAudio,
|
||||||
InlineQueryResultGame, InlineQueryResultGif, InlineQueryResultLocation, InlineQueryResultMpeg4Gif, \
|
InlineQueryResultCachedAudio,
|
||||||
InlineQueryResultPhoto, InlineQueryResultVenue, InlineQueryResultVideo, InlineQueryResultVoice
|
InlineQueryResultCachedDocument,
|
||||||
|
InlineQueryResultCachedGif,
|
||||||
|
InlineQueryResultCachedMpeg4Gif,
|
||||||
|
InlineQueryResultCachedPhoto,
|
||||||
|
InlineQueryResultCachedSticker,
|
||||||
|
InlineQueryResultCachedVideo,
|
||||||
|
InlineQueryResultCachedVoice,
|
||||||
|
InlineQueryResultContact,
|
||||||
|
InlineQueryResultDocument,
|
||||||
|
InlineQueryResultGame,
|
||||||
|
InlineQueryResultGif,
|
||||||
|
InlineQueryResultLocation,
|
||||||
|
InlineQueryResultMpeg4Gif,
|
||||||
|
InlineQueryResultPhoto,
|
||||||
|
InlineQueryResultVenue,
|
||||||
|
InlineQueryResultVideo,
|
||||||
|
InlineQueryResultVoice,
|
||||||
|
)
|
||||||
from .input_file import InputFile
|
from .input_file import InputFile
|
||||||
from .input_media import InputMedia, InputMediaAnimation, InputMediaAudio, InputMediaDocument, InputMediaPhoto, \
|
from .input_media import (
|
||||||
InputMediaVideo, MediaGroup
|
InputMedia,
|
||||||
from .input_message_content import InputContactMessageContent, InputLocationMessageContent, InputMessageContent, \
|
InputMediaAnimation,
|
||||||
InputTextMessageContent, InputVenueMessageContent
|
InputMediaAudio,
|
||||||
|
InputMediaDocument,
|
||||||
|
InputMediaPhoto,
|
||||||
|
InputMediaVideo,
|
||||||
|
MediaGroup,
|
||||||
|
)
|
||||||
|
from .input_message_content import (
|
||||||
|
InputContactMessageContent,
|
||||||
|
InputLocationMessageContent,
|
||||||
|
InputMessageContent,
|
||||||
|
InputTextMessageContent,
|
||||||
|
InputVenueMessageContent,
|
||||||
|
)
|
||||||
from .invoice import Invoice
|
from .invoice import Invoice
|
||||||
from .labeled_price import LabeledPrice
|
from .labeled_price import LabeledPrice
|
||||||
from .location import Location
|
from .location import Location
|
||||||
|
|
@ -39,9 +68,15 @@ from .message import ContentType, ContentTypes, Message, ParseMode
|
||||||
from .message_entity import MessageEntity, MessageEntityType
|
from .message_entity import MessageEntity, MessageEntityType
|
||||||
from .order_info import OrderInfo
|
from .order_info import OrderInfo
|
||||||
from .passport_data import PassportData
|
from .passport_data import PassportData
|
||||||
from .passport_element_error import PassportElementError, PassportElementErrorDataField, PassportElementErrorFile, \
|
from .passport_element_error import (
|
||||||
PassportElementErrorFiles, PassportElementErrorFrontSide, PassportElementErrorReverseSide, \
|
PassportElementError,
|
||||||
PassportElementErrorSelfie
|
PassportElementErrorDataField,
|
||||||
|
PassportElementErrorFile,
|
||||||
|
PassportElementErrorFiles,
|
||||||
|
PassportElementErrorFrontSide,
|
||||||
|
PassportElementErrorReverseSide,
|
||||||
|
PassportElementErrorSelfie,
|
||||||
|
)
|
||||||
from .passport_file import PassportFile
|
from .passport_file import PassportFile
|
||||||
from .photo_size import PhotoSize
|
from .photo_size import PhotoSize
|
||||||
from .poll import PollOption, Poll
|
from .poll import PollOption, Poll
|
||||||
|
|
@ -64,107 +99,107 @@ from .voice import Voice
|
||||||
from .webhook_info import WebhookInfo
|
from .webhook_info import WebhookInfo
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'AllowedUpdates',
|
"AllowedUpdates",
|
||||||
'Animation',
|
"Animation",
|
||||||
'Audio',
|
"Audio",
|
||||||
'AuthWidgetData',
|
"AuthWidgetData",
|
||||||
'CallbackGame',
|
"CallbackGame",
|
||||||
'CallbackQuery',
|
"CallbackQuery",
|
||||||
'Chat',
|
"Chat",
|
||||||
'ChatActions',
|
"ChatActions",
|
||||||
'ChatMember',
|
"ChatMember",
|
||||||
'ChatMemberStatus',
|
"ChatMemberStatus",
|
||||||
'ChatPhoto',
|
"ChatPhoto",
|
||||||
'ChatType',
|
"ChatType",
|
||||||
'ChosenInlineResult',
|
"ChosenInlineResult",
|
||||||
'Contact',
|
"Contact",
|
||||||
'ContentType',
|
"ContentType",
|
||||||
'ContentTypes',
|
"ContentTypes",
|
||||||
'Document',
|
"Document",
|
||||||
'EncryptedCredentials',
|
"EncryptedCredentials",
|
||||||
'EncryptedPassportElement',
|
"EncryptedPassportElement",
|
||||||
'File',
|
"File",
|
||||||
'ForceReply',
|
"ForceReply",
|
||||||
'Game',
|
"Game",
|
||||||
'GameHighScore',
|
"GameHighScore",
|
||||||
'InlineKeyboardButton',
|
"InlineKeyboardButton",
|
||||||
'InlineKeyboardMarkup',
|
"InlineKeyboardMarkup",
|
||||||
'InlineQuery',
|
"InlineQuery",
|
||||||
'InlineQueryResult',
|
"InlineQueryResult",
|
||||||
'InlineQueryResultArticle',
|
"InlineQueryResultArticle",
|
||||||
'InlineQueryResultAudio',
|
"InlineQueryResultAudio",
|
||||||
'InlineQueryResultCachedAudio',
|
"InlineQueryResultCachedAudio",
|
||||||
'InlineQueryResultCachedDocument',
|
"InlineQueryResultCachedDocument",
|
||||||
'InlineQueryResultCachedGif',
|
"InlineQueryResultCachedGif",
|
||||||
'InlineQueryResultCachedMpeg4Gif',
|
"InlineQueryResultCachedMpeg4Gif",
|
||||||
'InlineQueryResultCachedPhoto',
|
"InlineQueryResultCachedPhoto",
|
||||||
'InlineQueryResultCachedSticker',
|
"InlineQueryResultCachedSticker",
|
||||||
'InlineQueryResultCachedVideo',
|
"InlineQueryResultCachedVideo",
|
||||||
'InlineQueryResultCachedVoice',
|
"InlineQueryResultCachedVoice",
|
||||||
'InlineQueryResultContact',
|
"InlineQueryResultContact",
|
||||||
'InlineQueryResultDocument',
|
"InlineQueryResultDocument",
|
||||||
'InlineQueryResultGame',
|
"InlineQueryResultGame",
|
||||||
'InlineQueryResultGif',
|
"InlineQueryResultGif",
|
||||||
'InlineQueryResultLocation',
|
"InlineQueryResultLocation",
|
||||||
'InlineQueryResultMpeg4Gif',
|
"InlineQueryResultMpeg4Gif",
|
||||||
'InlineQueryResultPhoto',
|
"InlineQueryResultPhoto",
|
||||||
'InlineQueryResultVenue',
|
"InlineQueryResultVenue",
|
||||||
'InlineQueryResultVideo',
|
"InlineQueryResultVideo",
|
||||||
'InlineQueryResultVoice',
|
"InlineQueryResultVoice",
|
||||||
'InputContactMessageContent',
|
"InputContactMessageContent",
|
||||||
'InputFile',
|
"InputFile",
|
||||||
'InputLocationMessageContent',
|
"InputLocationMessageContent",
|
||||||
'InputMedia',
|
"InputMedia",
|
||||||
'InputMediaAnimation',
|
"InputMediaAnimation",
|
||||||
'InputMediaAudio',
|
"InputMediaAudio",
|
||||||
'InputMediaDocument',
|
"InputMediaDocument",
|
||||||
'InputMediaPhoto',
|
"InputMediaPhoto",
|
||||||
'InputMediaVideo',
|
"InputMediaVideo",
|
||||||
'InputMessageContent',
|
"InputMessageContent",
|
||||||
'InputTextMessageContent',
|
"InputTextMessageContent",
|
||||||
'InputVenueMessageContent',
|
"InputVenueMessageContent",
|
||||||
'Invoice',
|
"Invoice",
|
||||||
'KeyboardButton',
|
"KeyboardButton",
|
||||||
'LabeledPrice',
|
"LabeledPrice",
|
||||||
'Location',
|
"Location",
|
||||||
'LoginUrl',
|
"LoginUrl",
|
||||||
'MaskPosition',
|
"MaskPosition",
|
||||||
'MediaGroup',
|
"MediaGroup",
|
||||||
'Message',
|
"Message",
|
||||||
'MessageEntity',
|
"MessageEntity",
|
||||||
'MessageEntityType',
|
"MessageEntityType",
|
||||||
'OrderInfo',
|
"OrderInfo",
|
||||||
'ParseMode',
|
"ParseMode",
|
||||||
'PassportData',
|
"PassportData",
|
||||||
'PassportElementError',
|
"PassportElementError",
|
||||||
'PassportElementErrorDataField',
|
"PassportElementErrorDataField",
|
||||||
'PassportElementErrorFile',
|
"PassportElementErrorFile",
|
||||||
'PassportElementErrorFiles',
|
"PassportElementErrorFiles",
|
||||||
'PassportElementErrorFrontSide',
|
"PassportElementErrorFrontSide",
|
||||||
'PassportElementErrorReverseSide',
|
"PassportElementErrorReverseSide",
|
||||||
'PassportElementErrorSelfie',
|
"PassportElementErrorSelfie",
|
||||||
'PassportFile',
|
"PassportFile",
|
||||||
'PhotoSize',
|
"PhotoSize",
|
||||||
'Poll',
|
"Poll",
|
||||||
'PollOption',
|
"PollOption",
|
||||||
'PreCheckoutQuery',
|
"PreCheckoutQuery",
|
||||||
'ReplyKeyboardMarkup',
|
"ReplyKeyboardMarkup",
|
||||||
'ReplyKeyboardRemove',
|
"ReplyKeyboardRemove",
|
||||||
'ResponseParameters',
|
"ResponseParameters",
|
||||||
'ShippingAddress',
|
"ShippingAddress",
|
||||||
'ShippingOption',
|
"ShippingOption",
|
||||||
'ShippingQuery',
|
"ShippingQuery",
|
||||||
'Sticker',
|
"Sticker",
|
||||||
'StickerSet',
|
"StickerSet",
|
||||||
'SuccessfulPayment',
|
"SuccessfulPayment",
|
||||||
'Update',
|
"Update",
|
||||||
'User',
|
"User",
|
||||||
'UserProfilePhotos',
|
"UserProfilePhotos",
|
||||||
'Venue',
|
"Venue",
|
||||||
'Video',
|
"Video",
|
||||||
'VideoNote',
|
"VideoNote",
|
||||||
'Voice',
|
"Voice",
|
||||||
'WebhookInfo',
|
"WebhookInfo",
|
||||||
'base',
|
"base",
|
||||||
'fields',
|
"fields",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class Audio(base.TelegramObject, mixins.Downloadable):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#audio
|
https://core.telegram.org/bots/api#audio
|
||||||
"""
|
"""
|
||||||
|
|
||||||
file_id: base.String = fields.Field()
|
file_id: base.String = fields.Field()
|
||||||
duration: base.Integer = fields.Field()
|
duration: base.Integer = fields.Field()
|
||||||
performer: base.String = fields.Field()
|
performer: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,11 @@ class AuthWidgetData(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
query = dict(request.query)
|
query = dict(request.query)
|
||||||
query['id'] = int(query['id'])
|
query["id"] = int(query["id"])
|
||||||
query['auth_date'] = int(query['auth_date'])
|
query["auth_date"] = int(query["auth_date"])
|
||||||
widget = AuthWidgetData(**query)
|
widget = AuthWidgetData(**query)
|
||||||
except (ValueError, KeyError):
|
except (ValueError, KeyError):
|
||||||
raise web.HTTPBadRequest(text='Invalid auth data')
|
raise web.HTTPBadRequest(text="Invalid auth data")
|
||||||
else:
|
else:
|
||||||
return widget
|
return widget
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ class AuthWidgetData(base.TelegramObject):
|
||||||
def full_name(self):
|
def full_name(self):
|
||||||
result = self.first_name
|
result = self.first_name
|
||||||
if self.last_name:
|
if self.last_name:
|
||||||
result += ' '
|
result += " "
|
||||||
result += self.last_name
|
result += self.last_name
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,24 +10,33 @@ from .fields import BaseField
|
||||||
from ..utils import json
|
from ..utils import json
|
||||||
from ..utils.mixins import ContextInstanceMixin
|
from ..utils.mixins import ContextInstanceMixin
|
||||||
|
|
||||||
__all__ = ('MetaTelegramObject', 'TelegramObject', 'InputFile', 'String', 'Integer', 'Float', 'Boolean')
|
__all__ = (
|
||||||
|
"MetaTelegramObject",
|
||||||
|
"TelegramObject",
|
||||||
|
"InputFile",
|
||||||
|
"String",
|
||||||
|
"Integer",
|
||||||
|
"Float",
|
||||||
|
"Boolean",
|
||||||
|
)
|
||||||
|
|
||||||
PROPS_ATTR_NAME = '_props'
|
PROPS_ATTR_NAME = "_props"
|
||||||
VALUES_ATTR_NAME = '_values'
|
VALUES_ATTR_NAME = "_values"
|
||||||
ALIASES_ATTR_NAME = '_aliases'
|
ALIASES_ATTR_NAME = "_aliases"
|
||||||
|
|
||||||
# Binding of builtin types
|
# Binding of builtin types
|
||||||
InputFile = TypeVar('InputFile', 'InputFile', io.BytesIO, io.FileIO, str)
|
InputFile = TypeVar("InputFile", "InputFile", io.BytesIO, io.FileIO, str)
|
||||||
String = TypeVar('String', bound=str)
|
String = TypeVar("String", bound=str)
|
||||||
Integer = TypeVar('Integer', bound=int)
|
Integer = TypeVar("Integer", bound=int)
|
||||||
Float = TypeVar('Float', bound=float)
|
Float = TypeVar("Float", bound=float)
|
||||||
Boolean = TypeVar('Boolean', bound=bool)
|
Boolean = TypeVar("Boolean", bound=bool)
|
||||||
|
|
||||||
|
|
||||||
class MetaTelegramObject(type):
|
class MetaTelegramObject(type):
|
||||||
"""
|
"""
|
||||||
Metaclass for telegram objects
|
Metaclass for telegram objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_objects = {}
|
_objects = {}
|
||||||
|
|
||||||
def __new__(mcs, name, bases, namespace, **kwargs):
|
def __new__(mcs, name, bases, namespace, **kwargs):
|
||||||
|
|
@ -46,7 +55,9 @@ class MetaTelegramObject(type):
|
||||||
aliases.update(getattr(base, ALIASES_ATTR_NAME))
|
aliases.update(getattr(base, ALIASES_ATTR_NAME))
|
||||||
|
|
||||||
# Scan current object for props
|
# Scan current object for props
|
||||||
for name, prop in ((name, prop) for name, prop in namespace.items() if isinstance(prop, BaseField)):
|
for name, prop in (
|
||||||
|
(name, prop) for name, prop in namespace.items() if isinstance(prop, BaseField)
|
||||||
|
):
|
||||||
props[prop.alias] = prop
|
props[prop.alias] = prop
|
||||||
if prop.default is not None:
|
if prop.default is not None:
|
||||||
values[prop.alias] = prop.default
|
values[prop.alias] = prop.default
|
||||||
|
|
@ -147,9 +158,11 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
||||||
|
|
||||||
bot = Bot.get_current()
|
bot = Bot.get_current()
|
||||||
if bot is None:
|
if bot is None:
|
||||||
raise RuntimeError("Can't get bot instance from context. "
|
raise RuntimeError(
|
||||||
"You can fix it with setting current instance: "
|
"Can't get bot instance from context. "
|
||||||
"'Bot.set_current(bot_instance)'")
|
"You can fix it with setting current instance: "
|
||||||
|
"'Bot.set_current(bot_instance)'"
|
||||||
|
)
|
||||||
return bot
|
return bot
|
||||||
|
|
||||||
def to_python(self) -> typing.Dict:
|
def to_python(self) -> typing.Dict:
|
||||||
|
|
@ -219,7 +232,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if key in self.props:
|
if key in self.props:
|
||||||
return self.props[key].set_value(self, value, self.conf.get('parent', None))
|
return self.props[key].set_value(self, value, self.conf.get("parent", None))
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
|
|
||||||
def __contains__(self, item):
|
def __contains__(self, item):
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ class CallbackGame(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#callbackgame
|
https://core.telegram.org/bots/api#callbackgame
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,22 @@ class CallbackQuery(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#callbackquery
|
https://core.telegram.org/bots/api#callbackquery
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: base.String = fields.Field()
|
id: base.String = fields.Field()
|
||||||
from_user: User = fields.Field(alias='from', base=User)
|
from_user: User = fields.Field(alias="from", base=User)
|
||||||
message: Message = fields.Field(base=Message)
|
message: Message = fields.Field(base=Message)
|
||||||
inline_message_id: base.String = fields.Field()
|
inline_message_id: base.String = fields.Field()
|
||||||
chat_instance: base.String = fields.Field()
|
chat_instance: base.String = fields.Field()
|
||||||
data: base.String = fields.Field()
|
data: base.String = fields.Field()
|
||||||
game_short_name: base.String = fields.Field()
|
game_short_name: base.String = fields.Field()
|
||||||
|
|
||||||
async def answer(self, text: typing.Union[base.String, None] = None,
|
async def answer(
|
||||||
show_alert: typing.Union[base.Boolean, None] = None,
|
self,
|
||||||
url: typing.Union[base.String, None] = None,
|
text: typing.Union[base.String, None] = None,
|
||||||
cache_time: typing.Union[base.Integer, None] = None):
|
show_alert: typing.Union[base.Boolean, None] = None,
|
||||||
|
url: typing.Union[base.String, None] = None,
|
||||||
|
cache_time: typing.Union[base.Integer, None] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Use this method to send answers to callback queries sent from inline keyboards.
|
Use this method to send answers to callback queries sent from inline keyboards.
|
||||||
The answer will be displayed to the user as a notification at the top of the chat screen or as an alert.
|
The answer will be displayed to the user as a notification at the top of the chat screen or as an alert.
|
||||||
|
|
@ -54,8 +58,13 @@ class CallbackQuery(base.TelegramObject):
|
||||||
:type cache_time: :obj:`typing.Union[base.Integer, None]`
|
:type cache_time: :obj:`typing.Union[base.Integer, None]`
|
||||||
:return: On success, True is returned.
|
:return: On success, True is returned.
|
||||||
:rtype: :obj:`base.Boolean`"""
|
:rtype: :obj:`base.Boolean`"""
|
||||||
await self.bot.answer_callback_query(callback_query_id=self.id, text=text,
|
await self.bot.answer_callback_query(
|
||||||
show_alert=show_alert, url=url, cache_time=cache_time)
|
callback_query_id=self.id,
|
||||||
|
text=text,
|
||||||
|
show_alert=show_alert,
|
||||||
|
url=url,
|
||||||
|
cache_time=cache_time,
|
||||||
|
)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.id)
|
return hash(self.id)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class Chat(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#chat
|
https://core.telegram.org/bots/api#chat
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: base.Integer = fields.Field()
|
id: base.Integer = fields.Field()
|
||||||
type: base.String = fields.Field()
|
type: base.String = fields.Field()
|
||||||
title: base.String = fields.Field()
|
title: base.String = fields.Field()
|
||||||
|
|
@ -26,7 +27,7 @@ class Chat(base.TelegramObject):
|
||||||
photo: ChatPhoto = fields.Field(base=ChatPhoto)
|
photo: ChatPhoto = fields.Field(base=ChatPhoto)
|
||||||
description: base.String = fields.Field()
|
description: base.String = fields.Field()
|
||||||
invite_link: base.String = fields.Field()
|
invite_link: base.String = fields.Field()
|
||||||
pinned_message: 'Message' = fields.Field(base='Message')
|
pinned_message: "Message" = fields.Field(base="Message")
|
||||||
sticker_set_name: base.String = fields.Field()
|
sticker_set_name: base.String = fields.Field()
|
||||||
can_set_sticker_set: base.Boolean = fields.Field()
|
can_set_sticker_set: base.Boolean = fields.Field()
|
||||||
|
|
||||||
|
|
@ -38,7 +39,7 @@ class Chat(base.TelegramObject):
|
||||||
if self.type == ChatType.PRIVATE:
|
if self.type == ChatType.PRIVATE:
|
||||||
full_name = self.first_name
|
full_name = self.first_name
|
||||||
if self.last_name:
|
if self.last_name:
|
||||||
full_name += ' ' + self.last_name
|
full_name += " " + self.last_name
|
||||||
return full_name
|
return full_name
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
@ -48,7 +49,7 @@ class Chat(base.TelegramObject):
|
||||||
Get mention if a Chat has a username, or get full name if this is a Private Chat, otherwise None is returned
|
Get mention if a Chat has a username, or get full name if this is a Private Chat, otherwise None is returned
|
||||||
"""
|
"""
|
||||||
if self.username:
|
if self.username:
|
||||||
return '@' + self.username
|
return "@" + self.username
|
||||||
if self.type == ChatType.PRIVATE:
|
if self.type == ChatType.PRIVATE:
|
||||||
return self.full_name
|
return self.full_name
|
||||||
return None
|
return None
|
||||||
|
|
@ -56,7 +57,7 @@ class Chat(base.TelegramObject):
|
||||||
@property
|
@property
|
||||||
def user_url(self):
|
def user_url(self):
|
||||||
if self.type != ChatType.PRIVATE:
|
if self.type != ChatType.PRIVATE:
|
||||||
raise TypeError('`user_url` property is only available in private chats!')
|
raise TypeError("`user_url` property is only available in private chats!")
|
||||||
|
|
||||||
return f"tg://user?id={self.id}"
|
return f"tg://user?id={self.id}"
|
||||||
|
|
||||||
|
|
@ -79,7 +80,7 @@ class Chat(base.TelegramObject):
|
||||||
return f"tg://user?id={self.id}"
|
return f"tg://user?id={self.id}"
|
||||||
|
|
||||||
if self.username:
|
if self.username:
|
||||||
return f'https://t.me/{self.username}'
|
return f"https://t.me/{self.username}"
|
||||||
|
|
||||||
if self.invite_link:
|
if self.invite_link:
|
||||||
return self.invite_link
|
return self.invite_link
|
||||||
|
|
@ -161,8 +162,9 @@ class Chat(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
return await self.bot.delete_chat_description(self.id, description)
|
return await self.bot.delete_chat_description(self.id, description)
|
||||||
|
|
||||||
async def kick(self, user_id: base.Integer,
|
async def kick(
|
||||||
until_date: typing.Union[base.Integer, None] = None):
|
self, user_id: base.Integer, until_date: typing.Union[base.Integer, None] = None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Use this method to kick a user from a group, a supergroup or a channel.
|
Use this method to kick a user from a group, a supergroup or a channel.
|
||||||
In the case of supergroups and channels, the user will not be able to return to the group
|
In the case of supergroups and channels, the user will not be able to return to the group
|
||||||
|
|
@ -201,12 +203,15 @@ class Chat(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
return await self.bot.unban_chat_member(self.id, user_id=user_id)
|
return await self.bot.unban_chat_member(self.id, user_id=user_id)
|
||||||
|
|
||||||
async def restrict(self, user_id: base.Integer,
|
async def restrict(
|
||||||
until_date: typing.Union[base.Integer, None] = None,
|
self,
|
||||||
can_send_messages: typing.Union[base.Boolean, None] = None,
|
user_id: base.Integer,
|
||||||
can_send_media_messages: typing.Union[base.Boolean, None] = None,
|
until_date: typing.Union[base.Integer, None] = None,
|
||||||
can_send_other_messages: typing.Union[base.Boolean, None] = None,
|
can_send_messages: typing.Union[base.Boolean, None] = None,
|
||||||
can_add_web_page_previews: typing.Union[base.Boolean, None] = None) -> base.Boolean:
|
can_send_media_messages: typing.Union[base.Boolean, None] = None,
|
||||||
|
can_send_other_messages: typing.Union[base.Boolean, None] = None,
|
||||||
|
can_add_web_page_previews: typing.Union[base.Boolean, None] = None,
|
||||||
|
) -> base.Boolean:
|
||||||
"""
|
"""
|
||||||
Use this method to restrict a user in a supergroup.
|
Use this method to restrict a user in a supergroup.
|
||||||
The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights.
|
The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights.
|
||||||
|
|
@ -232,21 +237,28 @@ class Chat(base.TelegramObject):
|
||||||
:return: Returns True on success.
|
:return: Returns True on success.
|
||||||
:rtype: :obj:`base.Boolean`
|
:rtype: :obj:`base.Boolean`
|
||||||
"""
|
"""
|
||||||
return await self.bot.restrict_chat_member(self.id, user_id=user_id, until_date=until_date,
|
return await self.bot.restrict_chat_member(
|
||||||
can_send_messages=can_send_messages,
|
self.id,
|
||||||
can_send_media_messages=can_send_media_messages,
|
user_id=user_id,
|
||||||
can_send_other_messages=can_send_other_messages,
|
until_date=until_date,
|
||||||
can_add_web_page_previews=can_add_web_page_previews)
|
can_send_messages=can_send_messages,
|
||||||
|
can_send_media_messages=can_send_media_messages,
|
||||||
|
can_send_other_messages=can_send_other_messages,
|
||||||
|
can_add_web_page_previews=can_add_web_page_previews,
|
||||||
|
)
|
||||||
|
|
||||||
async def promote(self, user_id: base.Integer,
|
async def promote(
|
||||||
can_change_info: typing.Union[base.Boolean, None] = None,
|
self,
|
||||||
can_post_messages: typing.Union[base.Boolean, None] = None,
|
user_id: base.Integer,
|
||||||
can_edit_messages: typing.Union[base.Boolean, None] = None,
|
can_change_info: typing.Union[base.Boolean, None] = None,
|
||||||
can_delete_messages: typing.Union[base.Boolean, None] = None,
|
can_post_messages: typing.Union[base.Boolean, None] = None,
|
||||||
can_invite_users: typing.Union[base.Boolean, None] = None,
|
can_edit_messages: typing.Union[base.Boolean, None] = None,
|
||||||
can_restrict_members: typing.Union[base.Boolean, None] = None,
|
can_delete_messages: typing.Union[base.Boolean, None] = None,
|
||||||
can_pin_messages: typing.Union[base.Boolean, None] = None,
|
can_invite_users: typing.Union[base.Boolean, None] = None,
|
||||||
can_promote_members: typing.Union[base.Boolean, None] = None) -> base.Boolean:
|
can_restrict_members: typing.Union[base.Boolean, None] = None,
|
||||||
|
can_pin_messages: typing.Union[base.Boolean, None] = None,
|
||||||
|
can_promote_members: typing.Union[base.Boolean, None] = None,
|
||||||
|
) -> base.Boolean:
|
||||||
"""
|
"""
|
||||||
Use this method to promote or demote a user in a supergroup or a channel.
|
Use this method to promote or demote a user in a supergroup or a channel.
|
||||||
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||||
|
|
@ -277,16 +289,18 @@ class Chat(base.TelegramObject):
|
||||||
:return: Returns True on success.
|
:return: Returns True on success.
|
||||||
:rtype: :obj:`base.Boolean`
|
:rtype: :obj:`base.Boolean`
|
||||||
"""
|
"""
|
||||||
return await self.bot.promote_chat_member(self.id,
|
return await self.bot.promote_chat_member(
|
||||||
user_id=user_id,
|
self.id,
|
||||||
can_change_info=can_change_info,
|
user_id=user_id,
|
||||||
can_post_messages=can_post_messages,
|
can_change_info=can_change_info,
|
||||||
can_edit_messages=can_edit_messages,
|
can_post_messages=can_post_messages,
|
||||||
can_delete_messages=can_delete_messages,
|
can_edit_messages=can_edit_messages,
|
||||||
can_invite_users=can_invite_users,
|
can_delete_messages=can_delete_messages,
|
||||||
can_restrict_members=can_restrict_members,
|
can_invite_users=can_invite_users,
|
||||||
can_pin_messages=can_pin_messages,
|
can_restrict_members=can_restrict_members,
|
||||||
can_promote_members=can_promote_members)
|
can_pin_messages=can_pin_messages,
|
||||||
|
can_promote_members=can_promote_members,
|
||||||
|
)
|
||||||
|
|
||||||
async def pin_message(self, message_id: int, disable_notification: bool = False):
|
async def pin_message(self, message_id: int, disable_notification: bool = False):
|
||||||
"""
|
"""
|
||||||
|
|
@ -422,9 +436,9 @@ class ChatType(helper.Helper):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check(obj, chat_types) -> bool:
|
def _check(obj, chat_types) -> bool:
|
||||||
if hasattr(obj, 'chat'):
|
if hasattr(obj, "chat"):
|
||||||
obj = obj.chat
|
obj = obj.chat
|
||||||
if not hasattr(obj, 'type'):
|
if not hasattr(obj, "type"):
|
||||||
return False
|
return False
|
||||||
return obj.type in chat_types
|
return obj.type in chat_types
|
||||||
|
|
||||||
|
|
@ -511,12 +525,13 @@ class ChatActions(helper.Helper):
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _do(cls, action: str, sleep=None):
|
async def _do(cls, action: str, sleep=None):
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
|
|
||||||
await Bot.get_current().send_chat_action(Chat.get_current().id, action)
|
await Bot.get_current().send_chat_action(Chat.get_current().id, action)
|
||||||
if sleep:
|
if sleep:
|
||||||
await asyncio.sleep(sleep)
|
await asyncio.sleep(sleep)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def calc_timeout(cls, text, timeout=.8):
|
def calc_timeout(cls, text, timeout=0.8):
|
||||||
"""
|
"""
|
||||||
Calculate timeout for text
|
Calculate timeout for text
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ class ChatMember(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#chatmember
|
https://core.telegram.org/bots/api#chatmember
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user: User = fields.Field(base=User)
|
user: User = fields.Field(base=User)
|
||||||
status: base.String = fields.Field()
|
status: base.String = fields.Field()
|
||||||
until_date: datetime.datetime = fields.DateTimeField()
|
until_date: datetime.datetime = fields.DateTimeField()
|
||||||
|
|
@ -32,9 +33,12 @@ class ChatMember(base.TelegramObject):
|
||||||
can_add_web_page_previews: base.Boolean = fields.Field()
|
can_add_web_page_previews: base.Boolean = fields.Field()
|
||||||
|
|
||||||
def is_admin(self):
|
def is_admin(self):
|
||||||
warnings.warn('`is_admin` method deprecated due to updates in Bot API 4.2. '
|
warnings.warn(
|
||||||
'This method renamed to `is_chat_admin` and will be available until aiogram 2.3',
|
"`is_admin` method deprecated due to updates in Bot API 4.2. "
|
||||||
DeprecationWarning, stacklevel=2)
|
"This method renamed to `is_chat_admin` and will be available until aiogram 2.3",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
return self.is_chat_admin()
|
return self.is_chat_admin()
|
||||||
|
|
||||||
def is_chat_admin(self):
|
def is_chat_admin(self):
|
||||||
|
|
@ -62,16 +66,22 @@ class ChatMemberStatus(helper.Helper):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_admin(cls, role):
|
def is_admin(cls, role):
|
||||||
warnings.warn('`is_admin` method deprecated due to updates in Bot API 4.2. '
|
warnings.warn(
|
||||||
'This method renamed to `is_chat_admin` and will be available until aiogram 2.3',
|
"`is_admin` method deprecated due to updates in Bot API 4.2. "
|
||||||
DeprecationWarning, stacklevel=2)
|
"This method renamed to `is_chat_admin` and will be available until aiogram 2.3",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
return cls.is_chat_admin(role)
|
return cls.is_chat_admin(role)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_member(cls, role):
|
def is_member(cls, role):
|
||||||
warnings.warn('`is_member` method deprecated due to updates in Bot API 4.2. '
|
warnings.warn(
|
||||||
'This method renamed to `is_chat_member` and will be available until aiogram 2.3',
|
"`is_member` method deprecated due to updates in Bot API 4.2. "
|
||||||
DeprecationWarning, stacklevel=2)
|
"This method renamed to `is_chat_member` and will be available until aiogram 2.3",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
return cls.is_chat_member(role)
|
return cls.is_chat_member(role)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,13 @@ class ChatPhoto(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#chatphoto
|
https://core.telegram.org/bots/api#chatphoto
|
||||||
"""
|
"""
|
||||||
|
|
||||||
small_file_id: base.String = fields.Field()
|
small_file_id: base.String = fields.Field()
|
||||||
big_file_id: base.String = fields.Field()
|
big_file_id: base.String = fields.Field()
|
||||||
|
|
||||||
async def download_small(self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True):
|
async def download_small(
|
||||||
|
self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Download file
|
Download file
|
||||||
|
|
||||||
|
|
@ -38,10 +41,17 @@ class ChatPhoto(base.TelegramObject):
|
||||||
if is_path and make_dirs:
|
if is_path and make_dirs:
|
||||||
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
||||||
|
|
||||||
return await self.bot.download_file(file_path=file.file_path, destination=destination, timeout=timeout,
|
return await self.bot.download_file(
|
||||||
chunk_size=chunk_size, seek=seek)
|
file_path=file.file_path,
|
||||||
|
destination=destination,
|
||||||
|
timeout=timeout,
|
||||||
|
chunk_size=chunk_size,
|
||||||
|
seek=seek,
|
||||||
|
)
|
||||||
|
|
||||||
async def download_big(self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True):
|
async def download_big(
|
||||||
|
self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Download file
|
Download file
|
||||||
|
|
||||||
|
|
@ -65,8 +75,13 @@ class ChatPhoto(base.TelegramObject):
|
||||||
if is_path and make_dirs:
|
if is_path and make_dirs:
|
||||||
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
||||||
|
|
||||||
return await self.bot.download_file(file_path=file.file_path, destination=destination, timeout=timeout,
|
return await self.bot.download_file(
|
||||||
chunk_size=chunk_size, seek=seek)
|
file_path=file.file_path,
|
||||||
|
destination=destination,
|
||||||
|
timeout=timeout,
|
||||||
|
chunk_size=chunk_size,
|
||||||
|
seek=seek,
|
||||||
|
)
|
||||||
|
|
||||||
async def get_small_file(self):
|
async def get_small_file(self):
|
||||||
return await self.bot.get_file(self.small_file_id)
|
return await self.bot.get_file(self.small_file_id)
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ class ChosenInlineResult(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#choseninlineresult
|
https://core.telegram.org/bots/api#choseninlineresult
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result_id: base.String = fields.Field()
|
result_id: base.String = fields.Field()
|
||||||
from_user: User = fields.Field(alias='from', base=User)
|
from_user: User = fields.Field(alias="from", base=User)
|
||||||
location: Location = fields.Field(base=Location)
|
location: Location = fields.Field(base=Location)
|
||||||
inline_message_id: base.String = fields.Field()
|
inline_message_id: base.String = fields.Field()
|
||||||
query: base.String = fields.Field()
|
query: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ class Contact(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#contact
|
https://core.telegram.org/bots/api#contact
|
||||||
"""
|
"""
|
||||||
|
|
||||||
phone_number: base.String = fields.Field()
|
phone_number: base.String = fields.Field()
|
||||||
first_name: base.String = fields.Field()
|
first_name: base.String = fields.Field()
|
||||||
last_name: base.String = fields.Field()
|
last_name: base.String = fields.Field()
|
||||||
|
|
@ -18,7 +19,7 @@ class Contact(base.TelegramObject):
|
||||||
def full_name(self):
|
def full_name(self):
|
||||||
name = self.first_name
|
name = self.first_name
|
||||||
if self.last_name is not None:
|
if self.last_name is not None:
|
||||||
name += ' ' + self.last_name
|
name += " " + self.last_name
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class Document(base.TelegramObject, mixins.Downloadable):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#document
|
https://core.telegram.org/bots/api#document
|
||||||
"""
|
"""
|
||||||
|
|
||||||
file_id: base.String = fields.Field()
|
file_id: base.String = fields.Field()
|
||||||
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
||||||
file_name: base.String = fields.Field()
|
file_name: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import abc
|
import abc
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
__all__ = ('BaseField', 'Field', 'ListField', 'DateTimeField', 'TextField', 'ListOfLists')
|
__all__ = ("BaseField", "Field", "ListField", "DateTimeField", "TextField", "ListOfLists")
|
||||||
|
|
||||||
|
|
||||||
class BaseField(metaclass=abc.ABCMeta):
|
class BaseField(metaclass=abc.ABCMeta):
|
||||||
|
|
@ -29,7 +29,7 @@ class BaseField(metaclass=abc.ABCMeta):
|
||||||
self.alias = name
|
self.alias = name
|
||||||
|
|
||||||
def resolve_base(self, instance):
|
def resolve_base(self, instance):
|
||||||
if self.base_object is None or hasattr(self.base_object, 'telegram_types'):
|
if self.base_object is None or hasattr(self.base_object, "telegram_types"):
|
||||||
return
|
return
|
||||||
elif isinstance(self.base_object, str):
|
elif isinstance(self.base_object, str):
|
||||||
self.base_object = instance.telegram_types.get(self.base_object)
|
self.base_object = instance.telegram_types.get(self.base_object)
|
||||||
|
|
@ -100,16 +100,18 @@ class Field(BaseField):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def serialize(self, value):
|
def serialize(self, value):
|
||||||
if self.base_object is not None and hasattr(value, 'to_python'):
|
if self.base_object is not None and hasattr(value, "to_python"):
|
||||||
return value.to_python()
|
return value.to_python()
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def deserialize(self, value, parent=None):
|
def deserialize(self, value, parent=None):
|
||||||
if isinstance(value, dict) \
|
if (
|
||||||
and self.base_object is not None \
|
isinstance(value, dict)
|
||||||
and not hasattr(value, 'base_object') \
|
and self.base_object is not None
|
||||||
and not hasattr(value, 'to_python'):
|
and not hasattr(value, "base_object")
|
||||||
return self.base_object(conf={'parent': parent}, **value)
|
and not hasattr(value, "to_python")
|
||||||
|
):
|
||||||
|
return self.base_object(conf={"parent": parent}, **value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -119,7 +121,7 @@ class ListField(Field):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
default = kwargs.pop('default', None)
|
default = kwargs.pop("default", None)
|
||||||
if default is None:
|
if default is None:
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
|
|
@ -154,7 +156,7 @@ class ListOfLists(Field):
|
||||||
def deserialize(self, value, parent=None):
|
def deserialize(self, value, parent=None):
|
||||||
result = []
|
result = []
|
||||||
deserialize = super(ListOfLists, self).deserialize
|
deserialize = super(ListOfLists, self).deserialize
|
||||||
if hasattr(value, '__iter__'):
|
if hasattr(value, "__iter__"):
|
||||||
for row in value:
|
for row in value:
|
||||||
row_result = []
|
row_result = []
|
||||||
for item in row:
|
for item in row:
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class File(base.TelegramObject, mixins.Downloadable):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#file
|
https://core.telegram.org/bots/api#file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
file_id: base.String = fields.Field()
|
file_id: base.String = fields.Field()
|
||||||
file_size: base.Integer = fields.Field()
|
file_size: base.Integer = fields.Field()
|
||||||
file_path: base.String = fields.Field()
|
file_path: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ class ForceReply(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#forcereply
|
https://core.telegram.org/bots/api#forcereply
|
||||||
"""
|
"""
|
||||||
|
|
||||||
force_reply: base.Boolean = fields.Field(default=True)
|
force_reply: base.Boolean = fields.Field(default=True)
|
||||||
selective: base.Boolean = fields.Field()
|
selective: base.Boolean = fields.Field()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class Game(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#game
|
https://core.telegram.org/bots/api#game
|
||||||
"""
|
"""
|
||||||
|
|
||||||
title: base.String = fields.Field()
|
title: base.String = fields.Field()
|
||||||
description: base.String = fields.Field()
|
description: base.String = fields.Field()
|
||||||
photo: typing.List[PhotoSize] = fields.ListField(base=PhotoSize)
|
photo: typing.List[PhotoSize] = fields.ListField(base=PhotoSize)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ class GameHighScore(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#gamehighscore
|
https://core.telegram.org/bots/api#gamehighscore
|
||||||
"""
|
"""
|
||||||
|
|
||||||
position: base.Integer = fields.Field()
|
position: base.Integer = fields.Field()
|
||||||
user: User = fields.Field(base=User)
|
user: User = fields.Field(base=User)
|
||||||
score: base.Integer = fields.Field()
|
score: base.Integer = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -15,26 +15,29 @@ class InlineKeyboardMarkup(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#inlinekeyboardmarkup
|
https://core.telegram.org/bots/api#inlinekeyboardmarkup
|
||||||
"""
|
"""
|
||||||
inline_keyboard: 'typing.List[typing.List[InlineKeyboardButton]]' = fields.ListOfLists(base='InlineKeyboardButton')
|
|
||||||
|
inline_keyboard: "typing.List[typing.List[InlineKeyboardButton]]" = fields.ListOfLists(
|
||||||
|
base="InlineKeyboardButton"
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, row_width=3, inline_keyboard=None, **kwargs):
|
def __init__(self, row_width=3, inline_keyboard=None, **kwargs):
|
||||||
if inline_keyboard is None:
|
if inline_keyboard is None:
|
||||||
inline_keyboard = []
|
inline_keyboard = []
|
||||||
|
|
||||||
conf = kwargs.pop('conf', {}) or {}
|
conf = kwargs.pop("conf", {}) or {}
|
||||||
conf['row_width'] = row_width
|
conf["row_width"] = row_width
|
||||||
|
|
||||||
super(InlineKeyboardMarkup, self).__init__(**kwargs,
|
super(InlineKeyboardMarkup, self).__init__(
|
||||||
conf=conf,
|
**kwargs, conf=conf, inline_keyboard=inline_keyboard
|
||||||
inline_keyboard=inline_keyboard)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def row_width(self):
|
def row_width(self):
|
||||||
return self.conf.get('row_width', 3)
|
return self.conf.get("row_width", 3)
|
||||||
|
|
||||||
@row_width.setter
|
@row_width.setter
|
||||||
def row_width(self, value):
|
def row_width(self, value):
|
||||||
self.conf['row_width'] = value
|
self.conf["row_width"] = value
|
||||||
|
|
||||||
def add(self, *args):
|
def add(self, *args):
|
||||||
"""
|
"""
|
||||||
|
|
@ -89,6 +92,7 @@ class InlineKeyboardButton(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#inlinekeyboardbutton
|
https://core.telegram.org/bots/api#inlinekeyboardbutton
|
||||||
"""
|
"""
|
||||||
|
|
||||||
text: base.String = fields.Field()
|
text: base.String = fields.Field()
|
||||||
url: base.String = fields.Field()
|
url: base.String = fields.Field()
|
||||||
login_url: LoginUrl = fields.Field(base=LoginUrl)
|
login_url: LoginUrl = fields.Field(base=LoginUrl)
|
||||||
|
|
@ -98,19 +102,26 @@ class InlineKeyboardButton(base.TelegramObject):
|
||||||
callback_game: CallbackGame = fields.Field(base=CallbackGame)
|
callback_game: CallbackGame = fields.Field(base=CallbackGame)
|
||||||
pay: base.Boolean = fields.Field()
|
pay: base.Boolean = fields.Field()
|
||||||
|
|
||||||
def __init__(self, text: base.String,
|
def __init__(
|
||||||
url: base.String = None,
|
self,
|
||||||
login_url: LoginUrl = None,
|
text: base.String,
|
||||||
callback_data: base.String = None,
|
url: base.String = None,
|
||||||
switch_inline_query: base.String = None,
|
login_url: LoginUrl = None,
|
||||||
switch_inline_query_current_chat: base.String = None,
|
callback_data: base.String = None,
|
||||||
callback_game: CallbackGame = None,
|
switch_inline_query: base.String = None,
|
||||||
pay: base.Boolean = None, **kwargs):
|
switch_inline_query_current_chat: base.String = None,
|
||||||
super(InlineKeyboardButton, self).__init__(text=text,
|
callback_game: CallbackGame = None,
|
||||||
url=url,
|
pay: base.Boolean = None,
|
||||||
login_url=login_url,
|
**kwargs,
|
||||||
callback_data=callback_data,
|
):
|
||||||
switch_inline_query=switch_inline_query,
|
super(InlineKeyboardButton, self).__init__(
|
||||||
switch_inline_query_current_chat=switch_inline_query_current_chat,
|
text=text,
|
||||||
callback_game=callback_game,
|
url=url,
|
||||||
pay=pay, **kwargs)
|
login_url=login_url,
|
||||||
|
callback_data=callback_data,
|
||||||
|
switch_inline_query=switch_inline_query,
|
||||||
|
switch_inline_query_current_chat=switch_inline_query_current_chat,
|
||||||
|
callback_game=callback_game,
|
||||||
|
pay=pay,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,22 @@ class InlineQuery(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#inlinequery
|
https://core.telegram.org/bots/api#inlinequery
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: base.String = fields.Field()
|
id: base.String = fields.Field()
|
||||||
from_user: User = fields.Field(alias='from', base=User)
|
from_user: User = fields.Field(alias="from", base=User)
|
||||||
location: Location = fields.Field(base=Location)
|
location: Location = fields.Field(base=Location)
|
||||||
query: base.String = fields.Field()
|
query: base.String = fields.Field()
|
||||||
offset: base.String = fields.Field()
|
offset: base.String = fields.Field()
|
||||||
|
|
||||||
async def answer(self,
|
async def answer(
|
||||||
results: typing.List[InlineQueryResult],
|
self,
|
||||||
cache_time: typing.Union[base.Integer, None] = None,
|
results: typing.List[InlineQueryResult],
|
||||||
is_personal: typing.Union[base.Boolean, None] = None,
|
cache_time: typing.Union[base.Integer, None] = None,
|
||||||
next_offset: typing.Union[base.String, None] = None,
|
is_personal: typing.Union[base.Boolean, None] = None,
|
||||||
switch_pm_text: typing.Union[base.String, None] = None,
|
next_offset: typing.Union[base.String, None] = None,
|
||||||
switch_pm_parameter: typing.Union[base.String, None] = None):
|
switch_pm_text: typing.Union[base.String, None] = None,
|
||||||
|
switch_pm_parameter: typing.Union[base.String, None] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Use this method to send answers to an inline query.
|
Use this method to send answers to an inline query.
|
||||||
No more than 50 results per query are allowed.
|
No more than 50 results per query are allowed.
|
||||||
|
|
@ -57,10 +60,12 @@ class InlineQuery(base.TelegramObject):
|
||||||
:return: On success, True is returned
|
:return: On success, True is returned
|
||||||
:rtype: :obj:`base.Boolean`
|
:rtype: :obj:`base.Boolean`
|
||||||
"""
|
"""
|
||||||
return await self.bot.answer_inline_query(self.id,
|
return await self.bot.answer_inline_query(
|
||||||
results=results,
|
self.id,
|
||||||
cache_time=cache_time,
|
results=results,
|
||||||
is_personal=is_personal,
|
cache_time=cache_time,
|
||||||
next_offset=next_offset,
|
is_personal=is_personal,
|
||||||
switch_pm_text=switch_pm_text,
|
next_offset=next_offset,
|
||||||
switch_pm_parameter=switch_pm_parameter)
|
switch_pm_text=switch_pm_text,
|
||||||
|
switch_pm_parameter=switch_pm_parameter,
|
||||||
|
)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -12,7 +12,7 @@ from ..bot import api
|
||||||
|
|
||||||
CHUNK_SIZE = 65536
|
CHUNK_SIZE = 65536
|
||||||
|
|
||||||
log = logging.getLogger('aiogram')
|
log = logging.getLogger("aiogram")
|
||||||
|
|
||||||
|
|
||||||
class InputFile(base.TelegramObject):
|
class InputFile(base.TelegramObject):
|
||||||
|
|
@ -35,7 +35,7 @@ class InputFile(base.TelegramObject):
|
||||||
super(InputFile, self).__init__(conf=conf)
|
super(InputFile, self).__init__(conf=conf)
|
||||||
if isinstance(path_or_bytesio, str):
|
if isinstance(path_or_bytesio, str):
|
||||||
# As path
|
# As path
|
||||||
self._file = open(path_or_bytesio, 'rb')
|
self._file = open(path_or_bytesio, "rb")
|
||||||
self._path = path_or_bytesio
|
self._path = path_or_bytesio
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = os.path.split(path_or_bytesio)[-1]
|
filename = os.path.split(path_or_bytesio)[-1]
|
||||||
|
|
@ -46,7 +46,7 @@ class InputFile(base.TelegramObject):
|
||||||
self._path = None
|
self._path = None
|
||||||
self._file = path_or_bytesio
|
self._file = path_or_bytesio
|
||||||
else:
|
else:
|
||||||
raise TypeError('Not supported file type.')
|
raise TypeError("Not supported file type.")
|
||||||
|
|
||||||
self._filename = filename
|
self._filename = filename
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ class InputFile(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
Close file descriptor
|
Close file descriptor
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, '_file'):
|
if not hasattr(self, "_file"):
|
||||||
return
|
return
|
||||||
|
|
||||||
if inspect.iscoroutinefunction(self._file.close):
|
if inspect.iscoroutinefunction(self._file.close):
|
||||||
|
|
@ -123,7 +123,7 @@ class InputFile(base.TelegramObject):
|
||||||
:param filename:
|
:param filename:
|
||||||
:param chunk_size:
|
:param chunk_size:
|
||||||
"""
|
"""
|
||||||
with open(filename, 'wb') as fp:
|
with open(filename, "wb") as fp:
|
||||||
while True:
|
while True:
|
||||||
# Chunk writer
|
# Chunk writer
|
||||||
data = self.file.read(chunk_size)
|
data = self.file.read(chunk_size)
|
||||||
|
|
@ -143,11 +143,11 @@ class InputFile(base.TelegramObject):
|
||||||
__repr__ = __str__
|
__repr__ = __str__
|
||||||
|
|
||||||
def to_python(self):
|
def to_python(self):
|
||||||
raise TypeError('Object of this type is not exportable!')
|
raise TypeError("Object of this type is not exportable!")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_object(cls, data):
|
def to_object(cls, data):
|
||||||
raise TypeError('Object of this type is not importable!')
|
raise TypeError("Object of this type is not importable!")
|
||||||
|
|
||||||
|
|
||||||
class _WebPipe:
|
class _WebPipe:
|
||||||
|
|
@ -165,7 +165,7 @@ class _WebPipe:
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
if not self._name:
|
if not self._name:
|
||||||
*_, part = self.url.rpartition('/')
|
*_, part = self.url.rpartition("/")
|
||||||
if part:
|
if part:
|
||||||
self._name = part
|
self._name = part
|
||||||
else:
|
else:
|
||||||
|
|
@ -206,7 +206,7 @@ class _WebPipe:
|
||||||
|
|
||||||
async def read(self, chunk_size=-1):
|
async def read(self, chunk_size=-1):
|
||||||
if not self._response:
|
if not self._response:
|
||||||
raise LookupError('I/O operation on closed stream')
|
raise LookupError("I/O operation on closed stream")
|
||||||
response: aiohttp.ClientResponse = self._response
|
response: aiohttp.ClientResponse = self._response
|
||||||
reader: aiohttp.StreamReader = response.content
|
reader: aiohttp.StreamReader = response.content
|
||||||
|
|
||||||
|
|
@ -214,6 +214,6 @@ class _WebPipe:
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
result = f"WebPipe url='{self.url}', name='{self.name}'"
|
result = f"WebPipe url='{self.url}', name='{self.name}'"
|
||||||
return '<' + result + '>'
|
return "<" + result + ">"
|
||||||
|
|
||||||
__repr__ = __str__
|
__repr__ = __str__
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
from .input_file import InputFile
|
from .input_file import InputFile
|
||||||
|
|
||||||
ATTACHMENT_PREFIX = 'attach://'
|
ATTACHMENT_PREFIX = "attach://"
|
||||||
|
|
||||||
|
|
||||||
class InputMedia(base.TelegramObject):
|
class InputMedia(base.TelegramObject):
|
||||||
|
|
@ -22,9 +22,12 @@ class InputMedia(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#inputmedia
|
https://core.telegram.org/bots/api#inputmedia
|
||||||
"""
|
"""
|
||||||
type: base.String = fields.Field(default='photo')
|
|
||||||
media: base.String = fields.Field(alias='media', on_change='_media_changed')
|
type: base.String = fields.Field(default="photo")
|
||||||
thumb: typing.Union[base.InputFile, base.String] = fields.Field(alias='thumb', on_change='_thumb_changed')
|
media: base.String = fields.Field(alias="media", on_change="_media_changed")
|
||||||
|
thumb: typing.Union[base.InputFile, base.String] = fields.Field(
|
||||||
|
alias="thumb", on_change="_thumb_changed"
|
||||||
|
)
|
||||||
caption: base.String = fields.Field()
|
caption: base.String = fields.Field()
|
||||||
parse_mode: base.Boolean = fields.Field()
|
parse_mode: base.Boolean = fields.Field()
|
||||||
|
|
||||||
|
|
@ -32,13 +35,13 @@ class InputMedia(base.TelegramObject):
|
||||||
self._thumb_file = None
|
self._thumb_file = None
|
||||||
self._media_file = None
|
self._media_file = None
|
||||||
|
|
||||||
media = kwargs.pop('media', None)
|
media = kwargs.pop("media", None)
|
||||||
if isinstance(media, (io.IOBase, InputFile)):
|
if isinstance(media, (io.IOBase, InputFile)):
|
||||||
self.file = media
|
self.file = media
|
||||||
elif media is not None:
|
elif media is not None:
|
||||||
self.media = media
|
self.media = media
|
||||||
|
|
||||||
thumb = kwargs.pop('thumb', None)
|
thumb = kwargs.pop("thumb", None)
|
||||||
if isinstance(thumb, (io.IOBase, InputFile)):
|
if isinstance(thumb, (io.IOBase, InputFile)):
|
||||||
self.thumb_file = thumb
|
self.thumb_file = thumb
|
||||||
elif thumb is not None:
|
elif thumb is not None:
|
||||||
|
|
@ -58,7 +61,7 @@ class InputMedia(base.TelegramObject):
|
||||||
|
|
||||||
@file.setter
|
@file.setter
|
||||||
def file(self, file: io.IOBase):
|
def file(self, file: io.IOBase):
|
||||||
self.media = 'attach://' + secrets.token_urlsafe(16)
|
self.media = "attach://" + secrets.token_urlsafe(16)
|
||||||
self._media_file = file
|
self._media_file = file
|
||||||
|
|
||||||
@file.deleter
|
@file.deleter
|
||||||
|
|
@ -67,7 +70,7 @@ class InputMedia(base.TelegramObject):
|
||||||
self._media_file = None
|
self._media_file = None
|
||||||
|
|
||||||
def _media_changed(self, value):
|
def _media_changed(self, value):
|
||||||
if value is None or isinstance(value, str) and not value.startswith('attach://'):
|
if value is None or isinstance(value, str) and not value.startswith("attach://"):
|
||||||
self._media_file = None
|
self._media_file = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -76,7 +79,7 @@ class InputMedia(base.TelegramObject):
|
||||||
|
|
||||||
@thumb_file.setter
|
@thumb_file.setter
|
||||||
def thumb_file(self, file: io.IOBase):
|
def thumb_file(self, file: io.IOBase):
|
||||||
self.thumb = 'attach://' + secrets.token_urlsafe(16)
|
self.thumb = "attach://" + secrets.token_urlsafe(16)
|
||||||
self._thumb_file = file
|
self._thumb_file = file
|
||||||
|
|
||||||
@thumb_file.deleter
|
@thumb_file.deleter
|
||||||
|
|
@ -85,7 +88,7 @@ class InputMedia(base.TelegramObject):
|
||||||
self._thumb_file = None
|
self._thumb_file = None
|
||||||
|
|
||||||
def _thumb_changed(self, value):
|
def _thumb_changed(self, value):
|
||||||
if value is None or isinstance(value, str) and not value.startswith('attach://'):
|
if value is None or isinstance(value, str) and not value.startswith("attach://"):
|
||||||
self._thumb_file = None
|
self._thumb_file = None
|
||||||
|
|
||||||
def get_files(self):
|
def get_files(self):
|
||||||
|
|
@ -106,14 +109,28 @@ class InputMediaAnimation(InputMedia):
|
||||||
height: base.Integer = fields.Field()
|
height: base.Integer = fields.Field()
|
||||||
duration: base.Integer = fields.Field()
|
duration: base.Integer = fields.Field()
|
||||||
|
|
||||||
def __init__(self, media: base.InputFile,
|
def __init__(
|
||||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
self,
|
||||||
caption: base.String = None,
|
media: base.InputFile,
|
||||||
width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None,
|
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||||
parse_mode: base.Boolean = None, **kwargs):
|
caption: base.String = None,
|
||||||
super(InputMediaAnimation, self).__init__(type='animation', media=media, thumb=thumb, caption=caption,
|
width: base.Integer = None,
|
||||||
width=width, height=height, duration=duration,
|
height: base.Integer = None,
|
||||||
parse_mode=parse_mode, conf=kwargs)
|
duration: base.Integer = None,
|
||||||
|
parse_mode: base.Boolean = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super(InputMediaAnimation, self).__init__(
|
||||||
|
type="animation",
|
||||||
|
media=media,
|
||||||
|
thumb=thumb,
|
||||||
|
caption=caption,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
duration=duration,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
conf=kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InputMediaDocument(InputMedia):
|
class InputMediaDocument(InputMedia):
|
||||||
|
|
@ -123,11 +140,22 @@ class InputMediaDocument(InputMedia):
|
||||||
https://core.telegram.org/bots/api#inputmediadocument
|
https://core.telegram.org/bots/api#inputmediadocument
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, media: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None,
|
def __init__(
|
||||||
caption: base.String = None, parse_mode: base.Boolean = None, **kwargs):
|
self,
|
||||||
super(InputMediaDocument, self).__init__(type='document', media=media, thumb=thumb,
|
media: base.InputFile,
|
||||||
caption=caption, parse_mode=parse_mode,
|
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||||
conf=kwargs)
|
caption: base.String = None,
|
||||||
|
parse_mode: base.Boolean = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super(InputMediaDocument, self).__init__(
|
||||||
|
type="document",
|
||||||
|
media=media,
|
||||||
|
thumb=thumb,
|
||||||
|
caption=caption,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
conf=kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InputMediaAudio(InputMedia):
|
class InputMediaAudio(InputMedia):
|
||||||
|
|
@ -143,18 +171,32 @@ class InputMediaAudio(InputMedia):
|
||||||
performer: base.String = fields.Field()
|
performer: base.String = fields.Field()
|
||||||
title: base.String = fields.Field()
|
title: base.String = fields.Field()
|
||||||
|
|
||||||
def __init__(self, media: base.InputFile,
|
def __init__(
|
||||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
self,
|
||||||
caption: base.String = None,
|
media: base.InputFile,
|
||||||
width: base.Integer = None, height: base.Integer = None,
|
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||||
duration: base.Integer = None,
|
caption: base.String = None,
|
||||||
performer: base.String = None,
|
width: base.Integer = None,
|
||||||
title: base.String = None,
|
height: base.Integer = None,
|
||||||
parse_mode: base.Boolean = None, **kwargs):
|
duration: base.Integer = None,
|
||||||
super(InputMediaAudio, self).__init__(type='audio', media=media, thumb=thumb, caption=caption,
|
performer: base.String = None,
|
||||||
width=width, height=height, duration=duration,
|
title: base.String = None,
|
||||||
performer=performer, title=title,
|
parse_mode: base.Boolean = None,
|
||||||
parse_mode=parse_mode, conf=kwargs)
|
**kwargs,
|
||||||
|
):
|
||||||
|
super(InputMediaAudio, self).__init__(
|
||||||
|
type="audio",
|
||||||
|
media=media,
|
||||||
|
thumb=thumb,
|
||||||
|
caption=caption,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
duration=duration,
|
||||||
|
performer=performer,
|
||||||
|
title=title,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
conf=kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InputMediaPhoto(InputMedia):
|
class InputMediaPhoto(InputMedia):
|
||||||
|
|
@ -164,11 +206,22 @@ class InputMediaPhoto(InputMedia):
|
||||||
https://core.telegram.org/bots/api#inputmediaphoto
|
https://core.telegram.org/bots/api#inputmediaphoto
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, media: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None,
|
def __init__(
|
||||||
caption: base.String = None, parse_mode: base.Boolean = None, **kwargs):
|
self,
|
||||||
super(InputMediaPhoto, self).__init__(type='photo', media=media, thumb=thumb,
|
media: base.InputFile,
|
||||||
caption=caption, parse_mode=parse_mode,
|
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||||
conf=kwargs)
|
caption: base.String = None,
|
||||||
|
parse_mode: base.Boolean = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super(InputMediaPhoto, self).__init__(
|
||||||
|
type="photo",
|
||||||
|
media=media,
|
||||||
|
thumb=thumb,
|
||||||
|
caption=caption,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
conf=kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InputMediaVideo(InputMedia):
|
class InputMediaVideo(InputMedia):
|
||||||
|
|
@ -177,21 +230,36 @@ class InputMediaVideo(InputMedia):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#inputmediavideo
|
https://core.telegram.org/bots/api#inputmediavideo
|
||||||
"""
|
"""
|
||||||
|
|
||||||
width: base.Integer = fields.Field()
|
width: base.Integer = fields.Field()
|
||||||
height: base.Integer = fields.Field()
|
height: base.Integer = fields.Field()
|
||||||
duration: base.Integer = fields.Field()
|
duration: base.Integer = fields.Field()
|
||||||
supports_streaming: base.Boolean = fields.Field()
|
supports_streaming: base.Boolean = fields.Field()
|
||||||
|
|
||||||
def __init__(self, media: base.InputFile,
|
def __init__(
|
||||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
self,
|
||||||
caption: base.String = None,
|
media: base.InputFile,
|
||||||
width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None,
|
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||||
parse_mode: base.Boolean = None,
|
caption: base.String = None,
|
||||||
supports_streaming: base.Boolean = None, **kwargs):
|
width: base.Integer = None,
|
||||||
super(InputMediaVideo, self).__init__(type='video', media=media, thumb=thumb, caption=caption,
|
height: base.Integer = None,
|
||||||
width=width, height=height, duration=duration,
|
duration: base.Integer = None,
|
||||||
parse_mode=parse_mode,
|
parse_mode: base.Boolean = None,
|
||||||
supports_streaming=supports_streaming, conf=kwargs)
|
supports_streaming: base.Boolean = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super(InputMediaVideo, self).__init__(
|
||||||
|
type="video",
|
||||||
|
media=media,
|
||||||
|
thumb=thumb,
|
||||||
|
caption=caption,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
duration=duration,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
supports_streaming=supports_streaming,
|
||||||
|
conf=kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MediaGroup(base.TelegramObject):
|
class MediaGroup(base.TelegramObject):
|
||||||
|
|
@ -199,7 +267,9 @@ class MediaGroup(base.TelegramObject):
|
||||||
Helper for sending media group
|
Helper for sending media group
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, medias: typing.Optional[typing.List[typing.Union[InputMedia, typing.Dict]]] = None):
|
def __init__(
|
||||||
|
self, medias: typing.Optional[typing.List[typing.Union[InputMedia, typing.Dict]]] = None
|
||||||
|
):
|
||||||
super(MediaGroup, self).__init__()
|
super(MediaGroup, self).__init__()
|
||||||
self.media = []
|
self.media = []
|
||||||
|
|
||||||
|
|
@ -222,13 +292,13 @@ class MediaGroup(base.TelegramObject):
|
||||||
:param media:
|
:param media:
|
||||||
"""
|
"""
|
||||||
if isinstance(media, dict):
|
if isinstance(media, dict):
|
||||||
if 'type' not in media:
|
if "type" not in media:
|
||||||
raise ValueError(f"Invalid media!")
|
raise ValueError(f"Invalid media!")
|
||||||
|
|
||||||
media_type = media['type']
|
media_type = media["type"]
|
||||||
if media_type == 'photo':
|
if media_type == "photo":
|
||||||
media = InputMediaPhoto(**media)
|
media = InputMediaPhoto(**media)
|
||||||
elif media_type == 'video':
|
elif media_type == "video":
|
||||||
media = InputMediaVideo(**media)
|
media = InputMediaVideo(**media)
|
||||||
# elif media_type == 'document':
|
# elif media_type == 'document':
|
||||||
# media = InputMediaDocument(**media)
|
# media = InputMediaDocument(**media)
|
||||||
|
|
@ -240,9 +310,11 @@ class MediaGroup(base.TelegramObject):
|
||||||
raise TypeError(f"Invalid media type '{media_type}'!")
|
raise TypeError(f"Invalid media type '{media_type}'!")
|
||||||
|
|
||||||
elif not isinstance(media, InputMedia):
|
elif not isinstance(media, InputMedia):
|
||||||
raise TypeError(f"Media must be an instance of InputMedia or dict, not {type(media).__name__}")
|
raise TypeError(
|
||||||
|
f"Media must be an instance of InputMedia or dict, not {type(media).__name__}"
|
||||||
|
)
|
||||||
|
|
||||||
elif media.type in ['document', 'audio', 'animation']:
|
elif media.type in ["document", "audio", "animation"]:
|
||||||
raise ValueError(f"This type of media is not supported by media groups!")
|
raise ValueError(f"This type of media is not supported by media groups!")
|
||||||
|
|
||||||
self.media.append(media)
|
self.media.append(media)
|
||||||
|
|
@ -313,8 +385,9 @@ class MediaGroup(base.TelegramObject):
|
||||||
self.attach(document)
|
self.attach(document)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def attach_photo(self, photo: typing.Union[InputMediaPhoto, base.InputFile],
|
def attach_photo(
|
||||||
caption: base.String = None):
|
self, photo: typing.Union[InputMediaPhoto, base.InputFile], caption: base.String = None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Attach photo
|
Attach photo
|
||||||
|
|
||||||
|
|
@ -325,10 +398,15 @@ class MediaGroup(base.TelegramObject):
|
||||||
photo = InputMediaPhoto(media=photo, caption=caption)
|
photo = InputMediaPhoto(media=photo, caption=caption)
|
||||||
self.attach(photo)
|
self.attach(photo)
|
||||||
|
|
||||||
def attach_video(self, video: typing.Union[InputMediaVideo, base.InputFile],
|
def attach_video(
|
||||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
self,
|
||||||
caption: base.String = None,
|
video: typing.Union[InputMediaVideo, base.InputFile],
|
||||||
width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None):
|
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||||
|
caption: base.String = None,
|
||||||
|
width: base.Integer = None,
|
||||||
|
height: base.Integer = None,
|
||||||
|
duration: base.Integer = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Attach video
|
Attach video
|
||||||
|
|
||||||
|
|
@ -339,8 +417,14 @@ class MediaGroup(base.TelegramObject):
|
||||||
:param duration:
|
:param duration:
|
||||||
"""
|
"""
|
||||||
if not isinstance(video, InputMedia):
|
if not isinstance(video, InputMedia):
|
||||||
video = InputMediaVideo(media=video, thumb=thumb, caption=caption,
|
video = InputMediaVideo(
|
||||||
width=width, height=height, duration=duration)
|
media=video,
|
||||||
|
thumb=thumb,
|
||||||
|
caption=caption,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
duration=duration,
|
||||||
|
)
|
||||||
self.attach(video)
|
self.attach(video)
|
||||||
|
|
||||||
def to_python(self) -> typing.List:
|
def to_python(self) -> typing.List:
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ class InputMessageContent(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#inputmessagecontent
|
https://core.telegram.org/bots/api#inputmessagecontent
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -24,16 +25,21 @@ class InputContactMessageContent(InputMessageContent):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#inputcontactmessagecontent
|
https://core.telegram.org/bots/api#inputcontactmessagecontent
|
||||||
"""
|
"""
|
||||||
|
|
||||||
phone_number: base.String = fields.Field()
|
phone_number: base.String = fields.Field()
|
||||||
first_name: base.String = fields.Field()
|
first_name: base.String = fields.Field()
|
||||||
last_name: base.String = fields.Field()
|
last_name: base.String = fields.Field()
|
||||||
vcard: base.String = fields.Field()
|
vcard: base.String = fields.Field()
|
||||||
|
|
||||||
def __init__(self, phone_number: base.String,
|
def __init__(
|
||||||
first_name: typing.Optional[base.String] = None,
|
self,
|
||||||
last_name: typing.Optional[base.String] = None):
|
phone_number: base.String,
|
||||||
super(InputContactMessageContent, self).__init__(phone_number=phone_number, first_name=first_name,
|
first_name: typing.Optional[base.String] = None,
|
||||||
last_name=last_name)
|
last_name: typing.Optional[base.String] = None,
|
||||||
|
):
|
||||||
|
super(InputContactMessageContent, self).__init__(
|
||||||
|
phone_number=phone_number, first_name=first_name, last_name=last_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InputLocationMessageContent(InputMessageContent):
|
class InputLocationMessageContent(InputMessageContent):
|
||||||
|
|
@ -45,11 +51,11 @@ class InputLocationMessageContent(InputMessageContent):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#inputlocationmessagecontent
|
https://core.telegram.org/bots/api#inputlocationmessagecontent
|
||||||
"""
|
"""
|
||||||
|
|
||||||
latitude: base.Float = fields.Field()
|
latitude: base.Float = fields.Field()
|
||||||
longitude: base.Float = fields.Field()
|
longitude: base.Float = fields.Field()
|
||||||
|
|
||||||
def __init__(self, latitude: base.Float,
|
def __init__(self, latitude: base.Float, longitude: base.Float):
|
||||||
longitude: base.Float):
|
|
||||||
super(InputLocationMessageContent, self).__init__(latitude=latitude, longitude=longitude)
|
super(InputLocationMessageContent, self).__init__(latitude=latitude, longitude=longitude)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -59,6 +65,7 @@ class InputTextMessageContent(InputMessageContent):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#inputtextmessagecontent
|
https://core.telegram.org/bots/api#inputtextmessagecontent
|
||||||
"""
|
"""
|
||||||
|
|
||||||
message_text: base.String = fields.Field()
|
message_text: base.String = fields.Field()
|
||||||
parse_mode: base.String = fields.Field()
|
parse_mode: base.String = fields.Field()
|
||||||
disable_web_page_preview: base.Boolean = fields.Field()
|
disable_web_page_preview: base.Boolean = fields.Field()
|
||||||
|
|
@ -69,14 +76,20 @@ class InputTextMessageContent(InputMessageContent):
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, message_text: typing.Optional[base.String] = None,
|
def __init__(
|
||||||
parse_mode: typing.Optional[base.String] = None,
|
self,
|
||||||
disable_web_page_preview: typing.Optional[base.Boolean] = None):
|
message_text: typing.Optional[base.String] = None,
|
||||||
|
parse_mode: typing.Optional[base.String] = None,
|
||||||
|
disable_web_page_preview: typing.Optional[base.Boolean] = None,
|
||||||
|
):
|
||||||
if parse_mode is None:
|
if parse_mode is None:
|
||||||
parse_mode = self.safe_get_parse_mode()
|
parse_mode = self.safe_get_parse_mode()
|
||||||
|
|
||||||
super(InputTextMessageContent, self).__init__(message_text=message_text, parse_mode=parse_mode,
|
super(InputTextMessageContent, self).__init__(
|
||||||
disable_web_page_preview=disable_web_page_preview)
|
message_text=message_text,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
disable_web_page_preview=disable_web_page_preview,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InputVenueMessageContent(InputMessageContent):
|
class InputVenueMessageContent(InputMessageContent):
|
||||||
|
|
@ -88,16 +101,25 @@ class InputVenueMessageContent(InputMessageContent):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#inputvenuemessagecontent
|
https://core.telegram.org/bots/api#inputvenuemessagecontent
|
||||||
"""
|
"""
|
||||||
|
|
||||||
latitude: base.Float = fields.Field()
|
latitude: base.Float = fields.Field()
|
||||||
longitude: base.Float = fields.Field()
|
longitude: base.Float = fields.Field()
|
||||||
title: base.String = fields.Field()
|
title: base.String = fields.Field()
|
||||||
address: base.String = fields.Field()
|
address: base.String = fields.Field()
|
||||||
foursquare_id: base.String = fields.Field()
|
foursquare_id: base.String = fields.Field()
|
||||||
|
|
||||||
def __init__(self, latitude: typing.Optional[base.Float] = None,
|
def __init__(
|
||||||
longitude: typing.Optional[base.Float] = None,
|
self,
|
||||||
title: typing.Optional[base.String] = None,
|
latitude: typing.Optional[base.Float] = None,
|
||||||
address: typing.Optional[base.String] = None,
|
longitude: typing.Optional[base.Float] = None,
|
||||||
foursquare_id: typing.Optional[base.String] = None):
|
title: typing.Optional[base.String] = None,
|
||||||
super(InputVenueMessageContent, self).__init__(latitude=latitude, longitude=longitude, title=title,
|
address: typing.Optional[base.String] = None,
|
||||||
address=address, foursquare_id=foursquare_id)
|
foursquare_id: typing.Optional[base.String] = None,
|
||||||
|
):
|
||||||
|
super(InputVenueMessageContent, self).__init__(
|
||||||
|
latitude=latitude,
|
||||||
|
longitude=longitude,
|
||||||
|
title=title,
|
||||||
|
address=address,
|
||||||
|
foursquare_id=foursquare_id,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ class Invoice(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#invoice
|
https://core.telegram.org/bots/api#invoice
|
||||||
"""
|
"""
|
||||||
|
|
||||||
title: base.String = fields.Field()
|
title: base.String = fields.Field()
|
||||||
description: base.String = fields.Field()
|
description: base.String = fields.Field()
|
||||||
start_parameter: base.String = fields.Field()
|
start_parameter: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ class LabeledPrice(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#labeledprice
|
https://core.telegram.org/bots/api#labeledprice
|
||||||
"""
|
"""
|
||||||
|
|
||||||
label: base.String = fields.Field()
|
label: base.String = fields.Field()
|
||||||
amount: base.Integer = fields.Field()
|
amount: base.Integer = fields.Field()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,6 @@ class Location(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#location
|
https://core.telegram.org/bots/api#location
|
||||||
"""
|
"""
|
||||||
|
|
||||||
longitude: base.Float = fields.Field()
|
longitude: base.Float = fields.Field()
|
||||||
latitude: base.Float = fields.Field()
|
latitude: base.Float = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,24 @@ class LoginUrl(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#loginurl
|
https://core.telegram.org/bots/api#loginurl
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url: base.String = fields.Field()
|
url: base.String = fields.Field()
|
||||||
forward_text: base.String = fields.Field()
|
forward_text: base.String = fields.Field()
|
||||||
bot_username: base.String = fields.Field()
|
bot_username: base.String = fields.Field()
|
||||||
request_write_access: base.Boolean = fields.Field()
|
request_write_access: base.Boolean = fields.Field()
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(
|
||||||
url: base.String,
|
self,
|
||||||
forward_text: base.String = None,
|
url: base.String,
|
||||||
bot_username: base.String = None,
|
forward_text: base.String = None,
|
||||||
request_write_access: base.Boolean = None,
|
bot_username: base.String = None,
|
||||||
**kwargs):
|
request_write_access: base.Boolean = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
super(LoginUrl, self).__init__(
|
super(LoginUrl, self).__init__(
|
||||||
url=url,
|
url=url,
|
||||||
forward_text=forward_text,
|
forward_text=forward_text,
|
||||||
bot_username=bot_username,
|
bot_username=bot_username,
|
||||||
request_write_access=request_write_access,
|
request_write_access=request_write_access,
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ class MaskPosition(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#maskposition
|
https://core.telegram.org/bots/api#maskposition
|
||||||
"""
|
"""
|
||||||
|
|
||||||
point: base.String = fields.Field()
|
point: base.String = fields.Field()
|
||||||
x_shift: base.Float = fields.Field()
|
x_shift: base.Float = fields.Field()
|
||||||
y_shift: base.Float = fields.Field()
|
y_shift: base.Float = fields.Field()
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -12,6 +12,7 @@ class MessageEntity(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#messageentity
|
https://core.telegram.org/bots/api#messageentity
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: base.String = fields.Field()
|
type: base.String = fields.Field()
|
||||||
offset: base.Integer = fields.Field()
|
offset: base.Integer = fields.Field()
|
||||||
length: base.Integer = fields.Field()
|
length: base.Integer = fields.Field()
|
||||||
|
|
@ -25,16 +26,16 @@ class MessageEntity(base.TelegramObject):
|
||||||
:param text: full text
|
:param text: full text
|
||||||
:return: part of text
|
:return: part of text
|
||||||
"""
|
"""
|
||||||
if sys.maxunicode == 0xffff:
|
if sys.maxunicode == 0xFFFF:
|
||||||
return text[self.offset:self.offset + self.length]
|
return text[self.offset : self.offset + self.length]
|
||||||
|
|
||||||
if not isinstance(text, bytes):
|
if not isinstance(text, bytes):
|
||||||
entity_text = text.encode('utf-16-le')
|
entity_text = text.encode("utf-16-le")
|
||||||
else:
|
else:
|
||||||
entity_text = text
|
entity_text = text
|
||||||
|
|
||||||
entity_text = entity_text[self.offset * 2:(self.offset + self.length) * 2]
|
entity_text = entity_text[self.offset * 2 : (self.offset + self.length) * 2]
|
||||||
return entity_text.decode('utf-16-le')
|
return entity_text.decode("utf-16-le")
|
||||||
|
|
||||||
def parse(self, text, as_html=True):
|
def parse(self, text, as_html=True):
|
||||||
"""
|
"""
|
||||||
|
|
@ -95,6 +96,7 @@ class MessageEntityType(helper.Helper):
|
||||||
:key: TEXT_LINK
|
:key: TEXT_LINK
|
||||||
:key: TEXT_MENTION
|
:key: TEXT_MENTION
|
||||||
"""
|
"""
|
||||||
|
|
||||||
mode = helper.HelperMode.snake_case
|
mode = helper.HelperMode.snake_case
|
||||||
|
|
||||||
MENTION = helper.Item() # mention - @username
|
MENTION = helper.Item() # mention - @username
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ class Downloadable:
|
||||||
Mixin for files
|
Mixin for files
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def download(self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True):
|
async def download(
|
||||||
|
self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Download file
|
Download file
|
||||||
|
|
||||||
|
|
@ -31,8 +33,13 @@ class Downloadable:
|
||||||
if is_path and make_dirs:
|
if is_path and make_dirs:
|
||||||
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
||||||
|
|
||||||
return await self.bot.download_file(file_path=file.file_path, destination=destination, timeout=timeout,
|
return await self.bot.download_file(
|
||||||
chunk_size=chunk_size, seek=seek)
|
file_path=file.file_path,
|
||||||
|
destination=destination,
|
||||||
|
timeout=timeout,
|
||||||
|
chunk_size=chunk_size,
|
||||||
|
seek=seek,
|
||||||
|
)
|
||||||
|
|
||||||
async def get_file(self):
|
async def get_file(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -40,7 +47,7 @@ class Downloadable:
|
||||||
|
|
||||||
:return: :obj:`aiogram.types.File`
|
:return: :obj:`aiogram.types.File`
|
||||||
"""
|
"""
|
||||||
if hasattr(self, 'file_path'):
|
if hasattr(self, "file_path"):
|
||||||
return self
|
return self
|
||||||
else:
|
else:
|
||||||
return await self.bot.get_file(self.file_id)
|
return await self.bot.get_file(self.file_id)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class OrderInfo(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#orderinfo
|
https://core.telegram.org/bots/api#orderinfo
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: base.String = fields.Field()
|
name: base.String = fields.Field()
|
||||||
phone_number: base.String = fields.Field()
|
phone_number: base.String = fields.Field()
|
||||||
email: base.String = fields.Field()
|
email: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,17 @@ class PassportElementErrorDataField(PassportElementError):
|
||||||
field_name: base.String = fields.Field()
|
field_name: base.String = fields.Field()
|
||||||
data_hash: base.String = fields.Field()
|
data_hash: base.String = fields.Field()
|
||||||
|
|
||||||
def __init__(self, source: base.String, type: base.String, field_name: base.String,
|
def __init__(
|
||||||
data_hash: base.String, message: base.String):
|
self,
|
||||||
super(PassportElementErrorDataField, self).__init__(source=source, type=type, field_name=field_name,
|
source: base.String,
|
||||||
data_hash=data_hash, message=message)
|
type: base.String,
|
||||||
|
field_name: base.String,
|
||||||
|
data_hash: base.String,
|
||||||
|
message: base.String,
|
||||||
|
):
|
||||||
|
super(PassportElementErrorDataField, self).__init__(
|
||||||
|
source=source, type=type, field_name=field_name, data_hash=data_hash, message=message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PassportElementErrorFile(PassportElementError):
|
class PassportElementErrorFile(PassportElementError):
|
||||||
|
|
@ -44,9 +51,12 @@ class PassportElementErrorFile(PassportElementError):
|
||||||
|
|
||||||
file_hash: base.String = fields.Field()
|
file_hash: base.String = fields.Field()
|
||||||
|
|
||||||
def __init__(self, source: base.String, type: base.String, file_hash: base.String, message: base.String):
|
def __init__(
|
||||||
super(PassportElementErrorFile, self).__init__(source=source, type=type, file_hash=file_hash,
|
self, source: base.String, type: base.String, file_hash: base.String, message: base.String
|
||||||
message=message)
|
):
|
||||||
|
super(PassportElementErrorFile, self).__init__(
|
||||||
|
source=source, type=type, file_hash=file_hash, message=message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PassportElementErrorFiles(PassportElementError):
|
class PassportElementErrorFiles(PassportElementError):
|
||||||
|
|
@ -59,10 +69,16 @@ class PassportElementErrorFiles(PassportElementError):
|
||||||
|
|
||||||
file_hashes: typing.List[base.String] = fields.ListField()
|
file_hashes: typing.List[base.String] = fields.ListField()
|
||||||
|
|
||||||
def __init__(self, source: base.String, type: base.String, file_hashes: typing.List[base.String],
|
def __init__(
|
||||||
message: base.String):
|
self,
|
||||||
super(PassportElementErrorFiles, self).__init__(source=source, type=type, file_hashes=file_hashes,
|
source: base.String,
|
||||||
message=message)
|
type: base.String,
|
||||||
|
file_hashes: typing.List[base.String],
|
||||||
|
message: base.String,
|
||||||
|
):
|
||||||
|
super(PassportElementErrorFiles, self).__init__(
|
||||||
|
source=source, type=type, file_hashes=file_hashes, message=message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PassportElementErrorFrontSide(PassportElementError):
|
class PassportElementErrorFrontSide(PassportElementError):
|
||||||
|
|
@ -75,9 +91,12 @@ class PassportElementErrorFrontSide(PassportElementError):
|
||||||
|
|
||||||
file_hash: base.String = fields.Field()
|
file_hash: base.String = fields.Field()
|
||||||
|
|
||||||
def __init__(self, source: base.String, type: base.String, file_hash: base.String, message: base.String):
|
def __init__(
|
||||||
super(PassportElementErrorFrontSide, self).__init__(source=source, type=type, file_hash=file_hash,
|
self, source: base.String, type: base.String, file_hash: base.String, message: base.String
|
||||||
message=message)
|
):
|
||||||
|
super(PassportElementErrorFrontSide, self).__init__(
|
||||||
|
source=source, type=type, file_hash=file_hash, message=message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PassportElementErrorReverseSide(PassportElementError):
|
class PassportElementErrorReverseSide(PassportElementError):
|
||||||
|
|
@ -90,9 +109,12 @@ class PassportElementErrorReverseSide(PassportElementError):
|
||||||
|
|
||||||
file_hash: base.String = fields.Field()
|
file_hash: base.String = fields.Field()
|
||||||
|
|
||||||
def __init__(self, source: base.String, type: base.String, file_hash: base.String, message: base.String):
|
def __init__(
|
||||||
super(PassportElementErrorReverseSide, self).__init__(source=source, type=type, file_hash=file_hash,
|
self, source: base.String, type: base.String, file_hash: base.String, message: base.String
|
||||||
message=message)
|
):
|
||||||
|
super(PassportElementErrorReverseSide, self).__init__(
|
||||||
|
source=source, type=type, file_hash=file_hash, message=message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PassportElementErrorSelfie(PassportElementError):
|
class PassportElementErrorSelfie(PassportElementError):
|
||||||
|
|
@ -105,6 +127,9 @@ class PassportElementErrorSelfie(PassportElementError):
|
||||||
|
|
||||||
file_hash: base.String = fields.Field()
|
file_hash: base.String = fields.Field()
|
||||||
|
|
||||||
def __init__(self, source: base.String, type: base.String, file_hash: base.String, message: base.String):
|
def __init__(
|
||||||
super(PassportElementErrorSelfie, self).__init__(source=source, type=type, file_hash=file_hash,
|
self, source: base.String, type: base.String, file_hash: base.String, message: base.String
|
||||||
message=message)
|
):
|
||||||
|
super(PassportElementErrorSelfie, self).__init__(
|
||||||
|
source=source, type=type, file_hash=file_hash, message=message
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class PhotoSize(base.TelegramObject, mixins.Downloadable):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#photosize
|
https://core.telegram.org/bots/api#photosize
|
||||||
"""
|
"""
|
||||||
|
|
||||||
file_id: base.String = fields.Field()
|
file_id: base.String = fields.Field()
|
||||||
width: base.Integer = fields.Field()
|
width: base.Integer = fields.Field()
|
||||||
height: base.Integer = fields.Field()
|
height: base.Integer = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ class PreCheckoutQuery(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#precheckoutquery
|
https://core.telegram.org/bots/api#precheckoutquery
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: base.String = fields.Field()
|
id: base.String = fields.Field()
|
||||||
from_user: User = fields.Field(alias='from', base=User)
|
from_user: User = fields.Field(alias="from", base=User)
|
||||||
currency: base.String = fields.Field()
|
currency: base.String = fields.Field()
|
||||||
total_amount: base.Integer = fields.Field()
|
total_amount: base.Integer = fields.Field()
|
||||||
invoice_payload: base.String = fields.Field()
|
invoice_payload: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -10,27 +10,37 @@ class ReplyKeyboardMarkup(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#replykeyboardmarkup
|
https://core.telegram.org/bots/api#replykeyboardmarkup
|
||||||
"""
|
"""
|
||||||
keyboard: 'typing.List[typing.List[KeyboardButton]]' = fields.ListOfLists(base='KeyboardButton', default=[])
|
|
||||||
|
keyboard: "typing.List[typing.List[KeyboardButton]]" = fields.ListOfLists(
|
||||||
|
base="KeyboardButton", default=[]
|
||||||
|
)
|
||||||
resize_keyboard: base.Boolean = fields.Field()
|
resize_keyboard: base.Boolean = fields.Field()
|
||||||
one_time_keyboard: base.Boolean = fields.Field()
|
one_time_keyboard: base.Boolean = fields.Field()
|
||||||
selective: base.Boolean = fields.Field()
|
selective: base.Boolean = fields.Field()
|
||||||
|
|
||||||
def __init__(self, keyboard: 'typing.List[typing.List[KeyboardButton]]' = None,
|
def __init__(
|
||||||
resize_keyboard: base.Boolean = None,
|
self,
|
||||||
one_time_keyboard: base.Boolean = None,
|
keyboard: "typing.List[typing.List[KeyboardButton]]" = None,
|
||||||
selective: base.Boolean = None,
|
resize_keyboard: base.Boolean = None,
|
||||||
row_width: base.Integer = 3):
|
one_time_keyboard: base.Boolean = None,
|
||||||
super(ReplyKeyboardMarkup, self).__init__(keyboard=keyboard, resize_keyboard=resize_keyboard,
|
selective: base.Boolean = None,
|
||||||
one_time_keyboard=one_time_keyboard, selective=selective,
|
row_width: base.Integer = 3,
|
||||||
conf={'row_width': row_width})
|
):
|
||||||
|
super(ReplyKeyboardMarkup, self).__init__(
|
||||||
|
keyboard=keyboard,
|
||||||
|
resize_keyboard=resize_keyboard,
|
||||||
|
one_time_keyboard=one_time_keyboard,
|
||||||
|
selective=selective,
|
||||||
|
conf={"row_width": row_width},
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def row_width(self):
|
def row_width(self):
|
||||||
return self.conf.get('row_width', 3)
|
return self.conf.get("row_width", 3)
|
||||||
|
|
||||||
@row_width.setter
|
@row_width.setter
|
||||||
def row_width(self, value):
|
def row_width(self, value):
|
||||||
self.conf['row_width'] = value
|
self.conf["row_width"] = value
|
||||||
|
|
||||||
def add(self, *args):
|
def add(self, *args):
|
||||||
"""
|
"""
|
||||||
|
|
@ -86,16 +96,20 @@ class KeyboardButton(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#keyboardbutton
|
https://core.telegram.org/bots/api#keyboardbutton
|
||||||
"""
|
"""
|
||||||
|
|
||||||
text: base.String = fields.Field()
|
text: base.String = fields.Field()
|
||||||
request_contact: base.Boolean = fields.Field()
|
request_contact: base.Boolean = fields.Field()
|
||||||
request_location: base.Boolean = fields.Field()
|
request_location: base.Boolean = fields.Field()
|
||||||
|
|
||||||
def __init__(self, text: base.String,
|
def __init__(
|
||||||
request_contact: base.Boolean = None,
|
self,
|
||||||
request_location: base.Boolean = None):
|
text: base.String,
|
||||||
super(KeyboardButton, self).__init__(text=text,
|
request_contact: base.Boolean = None,
|
||||||
request_contact=request_contact,
|
request_location: base.Boolean = None,
|
||||||
request_location=request_location)
|
):
|
||||||
|
super(KeyboardButton, self).__init__(
|
||||||
|
text=text, request_contact=request_contact, request_location=request_location
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReplyKeyboardRemove(base.TelegramObject):
|
class ReplyKeyboardRemove(base.TelegramObject):
|
||||||
|
|
@ -104,6 +118,7 @@ class ReplyKeyboardRemove(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#replykeyboardremove
|
https://core.telegram.org/bots/api#replykeyboardremove
|
||||||
"""
|
"""
|
||||||
|
|
||||||
remove_keyboard: base.Boolean = fields.Field(default=True)
|
remove_keyboard: base.Boolean = fields.Field(default=True)
|
||||||
selective: base.Boolean = fields.Field()
|
selective: base.Boolean = fields.Field()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,6 @@ class ResponseParameters(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#responseparameters
|
https://core.telegram.org/bots/api#responseparameters
|
||||||
"""
|
"""
|
||||||
|
|
||||||
migrate_to_chat_id: base.Integer = fields.Field()
|
migrate_to_chat_id: base.Integer = fields.Field()
|
||||||
retry_after: base.Integer = fields.Field()
|
retry_after: base.Integer = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ class ShippingAddress(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#shippingaddress
|
https://core.telegram.org/bots/api#shippingaddress
|
||||||
"""
|
"""
|
||||||
|
|
||||||
country_code: base.String = fields.Field()
|
country_code: base.String = fields.Field()
|
||||||
state: base.String = fields.Field()
|
state: base.String = fields.Field()
|
||||||
city: base.String = fields.Field()
|
city: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,14 @@ class ShippingOption(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#shippingoption
|
https://core.telegram.org/bots/api#shippingoption
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: base.String = fields.Field()
|
id: base.String = fields.Field()
|
||||||
title: base.String = fields.Field()
|
title: base.String = fields.Field()
|
||||||
prices: typing.List[LabeledPrice] = fields.ListField(base=LabeledPrice)
|
prices: typing.List[LabeledPrice] = fields.ListField(base=LabeledPrice)
|
||||||
|
|
||||||
def __init__(self, id: base.String, title: base.String, prices: typing.List[LabeledPrice] = None):
|
def __init__(
|
||||||
|
self, id: base.String, title: base.String, prices: typing.List[LabeledPrice] = None
|
||||||
|
):
|
||||||
if prices is None:
|
if prices is None:
|
||||||
prices = []
|
prices = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ class ShippingQuery(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#shippingquery
|
https://core.telegram.org/bots/api#shippingquery
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: base.String = fields.Field()
|
id: base.String = fields.Field()
|
||||||
from_user: User = fields.Field(alias='from', base=User)
|
from_user: User = fields.Field(alias="from", base=User)
|
||||||
invoice_payload: base.String = fields.Field()
|
invoice_payload: base.String = fields.Field()
|
||||||
shipping_address: ShippingAddress = fields.Field(base=ShippingAddress)
|
shipping_address: ShippingAddress = fields.Field(base=ShippingAddress)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ class Sticker(base.TelegramObject, mixins.Downloadable):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#sticker
|
https://core.telegram.org/bots/api#sticker
|
||||||
"""
|
"""
|
||||||
|
|
||||||
file_id: base.String = fields.Field()
|
file_id: base.String = fields.Field()
|
||||||
width: base.Integer = fields.Field()
|
width: base.Integer = fields.Field()
|
||||||
height: base.Integer = fields.Field()
|
height: base.Integer = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ class StickerSet(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#stickerset
|
https://core.telegram.org/bots/api#stickerset
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: base.String = fields.Field()
|
name: base.String = fields.Field()
|
||||||
title: base.String = fields.Field()
|
title: base.String = fields.Field()
|
||||||
contains_masks: base.Boolean = fields.Field()
|
contains_masks: base.Boolean = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class SuccessfulPayment(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#successfulpayment
|
https://core.telegram.org/bots/api#successfulpayment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
currency: base.String = fields.Field()
|
currency: base.String = fields.Field()
|
||||||
total_amount: base.Integer = fields.Field()
|
total_amount: base.Integer = fields.Field()
|
||||||
invoice_payload: base.String = fields.Field()
|
invoice_payload: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class Update(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#update
|
https://core.telegram.org/bots/api#update
|
||||||
"""
|
"""
|
||||||
|
|
||||||
update_id: base.Integer = fields.Field()
|
update_id: base.Integer = fields.Field()
|
||||||
message: Message = fields.Field(base=Message)
|
message: Message = fields.Field(base=Message)
|
||||||
edited_message: Message = fields.Field(base=Message)
|
edited_message: Message = fields.Field(base=Message)
|
||||||
|
|
@ -47,6 +48,7 @@ class AllowedUpdates(helper.Helper):
|
||||||
Example:
|
Example:
|
||||||
>>> bot.get_updates(allowed_updates=AllowedUpdates.MESSAGE + AllowedUpdates.EDITED_MESSAGE)
|
>>> bot.get_updates(allowed_updates=AllowedUpdates.MESSAGE + AllowedUpdates.EDITED_MESSAGE)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
mode = helper.HelperMode.snake_case
|
mode = helper.HelperMode.snake_case
|
||||||
|
|
||||||
MESSAGE = helper.ListItem() # message
|
MESSAGE = helper.ListItem() # message
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ class User(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#user
|
https://core.telegram.org/bots/api#user
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: base.Integer = fields.Field()
|
id: base.Integer = fields.Field()
|
||||||
is_bot: base.Boolean = fields.Field()
|
is_bot: base.Boolean = fields.Field()
|
||||||
first_name: base.String = fields.Field()
|
first_name: base.String = fields.Field()
|
||||||
|
|
@ -29,7 +30,7 @@ class User(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
full_name = self.first_name
|
full_name = self.first_name
|
||||||
if self.last_name:
|
if self.last_name:
|
||||||
full_name += ' ' + self.last_name
|
full_name += " " + self.last_name
|
||||||
return full_name
|
return full_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -41,7 +42,7 @@ class User(base.TelegramObject):
|
||||||
:return: str
|
:return: str
|
||||||
"""
|
"""
|
||||||
if self.username:
|
if self.username:
|
||||||
return '@' + self.username
|
return "@" + self.username
|
||||||
return self.full_name
|
return self.full_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -53,16 +54,16 @@ class User(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
if not self.language_code:
|
if not self.language_code:
|
||||||
return None
|
return None
|
||||||
if not hasattr(self, '_locale'):
|
if not hasattr(self, "_locale"):
|
||||||
setattr(self, '_locale', babel.core.Locale.parse(self.language_code, sep='-'))
|
setattr(self, "_locale", babel.core.Locale.parse(self.language_code, sep="-"))
|
||||||
return getattr(self, '_locale')
|
return getattr(self, "_locale")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
return f"tg://user?id={self.id}"
|
return f"tg://user?id={self.id}"
|
||||||
|
|
||||||
def get_mention(self, name=None, as_html=None):
|
def get_mention(self, name=None, as_html=None):
|
||||||
if as_html is None and self.bot.parse_mode and self.bot.parse_mode.lower() == 'html':
|
if as_html is None and self.bot.parse_mode and self.bot.parse_mode.lower() == "html":
|
||||||
as_html = True
|
as_html = True
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,6 @@ class UserProfilePhotos(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#userprofilephotos
|
https://core.telegram.org/bots/api#userprofilephotos
|
||||||
"""
|
"""
|
||||||
|
|
||||||
total_count: base.Integer = fields.Field()
|
total_count: base.Integer = fields.Field()
|
||||||
photos: typing.List[typing.List[PhotoSize]] = fields.ListOfLists(base=PhotoSize)
|
photos: typing.List[typing.List[PhotoSize]] = fields.ListOfLists(base=PhotoSize)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class Venue(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#venue
|
https://core.telegram.org/bots/api#venue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
location: Location = fields.Field(base=Location)
|
location: Location = fields.Field(base=Location)
|
||||||
title: base.String = fields.Field()
|
title: base.String = fields.Field()
|
||||||
address: base.String = fields.Field()
|
address: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class Video(base.TelegramObject, mixins.Downloadable):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#video
|
https://core.telegram.org/bots/api#video
|
||||||
"""
|
"""
|
||||||
|
|
||||||
file_id: base.String = fields.Field()
|
file_id: base.String = fields.Field()
|
||||||
width: base.Integer = fields.Field()
|
width: base.Integer = fields.Field()
|
||||||
height: base.Integer = fields.Field()
|
height: base.Integer = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class VideoNote(base.TelegramObject, mixins.Downloadable):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#videonote
|
https://core.telegram.org/bots/api#videonote
|
||||||
"""
|
"""
|
||||||
|
|
||||||
file_id: base.String = fields.Field()
|
file_id: base.String = fields.Field()
|
||||||
length: base.Integer = fields.Field()
|
length: base.Integer = fields.Field()
|
||||||
duration: base.Integer = fields.Field()
|
duration: base.Integer = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class Voice(base.TelegramObject, mixins.Downloadable):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#voice
|
https://core.telegram.org/bots/api#voice
|
||||||
"""
|
"""
|
||||||
|
|
||||||
file_id: base.String = fields.Field()
|
file_id: base.String = fields.Field()
|
||||||
duration: base.Integer = fields.Field()
|
duration: base.Integer = fields.Field()
|
||||||
mime_type: base.String = fields.Field()
|
mime_type: base.String = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class WebhookInfo(base.TelegramObject):
|
||||||
|
|
||||||
https://core.telegram.org/bots/api#webhookinfo
|
https://core.telegram.org/bots/api#webhookinfo
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url: base.String = fields.Field()
|
url: base.String = fields.Field()
|
||||||
has_custom_certificate: base.Boolean = fields.Field()
|
has_custom_certificate: base.Boolean = fields.Field()
|
||||||
pending_update_count: base.Integer = fields.Field()
|
pending_update_count: base.Integer = fields.Field()
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ def generate_hash(data: dict, token: str) -> str:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
secret = hashlib.sha256()
|
secret = hashlib.sha256()
|
||||||
secret.update(token.encode('utf-8'))
|
secret.update(token.encode("utf-8"))
|
||||||
sorted_params = collections.OrderedDict(sorted(data.items()))
|
sorted_params = collections.OrderedDict(sorted(data.items()))
|
||||||
msg = '\n'.join("{}={}".format(k, v) for k, v in sorted_params.items() if k != 'hash')
|
msg = "\n".join("{}={}".format(k, v) for k, v in sorted_params.items() if k != "hash")
|
||||||
return hmac.new(secret.digest(), msg.encode('utf-8'), digestmod=hashlib.sha256).hexdigest()
|
return hmac.new(secret.digest(), msg.encode("utf-8"), digestmod=hashlib.sha256).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def check_token(data: dict, token: str) -> bool:
|
def check_token(data: dict, token: str) -> bool:
|
||||||
|
|
@ -32,5 +32,5 @@ def check_token(data: dict, token: str) -> bool:
|
||||||
:param token:
|
:param token:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
param_hash = data.get('hash', '') or ''
|
param_hash = data.get("hash", "") or ""
|
||||||
return param_hash == generate_hash(data, token)
|
return param_hash == generate_hash(data, token)
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,15 @@ class CallbackData:
|
||||||
Callback data factory
|
Callback data factory
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, prefix, *parts, sep=':'):
|
def __init__(self, prefix, *parts, sep=":"):
|
||||||
if not isinstance(prefix, str):
|
if not isinstance(prefix, str):
|
||||||
raise TypeError(f"Prefix must be instance of str not {type(prefix).__name__}")
|
raise TypeError(f"Prefix must be instance of str not {type(prefix).__name__}")
|
||||||
elif not prefix:
|
elif not prefix:
|
||||||
raise ValueError('Prefix can\'t be empty')
|
raise ValueError("Prefix can't be empty")
|
||||||
elif sep in prefix:
|
elif sep in prefix:
|
||||||
raise ValueError(f"Separator '{sep}' can't be used in prefix")
|
raise ValueError(f"Separator '{sep}' can't be used in prefix")
|
||||||
elif not parts:
|
elif not parts:
|
||||||
raise TypeError('Parts is not passed!')
|
raise TypeError("Parts is not passed!")
|
||||||
|
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.sep = sep
|
self.sep = sep
|
||||||
|
|
@ -72,11 +72,11 @@ class CallbackData:
|
||||||
data.append(value)
|
data.append(value)
|
||||||
|
|
||||||
if args or kwargs:
|
if args or kwargs:
|
||||||
raise TypeError('Too many arguments is passed!')
|
raise TypeError("Too many arguments is passed!")
|
||||||
|
|
||||||
callback_data = self.sep.join(data)
|
callback_data = self.sep.join(data)
|
||||||
if len(callback_data) > 64:
|
if len(callback_data) > 64:
|
||||||
raise ValueError('Resulted callback data is too long!')
|
raise ValueError("Resulted callback data is too long!")
|
||||||
|
|
||||||
return callback_data
|
return callback_data
|
||||||
|
|
||||||
|
|
@ -91,9 +91,9 @@ class CallbackData:
|
||||||
if prefix != self.prefix:
|
if prefix != self.prefix:
|
||||||
raise ValueError("Passed callback data can't be parsed with that prefix.")
|
raise ValueError("Passed callback data can't be parsed with that prefix.")
|
||||||
elif len(parts) != len(self._part_names):
|
elif len(parts) != len(self._part_names):
|
||||||
raise ValueError('Invalid parts count!')
|
raise ValueError("Invalid parts count!")
|
||||||
|
|
||||||
result = {'@': prefix}
|
result = {"@": prefix}
|
||||||
result.update(zip(self._part_names, parts))
|
result.update(zip(self._part_names, parts))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ class CallbackDataFilter(Filter):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, full_config: typing.Dict[str, typing.Any]):
|
def validate(cls, full_config: typing.Dict[str, typing.Any]):
|
||||||
raise ValueError('That filter can\'t be used in filters factory!')
|
raise ValueError("That filter can't be used in filters factory!")
|
||||||
|
|
||||||
async def check(self, query: types.CallbackQuery):
|
async def check(self, query: types.CallbackQuery):
|
||||||
try:
|
try:
|
||||||
|
|
@ -132,4 +132,4 @@ class CallbackDataFilter(Filter):
|
||||||
else:
|
else:
|
||||||
if value != data.get(key):
|
if value != data.get(key):
|
||||||
return False
|
return False
|
||||||
return {'callback_data': data}
|
return {"callback_data": data}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ def deprecated(reason):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
warn_deprecated(msg.format(name=func.__name__, reason=reason))
|
warn_deprecated(msg.format(name=func.__name__, reason=reason))
|
||||||
warnings.simplefilter('default', DeprecationWarning)
|
warnings.simplefilter("default", DeprecationWarning)
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
@ -70,6 +70,6 @@ def deprecated(reason):
|
||||||
|
|
||||||
|
|
||||||
def warn_deprecated(message, warning=DeprecationWarning, stacklevel=2):
|
def warn_deprecated(message, warning=DeprecationWarning, stacklevel=2):
|
||||||
warnings.simplefilter('always', warning)
|
warnings.simplefilter("always", warning)
|
||||||
warnings.warn(message, category=warning, stacklevel=stacklevel)
|
warnings.warn(message, category=warning, stacklevel=stacklevel)
|
||||||
warnings.simplefilter('default', warning)
|
warnings.simplefilter("default", warning)
|
||||||
|
|
|
||||||
|
|
@ -90,13 +90,13 @@ import time
|
||||||
|
|
||||||
# TODO: Use exceptions detector from `aiograph`.
|
# TODO: Use exceptions detector from `aiograph`.
|
||||||
|
|
||||||
_PREFIXES = ['error: ', '[error]: ', 'bad request: ', 'conflict: ', 'not found: ']
|
_PREFIXES = ["error: ", "[error]: ", "bad request: ", "conflict: ", "not found: "]
|
||||||
|
|
||||||
|
|
||||||
def _clean_message(text):
|
def _clean_message(text):
|
||||||
for prefix in _PREFIXES:
|
for prefix in _PREFIXES:
|
||||||
if text.startswith(prefix):
|
if text.startswith(prefix):
|
||||||
text = text[len(prefix):]
|
text = text[len(prefix) :]
|
||||||
return (text[0].upper() + text[1:]).strip()
|
return (text[0].upper() + text[1:]).strip()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ class TelegramAPIError(Exception):
|
||||||
|
|
||||||
|
|
||||||
class _MatchErrorMixin:
|
class _MatchErrorMixin:
|
||||||
match = ''
|
match = ""
|
||||||
text = None
|
text = None
|
||||||
|
|
||||||
__subclasses = []
|
__subclasses = []
|
||||||
|
|
@ -166,67 +166,72 @@ class MessageNotModified(MessageError):
|
||||||
"""
|
"""
|
||||||
Will be raised when you try to set new text is equals to current text.
|
Will be raised when you try to set new text is equals to current text.
|
||||||
"""
|
"""
|
||||||
match = 'message is not modified'
|
|
||||||
|
match = "message is not modified"
|
||||||
|
|
||||||
|
|
||||||
class MessageToForwardNotFound(MessageError):
|
class MessageToForwardNotFound(MessageError):
|
||||||
"""
|
"""
|
||||||
Will be raised when you try to forward very old or deleted or unknown message.
|
Will be raised when you try to forward very old or deleted or unknown message.
|
||||||
"""
|
"""
|
||||||
match = 'message to forward not found'
|
|
||||||
|
match = "message to forward not found"
|
||||||
|
|
||||||
|
|
||||||
class MessageToDeleteNotFound(MessageError):
|
class MessageToDeleteNotFound(MessageError):
|
||||||
"""
|
"""
|
||||||
Will be raised when you try to delete very old or deleted or unknown message.
|
Will be raised when you try to delete very old or deleted or unknown message.
|
||||||
"""
|
"""
|
||||||
match = 'message to delete not found'
|
|
||||||
|
match = "message to delete not found"
|
||||||
|
|
||||||
|
|
||||||
class MessageToReplyNotFound(MessageError):
|
class MessageToReplyNotFound(MessageError):
|
||||||
"""
|
"""
|
||||||
Will be raised when you try to reply to very old or deleted or unknown message.
|
Will be raised when you try to reply to very old or deleted or unknown message.
|
||||||
"""
|
"""
|
||||||
match = 'message to reply not found'
|
|
||||||
|
match = "message to reply not found"
|
||||||
|
|
||||||
|
|
||||||
class MessageIdentifierNotSpecified(MessageError):
|
class MessageIdentifierNotSpecified(MessageError):
|
||||||
match = 'message identifier is not specified'
|
match = "message identifier is not specified"
|
||||||
|
|
||||||
|
|
||||||
class MessageTextIsEmpty(MessageError):
|
class MessageTextIsEmpty(MessageError):
|
||||||
match = 'Message text is empty'
|
match = "Message text is empty"
|
||||||
|
|
||||||
|
|
||||||
class MessageCantBeEdited(MessageError):
|
class MessageCantBeEdited(MessageError):
|
||||||
match = 'message can\'t be edited'
|
match = "message can't be edited"
|
||||||
|
|
||||||
|
|
||||||
class MessageCantBeDeleted(MessageError):
|
class MessageCantBeDeleted(MessageError):
|
||||||
match = 'message can\'t be deleted'
|
match = "message can't be deleted"
|
||||||
|
|
||||||
|
|
||||||
class MessageToEditNotFound(MessageError):
|
class MessageToEditNotFound(MessageError):
|
||||||
match = 'message to edit not found'
|
match = "message to edit not found"
|
||||||
|
|
||||||
|
|
||||||
class MessageIsTooLong(MessageError):
|
class MessageIsTooLong(MessageError):
|
||||||
match = 'message is too long'
|
match = "message is too long"
|
||||||
|
|
||||||
|
|
||||||
class ToMuchMessages(MessageError):
|
class ToMuchMessages(MessageError):
|
||||||
"""
|
"""
|
||||||
Will be raised when you try to send media group with more than 10 items.
|
Will be raised when you try to send media group with more than 10 items.
|
||||||
"""
|
"""
|
||||||
match = 'Too much messages to send as an album'
|
|
||||||
|
match = "Too much messages to send as an album"
|
||||||
|
|
||||||
|
|
||||||
class ObjectExpectedAsReplyMarkup(BadRequest):
|
class ObjectExpectedAsReplyMarkup(BadRequest):
|
||||||
match = 'object expected as reply markup'
|
match = "object expected as reply markup"
|
||||||
|
|
||||||
|
|
||||||
class InlineKeyboardExpected(BadRequest):
|
class InlineKeyboardExpected(BadRequest):
|
||||||
match = 'inline keyboard expected'
|
match = "inline keyboard expected"
|
||||||
|
|
||||||
|
|
||||||
class PollError(BadRequest):
|
class PollError(BadRequest):
|
||||||
|
|
@ -238,7 +243,7 @@ class PollCantBeStopped(PollError):
|
||||||
|
|
||||||
|
|
||||||
class PollHasAlreadyBeenClosed(PollError):
|
class PollHasAlreadyBeenClosed(PollError):
|
||||||
match = 'poll has already been closed'
|
match = "poll has already been closed"
|
||||||
|
|
||||||
|
|
||||||
class PollsCantBeSentToPrivateChats(PollError):
|
class PollsCantBeSentToPrivateChats(PollError):
|
||||||
|
|
@ -277,109 +282,112 @@ class MessageWithPollNotFound(PollError, MessageError):
|
||||||
"""
|
"""
|
||||||
Will be raised when you try to stop poll with message without poll
|
Will be raised when you try to stop poll with message without poll
|
||||||
"""
|
"""
|
||||||
match = 'message with poll to stop not found'
|
|
||||||
|
match = "message with poll to stop not found"
|
||||||
|
|
||||||
|
|
||||||
class MessageIsNotAPoll(PollError, MessageError):
|
class MessageIsNotAPoll(PollError, MessageError):
|
||||||
"""
|
"""
|
||||||
Will be raised when you try to stop poll with message without poll
|
Will be raised when you try to stop poll with message without poll
|
||||||
"""
|
"""
|
||||||
match = 'message is not a poll'
|
|
||||||
|
match = "message is not a poll"
|
||||||
|
|
||||||
|
|
||||||
class ChatNotFound(BadRequest):
|
class ChatNotFound(BadRequest):
|
||||||
match = 'chat not found'
|
match = "chat not found"
|
||||||
|
|
||||||
|
|
||||||
class ChatIdIsEmpty(BadRequest):
|
class ChatIdIsEmpty(BadRequest):
|
||||||
match = 'chat_id is empty'
|
match = "chat_id is empty"
|
||||||
|
|
||||||
|
|
||||||
class InvalidUserId(BadRequest):
|
class InvalidUserId(BadRequest):
|
||||||
match = 'user_id_invalid'
|
match = "user_id_invalid"
|
||||||
text = 'Invalid user id'
|
text = "Invalid user id"
|
||||||
|
|
||||||
|
|
||||||
class ChatDescriptionIsNotModified(BadRequest):
|
class ChatDescriptionIsNotModified(BadRequest):
|
||||||
match = 'chat description is not modified'
|
match = "chat description is not modified"
|
||||||
|
|
||||||
|
|
||||||
class InvalidQueryID(BadRequest):
|
class InvalidQueryID(BadRequest):
|
||||||
match = 'query is too old and response timeout expired or query id is invalid'
|
match = "query is too old and response timeout expired or query id is invalid"
|
||||||
|
|
||||||
|
|
||||||
class InvalidPeerID(BadRequest):
|
class InvalidPeerID(BadRequest):
|
||||||
match = 'PEER_ID_INVALID'
|
match = "PEER_ID_INVALID"
|
||||||
text = 'Invalid peer ID'
|
text = "Invalid peer ID"
|
||||||
|
|
||||||
|
|
||||||
class InvalidHTTPUrlContent(BadRequest):
|
class InvalidHTTPUrlContent(BadRequest):
|
||||||
match = 'Failed to get HTTP URL content'
|
match = "Failed to get HTTP URL content"
|
||||||
|
|
||||||
|
|
||||||
class ButtonURLInvalid(BadRequest):
|
class ButtonURLInvalid(BadRequest):
|
||||||
match = 'BUTTON_URL_INVALID'
|
match = "BUTTON_URL_INVALID"
|
||||||
text = 'Button URL invalid'
|
text = "Button URL invalid"
|
||||||
|
|
||||||
|
|
||||||
class URLHostIsEmpty(BadRequest):
|
class URLHostIsEmpty(BadRequest):
|
||||||
match = 'URL host is empty'
|
match = "URL host is empty"
|
||||||
|
|
||||||
|
|
||||||
class StartParamInvalid(BadRequest):
|
class StartParamInvalid(BadRequest):
|
||||||
match = 'START_PARAM_INVALID'
|
match = "START_PARAM_INVALID"
|
||||||
text = 'Start param invalid'
|
text = "Start param invalid"
|
||||||
|
|
||||||
|
|
||||||
class ButtonDataInvalid(BadRequest):
|
class ButtonDataInvalid(BadRequest):
|
||||||
match = 'BUTTON_DATA_INVALID'
|
match = "BUTTON_DATA_INVALID"
|
||||||
text = 'Button data invalid'
|
text = "Button data invalid"
|
||||||
|
|
||||||
|
|
||||||
class WrongFileIdentifier(BadRequest):
|
class WrongFileIdentifier(BadRequest):
|
||||||
match = 'wrong file identifier/HTTP URL specified'
|
match = "wrong file identifier/HTTP URL specified"
|
||||||
|
|
||||||
|
|
||||||
class GroupDeactivated(BadRequest):
|
class GroupDeactivated(BadRequest):
|
||||||
match = 'group is deactivated'
|
match = "group is deactivated"
|
||||||
|
|
||||||
|
|
||||||
class PhotoAsInputFileRequired(BadRequest):
|
class PhotoAsInputFileRequired(BadRequest):
|
||||||
"""
|
"""
|
||||||
Will be raised when you try to set chat photo from file ID.
|
Will be raised when you try to set chat photo from file ID.
|
||||||
"""
|
"""
|
||||||
match = 'Photo should be uploaded as an InputFile'
|
|
||||||
|
match = "Photo should be uploaded as an InputFile"
|
||||||
|
|
||||||
|
|
||||||
class InvalidStickersSet(BadRequest):
|
class InvalidStickersSet(BadRequest):
|
||||||
match = 'STICKERSET_INVALID'
|
match = "STICKERSET_INVALID"
|
||||||
text = 'Stickers set is invalid'
|
text = "Stickers set is invalid"
|
||||||
|
|
||||||
|
|
||||||
class NoStickerInRequest(BadRequest):
|
class NoStickerInRequest(BadRequest):
|
||||||
match = 'there is no sticker in the request'
|
match = "there is no sticker in the request"
|
||||||
|
|
||||||
|
|
||||||
class ChatAdminRequired(BadRequest):
|
class ChatAdminRequired(BadRequest):
|
||||||
match = 'CHAT_ADMIN_REQUIRED'
|
match = "CHAT_ADMIN_REQUIRED"
|
||||||
text = 'Admin permissions is required!'
|
text = "Admin permissions is required!"
|
||||||
|
|
||||||
|
|
||||||
class NeedAdministratorRightsInTheChannel(BadRequest):
|
class NeedAdministratorRightsInTheChannel(BadRequest):
|
||||||
match = 'need administrator rights in the channel chat'
|
match = "need administrator rights in the channel chat"
|
||||||
text = 'Admin permissions is required!'
|
text = "Admin permissions is required!"
|
||||||
|
|
||||||
|
|
||||||
class NotEnoughRightsToPinMessage(BadRequest):
|
class NotEnoughRightsToPinMessage(BadRequest):
|
||||||
match = 'not enough rights to pin a message'
|
match = "not enough rights to pin a message"
|
||||||
|
|
||||||
|
|
||||||
class MethodNotAvailableInPrivateChats(BadRequest):
|
class MethodNotAvailableInPrivateChats(BadRequest):
|
||||||
match = 'method is available only for supergroups and channel'
|
match = "method is available only for supergroups and channel"
|
||||||
|
|
||||||
|
|
||||||
class CantDemoteChatCreator(BadRequest):
|
class CantDemoteChatCreator(BadRequest):
|
||||||
match = 'can\'t demote chat creator'
|
match = "can't demote chat creator"
|
||||||
|
|
||||||
|
|
||||||
class CantRestrictSelf(BadRequest):
|
class CantRestrictSelf(BadRequest):
|
||||||
|
|
@ -388,34 +396,34 @@ class CantRestrictSelf(BadRequest):
|
||||||
|
|
||||||
|
|
||||||
class NotEnoughRightsToRestrict(BadRequest):
|
class NotEnoughRightsToRestrict(BadRequest):
|
||||||
match = 'not enough rights to restrict/unrestrict chat member'
|
match = "not enough rights to restrict/unrestrict chat member"
|
||||||
|
|
||||||
|
|
||||||
class PhotoDimensions(BadRequest):
|
class PhotoDimensions(BadRequest):
|
||||||
match = 'PHOTO_INVALID_DIMENSIONS'
|
match = "PHOTO_INVALID_DIMENSIONS"
|
||||||
text = 'Invalid photo dimensions'
|
text = "Invalid photo dimensions"
|
||||||
|
|
||||||
|
|
||||||
class UnavailableMembers(BadRequest):
|
class UnavailableMembers(BadRequest):
|
||||||
match = 'supergroup members are unavailable'
|
match = "supergroup members are unavailable"
|
||||||
|
|
||||||
|
|
||||||
class TypeOfFileMismatch(BadRequest):
|
class TypeOfFileMismatch(BadRequest):
|
||||||
match = 'type of file mismatch'
|
match = "type of file mismatch"
|
||||||
|
|
||||||
|
|
||||||
class WrongRemoteFileIdSpecified(BadRequest):
|
class WrongRemoteFileIdSpecified(BadRequest):
|
||||||
match = 'wrong remote file id specified'
|
match = "wrong remote file id specified"
|
||||||
|
|
||||||
|
|
||||||
class PaymentProviderInvalid(BadRequest):
|
class PaymentProviderInvalid(BadRequest):
|
||||||
match = 'PAYMENT_PROVIDER_INVALID'
|
match = "PAYMENT_PROVIDER_INVALID"
|
||||||
text = 'payment provider invalid'
|
text = "payment provider invalid"
|
||||||
|
|
||||||
|
|
||||||
class CurrencyTotalAmountInvalid(BadRequest):
|
class CurrencyTotalAmountInvalid(BadRequest):
|
||||||
match = 'currency_total_amount_invalid'
|
match = "currency_total_amount_invalid"
|
||||||
text = 'currency total amount invalid'
|
text = "currency total amount invalid"
|
||||||
|
|
||||||
|
|
||||||
class BadWebhook(BadRequest):
|
class BadWebhook(BadRequest):
|
||||||
|
|
@ -423,44 +431,44 @@ class BadWebhook(BadRequest):
|
||||||
|
|
||||||
|
|
||||||
class WebhookRequireHTTPS(BadWebhook):
|
class WebhookRequireHTTPS(BadWebhook):
|
||||||
match = 'HTTPS url must be provided for webhook'
|
match = "HTTPS url must be provided for webhook"
|
||||||
text = 'bad webhook: ' + match
|
text = "bad webhook: " + match
|
||||||
|
|
||||||
|
|
||||||
class BadWebhookPort(BadWebhook):
|
class BadWebhookPort(BadWebhook):
|
||||||
match = 'Webhook can be set up only on ports 80, 88, 443 or 8443'
|
match = "Webhook can be set up only on ports 80, 88, 443 or 8443"
|
||||||
text = 'bad webhook: ' + match
|
text = "bad webhook: " + match
|
||||||
|
|
||||||
|
|
||||||
class BadWebhookAddrInfo(BadWebhook):
|
class BadWebhookAddrInfo(BadWebhook):
|
||||||
match = 'getaddrinfo: Temporary failure in name resolution'
|
match = "getaddrinfo: Temporary failure in name resolution"
|
||||||
text = 'bad webhook: ' + match
|
text = "bad webhook: " + match
|
||||||
|
|
||||||
|
|
||||||
class BadWebhookNoAddressAssociatedWithHostname(BadWebhook):
|
class BadWebhookNoAddressAssociatedWithHostname(BadWebhook):
|
||||||
match = 'failed to resolve host: no address associated with hostname'
|
match = "failed to resolve host: no address associated with hostname"
|
||||||
|
|
||||||
|
|
||||||
class CantParseUrl(BadRequest):
|
class CantParseUrl(BadRequest):
|
||||||
match = 'can\'t parse URL'
|
match = "can't parse URL"
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedUrlProtocol(BadRequest):
|
class UnsupportedUrlProtocol(BadRequest):
|
||||||
match = 'unsupported URL protocol'
|
match = "unsupported URL protocol"
|
||||||
|
|
||||||
|
|
||||||
class CantParseEntities(BadRequest):
|
class CantParseEntities(BadRequest):
|
||||||
match = 'can\'t parse entities'
|
match = "can't parse entities"
|
||||||
|
|
||||||
|
|
||||||
class ResultIdDuplicate(BadRequest):
|
class ResultIdDuplicate(BadRequest):
|
||||||
match = 'result_id_duplicate'
|
match = "result_id_duplicate"
|
||||||
text = 'Result ID duplicate'
|
text = "Result ID duplicate"
|
||||||
|
|
||||||
|
|
||||||
class BotDomainInvalid(BadRequest):
|
class BotDomainInvalid(BadRequest):
|
||||||
match = 'bot_domain_invalid'
|
match = "bot_domain_invalid"
|
||||||
text = 'Invalid bot domain'
|
text = "Invalid bot domain"
|
||||||
|
|
||||||
|
|
||||||
class NotFound(TelegramAPIError, _MatchErrorMixin):
|
class NotFound(TelegramAPIError, _MatchErrorMixin):
|
||||||
|
|
@ -468,7 +476,7 @@ class NotFound(TelegramAPIError, _MatchErrorMixin):
|
||||||
|
|
||||||
|
|
||||||
class MethodNotKnown(NotFound):
|
class MethodNotKnown(NotFound):
|
||||||
match = 'method not found'
|
match = "method not found"
|
||||||
|
|
||||||
|
|
||||||
class ConflictError(TelegramAPIError, _MatchErrorMixin):
|
class ConflictError(TelegramAPIError, _MatchErrorMixin):
|
||||||
|
|
@ -476,13 +484,15 @@ class ConflictError(TelegramAPIError, _MatchErrorMixin):
|
||||||
|
|
||||||
|
|
||||||
class TerminatedByOtherGetUpdates(ConflictError):
|
class TerminatedByOtherGetUpdates(ConflictError):
|
||||||
match = 'terminated by other getUpdates request'
|
match = "terminated by other getUpdates request"
|
||||||
text = 'Terminated by other getUpdates request; ' \
|
text = (
|
||||||
'Make sure that only one bot instance is running'
|
"Terminated by other getUpdates request; "
|
||||||
|
"Make sure that only one bot instance is running"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CantGetUpdates(ConflictError):
|
class CantGetUpdates(ConflictError):
|
||||||
match = 'can\'t use getUpdates method while webhook is active'
|
match = "can't use getUpdates method while webhook is active"
|
||||||
|
|
||||||
|
|
||||||
class Unauthorized(TelegramAPIError, _MatchErrorMixin):
|
class Unauthorized(TelegramAPIError, _MatchErrorMixin):
|
||||||
|
|
@ -490,23 +500,23 @@ class Unauthorized(TelegramAPIError, _MatchErrorMixin):
|
||||||
|
|
||||||
|
|
||||||
class BotKicked(Unauthorized):
|
class BotKicked(Unauthorized):
|
||||||
match = 'Bot was kicked from a chat'
|
match = "Bot was kicked from a chat"
|
||||||
|
|
||||||
|
|
||||||
class BotBlocked(Unauthorized):
|
class BotBlocked(Unauthorized):
|
||||||
match = 'bot was blocked by the user'
|
match = "bot was blocked by the user"
|
||||||
|
|
||||||
|
|
||||||
class UserDeactivated(Unauthorized):
|
class UserDeactivated(Unauthorized):
|
||||||
match = 'user is deactivated'
|
match = "user is deactivated"
|
||||||
|
|
||||||
|
|
||||||
class CantInitiateConversation(Unauthorized):
|
class CantInitiateConversation(Unauthorized):
|
||||||
match = 'bot can\'t initiate conversation with a user'
|
match = "bot can't initiate conversation with a user"
|
||||||
|
|
||||||
|
|
||||||
class CantTalkWithBots(Unauthorized):
|
class CantTalkWithBots(Unauthorized):
|
||||||
match = 'bot can\'t send messages to bots'
|
match = "bot can't send messages to bots"
|
||||||
|
|
||||||
|
|
||||||
class NetworkError(TelegramAPIError):
|
class NetworkError(TelegramAPIError):
|
||||||
|
|
@ -515,34 +525,43 @@ class NetworkError(TelegramAPIError):
|
||||||
|
|
||||||
class RestartingTelegram(TelegramAPIError):
|
class RestartingTelegram(TelegramAPIError):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(RestartingTelegram, self).__init__('The Telegram Bot API service is restarting. Wait few second.')
|
super(RestartingTelegram, self).__init__(
|
||||||
|
"The Telegram Bot API service is restarting. Wait few second."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RetryAfter(TelegramAPIError):
|
class RetryAfter(TelegramAPIError):
|
||||||
def __init__(self, retry_after):
|
def __init__(self, retry_after):
|
||||||
super(RetryAfter, self).__init__(f"Flood control exceeded. Retry in {retry_after} seconds.")
|
super(RetryAfter, self).__init__(
|
||||||
|
f"Flood control exceeded. Retry in {retry_after} seconds."
|
||||||
|
)
|
||||||
self.timeout = retry_after
|
self.timeout = retry_after
|
||||||
|
|
||||||
|
|
||||||
class MigrateToChat(TelegramAPIError):
|
class MigrateToChat(TelegramAPIError):
|
||||||
def __init__(self, chat_id):
|
def __init__(self, chat_id):
|
||||||
super(MigrateToChat, self).__init__(f"The group has been migrated to a supergroup. New id: {chat_id}.")
|
super(MigrateToChat, self).__init__(
|
||||||
|
f"The group has been migrated to a supergroup. New id: {chat_id}."
|
||||||
|
)
|
||||||
self.migrate_to_chat_id = chat_id
|
self.migrate_to_chat_id = chat_id
|
||||||
|
|
||||||
|
|
||||||
class Throttled(TelegramAPIError):
|
class Throttled(TelegramAPIError):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
from ..dispatcher.storage import DELTA, EXCEEDED_COUNT, KEY, LAST_CALL, RATE_LIMIT, RESULT
|
from ..dispatcher.storage import DELTA, EXCEEDED_COUNT, KEY, LAST_CALL, RATE_LIMIT, RESULT
|
||||||
self.key = kwargs.pop(KEY, '<None>')
|
|
||||||
|
self.key = kwargs.pop(KEY, "<None>")
|
||||||
self.called_at = kwargs.pop(LAST_CALL, time.time())
|
self.called_at = kwargs.pop(LAST_CALL, time.time())
|
||||||
self.rate = kwargs.pop(RATE_LIMIT, None)
|
self.rate = kwargs.pop(RATE_LIMIT, None)
|
||||||
self.result = kwargs.pop(RESULT, False)
|
self.result = kwargs.pop(RESULT, False)
|
||||||
self.exceeded_count = kwargs.pop(EXCEEDED_COUNT, 0)
|
self.exceeded_count = kwargs.pop(EXCEEDED_COUNT, 0)
|
||||||
self.delta = kwargs.pop(DELTA, 0)
|
self.delta = kwargs.pop(DELTA, 0)
|
||||||
self.user = kwargs.pop('user', None)
|
self.user = kwargs.pop("user", None)
|
||||||
self.chat = kwargs.pop('chat', None)
|
self.chat = kwargs.pop("chat", None)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Rate limit exceeded! (Limit: {self.rate} s, " \
|
return (
|
||||||
f"exceeded: {self.exceeded_count}, " \
|
f"Rate limit exceeded! (Limit: {self.rate} s, "
|
||||||
|
f"exceeded: {self.exceeded_count}, "
|
||||||
f"time delta: {round(self.delta, 3)} s)"
|
f"time delta: {round(self.delta, 3)} s)"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from ..bot.api import log
|
||||||
from ..dispatcher.dispatcher import Dispatcher
|
from ..dispatcher.dispatcher import Dispatcher
|
||||||
from ..dispatcher.webhook import BOT_DISPATCHER_KEY, DEFAULT_ROUTE_NAME, WebhookRequestHandler
|
from ..dispatcher.webhook import BOT_DISPATCHER_KEY, DEFAULT_ROUTE_NAME, WebhookRequestHandler
|
||||||
|
|
||||||
APP_EXECUTOR_KEY = 'APP_EXECUTOR'
|
APP_EXECUTOR_KEY = "APP_EXECUTOR"
|
||||||
|
|
||||||
|
|
||||||
def _setup_callbacks(executor, on_startup=None, on_shutdown=None):
|
def _setup_callbacks(executor, on_startup=None, on_shutdown=None):
|
||||||
|
|
@ -22,8 +22,17 @@ def _setup_callbacks(executor, on_startup=None, on_shutdown=None):
|
||||||
executor.on_shutdown(on_shutdown)
|
executor.on_shutdown(on_shutdown)
|
||||||
|
|
||||||
|
|
||||||
def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=True,
|
def start_polling(
|
||||||
on_startup=None, on_shutdown=None, timeout=20, fast=True):
|
dispatcher,
|
||||||
|
*,
|
||||||
|
loop=None,
|
||||||
|
skip_updates=False,
|
||||||
|
reset_webhook=True,
|
||||||
|
on_startup=None,
|
||||||
|
on_shutdown=None,
|
||||||
|
timeout=20,
|
||||||
|
fast=True,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Start bot in long-polling mode
|
Start bot in long-polling mode
|
||||||
|
|
||||||
|
|
@ -41,11 +50,19 @@ def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=Tr
|
||||||
executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, fast=fast)
|
executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, fast=fast)
|
||||||
|
|
||||||
|
|
||||||
def set_webhook(dispatcher: Dispatcher, webhook_path: str, *, loop: Optional[asyncio.AbstractEventLoop] = None,
|
def set_webhook(
|
||||||
skip_updates: bool = None, on_startup: Optional[Callable] = None,
|
dispatcher: Dispatcher,
|
||||||
on_shutdown: Optional[Callable] = None, check_ip: bool = False,
|
webhook_path: str,
|
||||||
retry_after: Optional[Union[str, int]] = None, route_name: str = DEFAULT_ROUTE_NAME,
|
*,
|
||||||
web_app: Optional[Application] = None):
|
loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||||
|
skip_updates: bool = None,
|
||||||
|
on_startup: Optional[Callable] = None,
|
||||||
|
on_shutdown: Optional[Callable] = None,
|
||||||
|
check_ip: bool = False,
|
||||||
|
retry_after: Optional[Union[str, int]] = None,
|
||||||
|
route_name: str = DEFAULT_ROUTE_NAME,
|
||||||
|
web_app: Optional[Application] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Set webhook for bot
|
Set webhook for bot
|
||||||
|
|
||||||
|
|
@ -61,17 +78,32 @@ def set_webhook(dispatcher: Dispatcher, webhook_path: str, *, loop: Optional[asy
|
||||||
:param web_app: Optional[Application] (default: None)
|
:param web_app: Optional[Application] (default: None)
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
executor = Executor(dispatcher, skip_updates=skip_updates, check_ip=check_ip, retry_after=retry_after,
|
executor = Executor(
|
||||||
loop=loop)
|
dispatcher,
|
||||||
|
skip_updates=skip_updates,
|
||||||
|
check_ip=check_ip,
|
||||||
|
retry_after=retry_after,
|
||||||
|
loop=loop,
|
||||||
|
)
|
||||||
_setup_callbacks(executor, on_startup, on_shutdown)
|
_setup_callbacks(executor, on_startup, on_shutdown)
|
||||||
|
|
||||||
executor.set_webhook(webhook_path, route_name=route_name, web_app=web_app)
|
executor.set_webhook(webhook_path, route_name=route_name, web_app=web_app)
|
||||||
return executor
|
return executor
|
||||||
|
|
||||||
|
|
||||||
def start_webhook(dispatcher, webhook_path, *, loop=None, skip_updates=None,
|
def start_webhook(
|
||||||
on_startup=None, on_shutdown=None, check_ip=False, retry_after=None, route_name=DEFAULT_ROUTE_NAME,
|
dispatcher,
|
||||||
**kwargs):
|
webhook_path,
|
||||||
|
*,
|
||||||
|
loop=None,
|
||||||
|
skip_updates=None,
|
||||||
|
on_startup=None,
|
||||||
|
on_shutdown=None,
|
||||||
|
check_ip=False,
|
||||||
|
retry_after=None,
|
||||||
|
route_name=DEFAULT_ROUTE_NAME,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Start bot in webhook mode
|
Start bot in webhook mode
|
||||||
|
|
||||||
|
|
@ -86,20 +118,21 @@ def start_webhook(dispatcher, webhook_path, *, loop=None, skip_updates=None,
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
executor = set_webhook(dispatcher=dispatcher,
|
executor = set_webhook(
|
||||||
webhook_path=webhook_path,
|
dispatcher=dispatcher,
|
||||||
loop=loop,
|
webhook_path=webhook_path,
|
||||||
skip_updates=skip_updates,
|
loop=loop,
|
||||||
on_startup=on_startup,
|
skip_updates=skip_updates,
|
||||||
on_shutdown=on_shutdown,
|
on_startup=on_startup,
|
||||||
check_ip=check_ip,
|
on_shutdown=on_shutdown,
|
||||||
retry_after=retry_after,
|
check_ip=check_ip,
|
||||||
route_name=route_name)
|
retry_after=retry_after,
|
||||||
|
route_name=route_name,
|
||||||
|
)
|
||||||
executor.run_app(**kwargs)
|
executor.run_app(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def start(dispatcher, future, *, loop=None, skip_updates=None,
|
def start(dispatcher, future, *, loop=None, skip_updates=None, on_startup=None, on_shutdown=None):
|
||||||
on_startup=None, on_shutdown=None):
|
|
||||||
"""
|
"""
|
||||||
Execute Future.
|
Execute Future.
|
||||||
|
|
||||||
|
|
@ -142,6 +175,7 @@ class Executor:
|
||||||
self._freeze = False
|
self._freeze = False
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher
|
from aiogram import Bot, Dispatcher
|
||||||
|
|
||||||
Bot.set_current(dispatcher.bot)
|
Bot.set_current(dispatcher.bot)
|
||||||
Dispatcher.set_current(dispatcher)
|
Dispatcher.set_current(dispatcher)
|
||||||
|
|
||||||
|
|
@ -160,7 +194,7 @@ class Executor:
|
||||||
@property
|
@property
|
||||||
def web_app(self) -> web.Application:
|
def web_app(self) -> web.Application:
|
||||||
if self._web_app is None:
|
if self._web_app is None:
|
||||||
raise RuntimeError('web.Application() is not configured!')
|
raise RuntimeError("web.Application() is not configured!")
|
||||||
return self._web_app
|
return self._web_app
|
||||||
|
|
||||||
def on_startup(self, callback: callable, polling=True, webhook=True):
|
def on_startup(self, callback: callable, polling=True, webhook=True):
|
||||||
|
|
@ -173,7 +207,7 @@ class Executor:
|
||||||
"""
|
"""
|
||||||
self._check_frozen()
|
self._check_frozen()
|
||||||
if not webhook and not polling:
|
if not webhook and not polling:
|
||||||
warn('This action has no effect!', UserWarning)
|
warn("This action has no effect!", UserWarning)
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(callback, (list, tuple, set)):
|
if isinstance(callback, (list, tuple, set)):
|
||||||
|
|
@ -196,7 +230,7 @@ class Executor:
|
||||||
"""
|
"""
|
||||||
self._check_frozen()
|
self._check_frozen()
|
||||||
if not webhook and not polling:
|
if not webhook and not polling:
|
||||||
warn('This action has no effect!', UserWarning)
|
warn("This action has no effect!", UserWarning)
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(callback, (list, tuple, set)):
|
if isinstance(callback, (list, tuple, set)):
|
||||||
|
|
@ -211,7 +245,7 @@ class Executor:
|
||||||
|
|
||||||
def _check_frozen(self):
|
def _check_frozen(self):
|
||||||
if self.frozen:
|
if self.frozen:
|
||||||
raise RuntimeError('Executor is frozen!')
|
raise RuntimeError("Executor is frozen!")
|
||||||
|
|
||||||
def _prepare_polling(self):
|
def _prepare_polling(self):
|
||||||
self._check_frozen()
|
self._check_frozen()
|
||||||
|
|
@ -219,7 +253,9 @@ class Executor:
|
||||||
|
|
||||||
# self.loop.set_task_factory(context.task_factory)
|
# self.loop.set_task_factory(context.task_factory)
|
||||||
|
|
||||||
def _prepare_webhook(self, path=None, handler=WebhookRequestHandler, route_name=DEFAULT_ROUTE_NAME, app=None):
|
def _prepare_webhook(
|
||||||
|
self, path=None, handler=WebhookRequestHandler, route_name=DEFAULT_ROUTE_NAME, app=None
|
||||||
|
):
|
||||||
self._check_frozen()
|
self._check_frozen()
|
||||||
self._freeze = True
|
self._freeze = True
|
||||||
|
|
||||||
|
|
@ -233,14 +269,14 @@ class Executor:
|
||||||
raise RuntimeError("web.Application() is already configured!")
|
raise RuntimeError("web.Application() is already configured!")
|
||||||
|
|
||||||
if self.retry_after:
|
if self.retry_after:
|
||||||
app['RETRY_AFTER'] = self.retry_after
|
app["RETRY_AFTER"] = self.retry_after
|
||||||
|
|
||||||
if self._identity == app.get(self._identity):
|
if self._identity == app.get(self._identity):
|
||||||
# App is already configured
|
# App is already configured
|
||||||
return
|
return
|
||||||
|
|
||||||
if path is not None:
|
if path is not None:
|
||||||
app.router.add_route('*', path, handler, name=route_name)
|
app.router.add_route("*", path, handler, name=route_name)
|
||||||
|
|
||||||
async def _wrap_callback(cb, _):
|
async def _wrap_callback(cb, _):
|
||||||
return await cb(self.dispatcher)
|
return await cb(self.dispatcher)
|
||||||
|
|
@ -258,10 +294,15 @@ class Executor:
|
||||||
app[APP_EXECUTOR_KEY] = self
|
app[APP_EXECUTOR_KEY] = self
|
||||||
app[BOT_DISPATCHER_KEY] = self.dispatcher
|
app[BOT_DISPATCHER_KEY] = self.dispatcher
|
||||||
app[self._identity] = datetime.datetime.now()
|
app[self._identity] = datetime.datetime.now()
|
||||||
app['_check_ip'] = self.check_ip
|
app["_check_ip"] = self.check_ip
|
||||||
|
|
||||||
def set_webhook(self, webhook_path: Optional[str] = None, request_handler: Any = WebhookRequestHandler,
|
def set_webhook(
|
||||||
route_name: str = DEFAULT_ROUTE_NAME, web_app: Optional[Application] = None):
|
self,
|
||||||
|
webhook_path: Optional[str] = None,
|
||||||
|
request_handler: Any = WebhookRequestHandler,
|
||||||
|
route_name: str = DEFAULT_ROUTE_NAME,
|
||||||
|
web_app: Optional[Application] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Set webhook for bot
|
Set webhook for bot
|
||||||
|
|
||||||
|
|
@ -277,8 +318,13 @@ class Executor:
|
||||||
def run_app(self, **kwargs):
|
def run_app(self, **kwargs):
|
||||||
web.run_app(self._web_app, **kwargs)
|
web.run_app(self._web_app, **kwargs)
|
||||||
|
|
||||||
def start_webhook(self, webhook_path=None, request_handler=WebhookRequestHandler, route_name=DEFAULT_ROUTE_NAME,
|
def start_webhook(
|
||||||
**kwargs):
|
self,
|
||||||
|
webhook_path=None,
|
||||||
|
request_handler=WebhookRequestHandler,
|
||||||
|
route_name=DEFAULT_ROUTE_NAME,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Start bot in webhook mode
|
Start bot in webhook mode
|
||||||
|
|
||||||
|
|
@ -288,7 +334,9 @@ class Executor:
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.set_webhook(webhook_path=webhook_path, request_handler=request_handler, route_name=route_name)
|
self.set_webhook(
|
||||||
|
webhook_path=webhook_path, request_handler=request_handler, route_name=route_name
|
||||||
|
)
|
||||||
self.run_app(**kwargs)
|
self.run_app(**kwargs)
|
||||||
|
|
||||||
def start_polling(self, reset_webhook=None, timeout=20, fast=True):
|
def start_polling(self, reset_webhook=None, timeout=20, fast=True):
|
||||||
|
|
@ -303,7 +351,11 @@ class Executor:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(self._startup_polling())
|
loop.run_until_complete(self._startup_polling())
|
||||||
loop.create_task(self.dispatcher.start_polling(reset_webhook=reset_webhook, timeout=timeout, fast=fast))
|
loop.create_task(
|
||||||
|
self.dispatcher.start_polling(
|
||||||
|
reset_webhook=reset_webhook, timeout=timeout, fast=fast
|
||||||
|
)
|
||||||
|
)
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
# loop.stop()
|
# loop.stop()
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Example:
|
||||||
|
|
||||||
|
|
||||||
class Helper:
|
class Helper:
|
||||||
mode = ''
|
mode = ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all(cls):
|
def all(cls):
|
||||||
|
|
@ -37,13 +37,13 @@ class Helper:
|
||||||
|
|
||||||
|
|
||||||
class HelperMode(Helper):
|
class HelperMode(Helper):
|
||||||
mode = 'original'
|
mode = "original"
|
||||||
|
|
||||||
SCREAMING_SNAKE_CASE = 'SCREAMING_SNAKE_CASE'
|
SCREAMING_SNAKE_CASE = "SCREAMING_SNAKE_CASE"
|
||||||
lowerCamelCase = 'lowerCamelCase'
|
lowerCamelCase = "lowerCamelCase"
|
||||||
CamelCase = 'CamelCase'
|
CamelCase = "CamelCase"
|
||||||
snake_case = 'snake_case'
|
snake_case = "snake_case"
|
||||||
lowercase = 'lowercase'
|
lowercase = "lowercase"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all(cls):
|
def all(cls):
|
||||||
|
|
@ -65,10 +65,10 @@ class HelperMode(Helper):
|
||||||
"""
|
"""
|
||||||
if text.isupper():
|
if text.isupper():
|
||||||
return text
|
return text
|
||||||
result = ''
|
result = ""
|
||||||
for pos, symbol in enumerate(text):
|
for pos, symbol in enumerate(text):
|
||||||
if symbol.isupper() and pos > 0:
|
if symbol.isupper() and pos > 0:
|
||||||
result += '_' + symbol
|
result += "_" + symbol
|
||||||
else:
|
else:
|
||||||
result += symbol.upper()
|
result += symbol.upper()
|
||||||
return result
|
return result
|
||||||
|
|
@ -94,10 +94,10 @@ class HelperMode(Helper):
|
||||||
:param first_upper: first symbol must be upper?
|
:param first_upper: first symbol must be upper?
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
result = ''
|
result = ""
|
||||||
need_upper = False
|
need_upper = False
|
||||||
for pos, symbol in enumerate(text):
|
for pos, symbol in enumerate(text):
|
||||||
if symbol == '_' and pos > 0:
|
if symbol == "_" and pos > 0:
|
||||||
need_upper = True
|
need_upper = True
|
||||||
else:
|
else:
|
||||||
if need_upper:
|
if need_upper:
|
||||||
|
|
@ -123,7 +123,7 @@ class HelperMode(Helper):
|
||||||
elif mode == cls.snake_case:
|
elif mode == cls.snake_case:
|
||||||
return cls._snake_case(text)
|
return cls._snake_case(text)
|
||||||
elif mode == cls.lowercase:
|
elif mode == cls.lowercase:
|
||||||
return cls._snake_case(text).replace('_', '')
|
return cls._snake_case(text).replace("_", "")
|
||||||
elif mode == cls.lowerCamelCase:
|
elif mode == cls.lowerCamelCase:
|
||||||
return cls._camel_case(text)
|
return cls._camel_case(text)
|
||||||
elif mode == cls.CamelCase:
|
elif mode == cls.CamelCase:
|
||||||
|
|
@ -149,10 +149,10 @@ class Item:
|
||||||
|
|
||||||
def __set_name__(self, owner, name):
|
def __set_name__(self, owner, name):
|
||||||
if not name.isupper():
|
if not name.isupper():
|
||||||
raise NameError('Name for helper item must be in uppercase!')
|
raise NameError("Name for helper item must be in uppercase!")
|
||||||
if not self._value:
|
if not self._value:
|
||||||
if hasattr(owner, 'mode'):
|
if hasattr(owner, "mode"):
|
||||||
self._value = HelperMode.apply(name, getattr(owner, 'mode'))
|
self._value = HelperMode.apply(name, getattr(owner, "mode"))
|
||||||
|
|
||||||
|
|
||||||
class ListItem(Item):
|
class ListItem(Item):
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
JSON = 'json'
|
JSON = "json"
|
||||||
RAPIDJSON = 'rapidjson'
|
RAPIDJSON = "rapidjson"
|
||||||
UJSON = 'ujson'
|
UJSON = "ujson"
|
||||||
|
|
||||||
# Detect mode
|
# Detect mode
|
||||||
mode = JSON
|
mode = JSON
|
||||||
for json_lib in (RAPIDJSON, UJSON):
|
for json_lib in (RAPIDJSON, UJSON):
|
||||||
if 'DISABLE_' + json_lib.upper() in os.environ:
|
if "DISABLE_" + json_lib.upper() in os.environ:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -20,30 +20,35 @@ for json_lib in (RAPIDJSON, UJSON):
|
||||||
break
|
break
|
||||||
|
|
||||||
if mode == RAPIDJSON:
|
if mode == RAPIDJSON:
|
||||||
def dumps(data):
|
|
||||||
return json.dumps(data, ensure_ascii=False, number_mode=json.NM_NATIVE,
|
|
||||||
datetime_mode=json.DM_ISO8601 | json.DM_NAIVE_IS_UTC)
|
|
||||||
|
|
||||||
|
def dumps(data):
|
||||||
|
return json.dumps(
|
||||||
|
data,
|
||||||
|
ensure_ascii=False,
|
||||||
|
number_mode=json.NM_NATIVE,
|
||||||
|
datetime_mode=json.DM_ISO8601 | json.DM_NAIVE_IS_UTC,
|
||||||
|
)
|
||||||
|
|
||||||
def loads(data):
|
def loads(data):
|
||||||
return json.loads(data, number_mode=json.NM_NATIVE,
|
return json.loads(
|
||||||
datetime_mode=json.DM_ISO8601 | json.DM_NAIVE_IS_UTC)
|
data, number_mode=json.NM_NATIVE, datetime_mode=json.DM_ISO8601 | json.DM_NAIVE_IS_UTC
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
elif mode == UJSON:
|
elif mode == UJSON:
|
||||||
|
|
||||||
def loads(data):
|
def loads(data):
|
||||||
return json.loads(data)
|
return json.loads(data)
|
||||||
|
|
||||||
|
|
||||||
def dumps(data):
|
def dumps(data):
|
||||||
return json.dumps(data, ensure_ascii=False)
|
return json.dumps(data, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
def dumps(data):
|
def dumps(data):
|
||||||
return json.dumps(data, ensure_ascii=False)
|
return json.dumps(data, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
def loads(data):
|
def loads(data):
|
||||||
return json.loads(data)
|
return json.loads(data)
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,32 @@
|
||||||
LIST_MD_SYMBOLS = '*_`['
|
LIST_MD_SYMBOLS = "*_`["
|
||||||
|
|
||||||
MD_SYMBOLS = (
|
MD_SYMBOLS = (
|
||||||
(LIST_MD_SYMBOLS[0], LIST_MD_SYMBOLS[0]),
|
(LIST_MD_SYMBOLS[0], LIST_MD_SYMBOLS[0]),
|
||||||
(LIST_MD_SYMBOLS[1], LIST_MD_SYMBOLS[1]),
|
(LIST_MD_SYMBOLS[1], LIST_MD_SYMBOLS[1]),
|
||||||
(LIST_MD_SYMBOLS[2], LIST_MD_SYMBOLS[2]),
|
(LIST_MD_SYMBOLS[2], LIST_MD_SYMBOLS[2]),
|
||||||
(LIST_MD_SYMBOLS[2] * 3 + '\n', '\n' + LIST_MD_SYMBOLS[2] * 3),
|
(LIST_MD_SYMBOLS[2] * 3 + "\n", "\n" + LIST_MD_SYMBOLS[2] * 3),
|
||||||
('<b>', '</b>'),
|
("<b>", "</b>"),
|
||||||
('<i>', '</i>'),
|
("<i>", "</i>"),
|
||||||
('<code>', '</code>'),
|
("<code>", "</code>"),
|
||||||
('<pre>', '</pre>'),
|
("<pre>", "</pre>"),
|
||||||
)
|
)
|
||||||
|
|
||||||
HTML_QUOTES_MAP = {
|
HTML_QUOTES_MAP = {"<": "<", ">": ">", "&": "&", '"': """}
|
||||||
'<': '<',
|
|
||||||
'>': '>',
|
|
||||||
'&': '&',
|
|
||||||
'"': '"'
|
|
||||||
}
|
|
||||||
|
|
||||||
_HQS = HTML_QUOTES_MAP.keys() # HQS for HTML QUOTES SYMBOLS
|
_HQS = HTML_QUOTES_MAP.keys() # HQS for HTML QUOTES SYMBOLS
|
||||||
|
|
||||||
|
|
||||||
def _join(*content, sep=' '):
|
def _join(*content, sep=" "):
|
||||||
return sep.join(map(str, content))
|
return sep.join(map(str, content))
|
||||||
|
|
||||||
|
|
||||||
def _escape(s, symbols=LIST_MD_SYMBOLS):
|
def _escape(s, symbols=LIST_MD_SYMBOLS):
|
||||||
for symbol in symbols:
|
for symbol in symbols:
|
||||||
s = s.replace(symbol, '\\' + symbol)
|
s = s.replace(symbol, "\\" + symbol)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
def _md(string, symbols=('', '')):
|
def _md(string, symbols=("", "")):
|
||||||
start, end = symbols
|
start, end = symbols
|
||||||
return start + string + end
|
return start + string + end
|
||||||
|
|
||||||
|
|
@ -47,13 +42,13 @@ def quote_html(content):
|
||||||
:param content: str
|
:param content: str
|
||||||
:return: str
|
:return: str
|
||||||
"""
|
"""
|
||||||
new_content = ''
|
new_content = ""
|
||||||
for symbol in content:
|
for symbol in content:
|
||||||
new_content += HTML_QUOTES_MAP[symbol] if symbol in _HQS else symbol
|
new_content += HTML_QUOTES_MAP[symbol] if symbol in _HQS else symbol
|
||||||
return new_content
|
return new_content
|
||||||
|
|
||||||
|
|
||||||
def text(*content, sep=' '):
|
def text(*content, sep=" "):
|
||||||
"""
|
"""
|
||||||
Join all elements with a separator
|
Join all elements with a separator
|
||||||
|
|
||||||
|
|
@ -64,7 +59,7 @@ def text(*content, sep=' '):
|
||||||
return _join(*content, sep=sep)
|
return _join(*content, sep=sep)
|
||||||
|
|
||||||
|
|
||||||
def bold(*content, sep=' '):
|
def bold(*content, sep=" "):
|
||||||
"""
|
"""
|
||||||
Make bold text (Markdown)
|
Make bold text (Markdown)
|
||||||
|
|
||||||
|
|
@ -75,7 +70,7 @@ def bold(*content, sep=' '):
|
||||||
return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[0])
|
return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[0])
|
||||||
|
|
||||||
|
|
||||||
def hbold(*content, sep=' '):
|
def hbold(*content, sep=" "):
|
||||||
"""
|
"""
|
||||||
Make bold text (HTML)
|
Make bold text (HTML)
|
||||||
|
|
||||||
|
|
@ -86,7 +81,7 @@ def hbold(*content, sep=' '):
|
||||||
return _md(quote_html(_join(*content, sep=sep)), symbols=MD_SYMBOLS[4])
|
return _md(quote_html(_join(*content, sep=sep)), symbols=MD_SYMBOLS[4])
|
||||||
|
|
||||||
|
|
||||||
def italic(*content, sep=' '):
|
def italic(*content, sep=" "):
|
||||||
"""
|
"""
|
||||||
Make italic text (Markdown)
|
Make italic text (Markdown)
|
||||||
|
|
||||||
|
|
@ -97,7 +92,7 @@ def italic(*content, sep=' '):
|
||||||
return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[1])
|
return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[1])
|
||||||
|
|
||||||
|
|
||||||
def hitalic(*content, sep=' '):
|
def hitalic(*content, sep=" "):
|
||||||
"""
|
"""
|
||||||
Make italic text (HTML)
|
Make italic text (HTML)
|
||||||
|
|
||||||
|
|
@ -108,7 +103,7 @@ def hitalic(*content, sep=' '):
|
||||||
return _md(quote_html(_join(*content, sep=sep)), symbols=MD_SYMBOLS[5])
|
return _md(quote_html(_join(*content, sep=sep)), symbols=MD_SYMBOLS[5])
|
||||||
|
|
||||||
|
|
||||||
def code(*content, sep=' '):
|
def code(*content, sep=" "):
|
||||||
"""
|
"""
|
||||||
Make mono-width text (Markdown)
|
Make mono-width text (Markdown)
|
||||||
|
|
||||||
|
|
@ -119,7 +114,7 @@ def code(*content, sep=' '):
|
||||||
return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[2])
|
return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[2])
|
||||||
|
|
||||||
|
|
||||||
def hcode(*content, sep=' '):
|
def hcode(*content, sep=" "):
|
||||||
"""
|
"""
|
||||||
Make mono-width text (HTML)
|
Make mono-width text (HTML)
|
||||||
|
|
||||||
|
|
@ -130,7 +125,7 @@ def hcode(*content, sep=' '):
|
||||||
return _md(quote_html(_join(*content, sep=sep)), symbols=MD_SYMBOLS[6])
|
return _md(quote_html(_join(*content, sep=sep)), symbols=MD_SYMBOLS[6])
|
||||||
|
|
||||||
|
|
||||||
def pre(*content, sep='\n'):
|
def pre(*content, sep="\n"):
|
||||||
"""
|
"""
|
||||||
Make mono-width text block (Markdown)
|
Make mono-width text block (Markdown)
|
||||||
|
|
||||||
|
|
@ -141,7 +136,7 @@ def pre(*content, sep='\n'):
|
||||||
return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[3])
|
return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[3])
|
||||||
|
|
||||||
|
|
||||||
def hpre(*content, sep='\n'):
|
def hpre(*content, sep="\n"):
|
||||||
"""
|
"""
|
||||||
Make mono-width text block (HTML)
|
Make mono-width text block (HTML)
|
||||||
|
|
||||||
|
|
@ -174,7 +169,7 @@ def hlink(title, url):
|
||||||
return '<a href="{0}">{1}</a>'.format(url, quote_html(title))
|
return '<a href="{0}">{1}</a>'.format(url, quote_html(title))
|
||||||
|
|
||||||
|
|
||||||
def escape_md(*content, sep=' '):
|
def escape_md(*content, sep=" "):
|
||||||
"""
|
"""
|
||||||
Escape markdown text
|
Escape markdown text
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import contextvars
|
import contextvars
|
||||||
from typing import TypeVar, Type
|
from typing import TypeVar, Type
|
||||||
|
|
||||||
__all__ = ('DataMixin', 'ContextInstanceMixin')
|
__all__ = ("DataMixin", "ContextInstanceMixin")
|
||||||
|
|
||||||
|
|
||||||
class DataMixin:
|
class DataMixin:
|
||||||
@property
|
@property
|
||||||
def data(self):
|
def data(self):
|
||||||
data = getattr(self, '_data', None)
|
data = getattr(self, "_data", None)
|
||||||
if data is None:
|
if data is None:
|
||||||
data = {}
|
data = {}
|
||||||
setattr(self, '_data', data)
|
setattr(self, "_data", data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
|
|
@ -26,12 +26,12 @@ class DataMixin:
|
||||||
return self.data.get(key, default)
|
return self.data.get(key, default)
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class ContextInstanceMixin:
|
class ContextInstanceMixin:
|
||||||
def __init_subclass__(cls, **kwargs):
|
def __init_subclass__(cls, **kwargs):
|
||||||
cls.__context_instance = contextvars.ContextVar('instance_' + cls.__name__)
|
cls.__context_instance = contextvars.ContextVar("instance_" + cls.__name__)
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -43,5 +43,7 @@ class ContextInstanceMixin:
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_current(cls: Type[T], value: T):
|
def set_current(cls: Type[T], value: T):
|
||||||
if not isinstance(value, cls):
|
if not isinstance(value, cls):
|
||||||
raise TypeError(f"Value should be instance of '{cls.__name__}' not '{type(value).__name__}'")
|
raise TypeError(
|
||||||
|
f"Value should be instance of '{cls.__name__}' not '{type(value).__name__}'"
|
||||||
|
)
|
||||||
cls.__context_instance.set(value)
|
cls.__context_instance.set(value)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ def split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]:
|
||||||
:return: list of parts
|
:return: list of parts
|
||||||
:rtype: :obj:`typing.List[str]`
|
:rtype: :obj:`typing.List[str]`
|
||||||
"""
|
"""
|
||||||
return [text[i:i + length] for i in range(0, len(text), length)]
|
return [text[i : i + length] for i in range(0, len(text), length)]
|
||||||
|
|
||||||
|
|
||||||
def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]:
|
def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]:
|
||||||
|
|
@ -30,7 +30,7 @@ def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[
|
||||||
while temp_text:
|
while temp_text:
|
||||||
if len(temp_text) > length:
|
if len(temp_text) > length:
|
||||||
try:
|
try:
|
||||||
split_pos = temp_text[:length].rindex(' ')
|
split_pos = temp_text[:length].rindex(" ")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
split_pos = length
|
split_pos = length
|
||||||
if split_pos < length // 4 * 3:
|
if split_pos < length // 4 * 3:
|
||||||
|
|
@ -56,4 +56,4 @@ def paginate(data: typing.Iterable, page: int = 0, limit: int = 10) -> typing.It
|
||||||
:return: sliced object
|
:return: sliced object
|
||||||
:rtype: :obj:`typing.Iterable`
|
:rtype: :obj:`typing.Iterable`
|
||||||
"""
|
"""
|
||||||
return data[page * limit:page * limit + limit]
|
return data[page * limit : page * limit + limit]
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from babel.support import LazyProxy
|
||||||
from aiogram import types
|
from aiogram import types
|
||||||
from . import json
|
from . import json
|
||||||
|
|
||||||
DEFAULT_FILTER = ['self', 'cls']
|
DEFAULT_FILTER = ["self", "cls"]
|
||||||
|
|
||||||
|
|
||||||
def generate_payload(exclude=None, **kwargs):
|
def generate_payload(exclude=None, **kwargs):
|
||||||
|
|
@ -21,10 +21,11 @@ def generate_payload(exclude=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
if exclude is None:
|
if exclude is None:
|
||||||
exclude = []
|
exclude = []
|
||||||
return {key: value for key, value in kwargs.items() if
|
return {
|
||||||
key not in exclude + DEFAULT_FILTER
|
key: value
|
||||||
and value is not None
|
for key, value in kwargs.items()
|
||||||
and not key.startswith('_')}
|
if key not in exclude + DEFAULT_FILTER and value is not None and not key.startswith("_")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _normalize(obj):
|
def _normalize(obj):
|
||||||
|
|
@ -38,7 +39,7 @@ def _normalize(obj):
|
||||||
return [_normalize(item) for item in obj]
|
return [_normalize(item) for item in obj]
|
||||||
elif isinstance(obj, dict):
|
elif isinstance(obj, dict):
|
||||||
return {k: _normalize(v) for k, v in obj.items() if v is not None}
|
return {k: _normalize(v) for k, v in obj.items() if v is not None}
|
||||||
elif hasattr(obj, 'to_python'):
|
elif hasattr(obj, "to_python"):
|
||||||
return obj.to_python()
|
return obj.to_python()
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
@ -52,7 +53,7 @@ def prepare_arg(value):
|
||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return value
|
return value
|
||||||
elif isinstance(value, (list, dict)) or hasattr(value, 'to_python'):
|
elif isinstance(value, (list, dict)) or hasattr(value, "to_python"):
|
||||||
return json.dumps(_normalize(value))
|
return json.dumps(_normalize(value))
|
||||||
elif isinstance(value, datetime.timedelta):
|
elif isinstance(value, datetime.timedelta):
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,13 @@ Welcome to aiogram's documentation!
|
||||||
:alt: Telegram Bot API
|
:alt: Telegram Bot API
|
||||||
|
|
||||||
.. image:: https://img.shields.io/readthedocs/pip/stable.svg?style=flat-square
|
.. image:: https://img.shields.io/readthedocs/pip/stable.svg?style=flat-square
|
||||||
:target: http://aiogram.readthedocs.io/en/latest/?badge=latest
|
:target: http://aiogram.readthedocs.io/en/latest/?badge=latest?style=flat-square
|
||||||
:alt: Documentation Status
|
:alt: Documentation Status
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square
|
||||||
|
:target: https://github.com/python/black
|
||||||
|
:alt: Code style: Black
|
||||||
|
|
||||||
.. image:: https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square
|
.. image:: https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square
|
||||||
:target: https://github.com/aiogram/aiogram/issues
|
:target: https://github.com/aiogram/aiogram/issues
|
||||||
:alt: Github issues
|
:alt: Github issues
|
||||||
|
|
|
||||||
|
|
@ -35,16 +35,18 @@ from aiogram.utils.executor import start_polling, start_webhook
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
# Configure arguments parser.
|
# Configure arguments parser.
|
||||||
parser = argparse.ArgumentParser(description='Python telegram bot')
|
parser = argparse.ArgumentParser(description="Python telegram bot")
|
||||||
parser.add_argument('--token', '-t', nargs='?', type=str, default=None, help='Set working directory')
|
parser.add_argument(
|
||||||
parser.add_argument('--sock', help='UNIX Socket path')
|
"--token", "-t", nargs="?", type=str, default=None, help="Set working directory"
|
||||||
parser.add_argument('--host', help='Webserver host')
|
)
|
||||||
parser.add_argument('--port', type=int, help='Webserver port')
|
parser.add_argument("--sock", help="UNIX Socket path")
|
||||||
parser.add_argument('--cert', help='Path to SSL certificate')
|
parser.add_argument("--host", help="Webserver host")
|
||||||
parser.add_argument('--pkey', help='Path to SSL private key')
|
parser.add_argument("--port", type=int, help="Webserver port")
|
||||||
parser.add_argument('--host-name', help='Set webhook host name')
|
parser.add_argument("--cert", help="Path to SSL certificate")
|
||||||
parser.add_argument('--webhook-port', type=int, help='Port for webhook (default=port)')
|
parser.add_argument("--pkey", help="Path to SSL private key")
|
||||||
parser.add_argument('--webhook-path', default='/webhook', help='Port for webhook (default=port)')
|
parser.add_argument("--host-name", help="Set webhook host name")
|
||||||
|
parser.add_argument("--webhook-port", type=int, help="Port for webhook (default=port)")
|
||||||
|
parser.add_argument("--webhook-path", default="/webhook", help="Port for webhook (default=port)")
|
||||||
|
|
||||||
|
|
||||||
async def cmd_start(message: types.Message):
|
async def cmd_start(message: types.Message):
|
||||||
|
|
@ -53,7 +55,7 @@ async def cmd_start(message: types.Message):
|
||||||
|
|
||||||
def setup_handlers(dispatcher: Dispatcher):
|
def setup_handlers(dispatcher: Dispatcher):
|
||||||
# This example has only one messages handler
|
# This example has only one messages handler
|
||||||
dispatcher.register_message_handler(cmd_start, commands=['start', 'welcome'])
|
dispatcher.register_message_handler(cmd_start, commands=["start", "welcome"])
|
||||||
|
|
||||||
|
|
||||||
async def on_startup(dispatcher, url=None, cert=None):
|
async def on_startup(dispatcher, url=None, cert=None):
|
||||||
|
|
@ -73,7 +75,7 @@ async def on_startup(dispatcher, url=None, cert=None):
|
||||||
|
|
||||||
# Set new URL for webhook
|
# Set new URL for webhook
|
||||||
if cert:
|
if cert:
|
||||||
with open(cert, 'rb') as cert_file:
|
with open(cert, "rb") as cert_file:
|
||||||
await bot.set_webhook(url, certificate=cert_file)
|
await bot.set_webhook(url, certificate=cert_file)
|
||||||
else:
|
else:
|
||||||
await bot.set_webhook(url)
|
await bot.set_webhook(url)
|
||||||
|
|
@ -83,7 +85,7 @@ async def on_startup(dispatcher, url=None, cert=None):
|
||||||
|
|
||||||
|
|
||||||
async def on_shutdown(dispatcher):
|
async def on_shutdown(dispatcher):
|
||||||
print('Shutdown.')
|
print("Shutdown.")
|
||||||
|
|
||||||
|
|
||||||
def main(arguments):
|
def main(arguments):
|
||||||
|
|
@ -99,8 +101,8 @@ def main(arguments):
|
||||||
webhook_path = args.webhook_path
|
webhook_path = args.webhook_path
|
||||||
|
|
||||||
# Fi webhook path
|
# Fi webhook path
|
||||||
if not webhook_path.startswith('/'):
|
if not webhook_path.startswith("/"):
|
||||||
webhook_path = '/' + webhook_path
|
webhook_path = "/" + webhook_path
|
||||||
|
|
||||||
# Generate webhook URL
|
# Generate webhook URL
|
||||||
webhook_url = f"https://{host_name}:{webhook_port}{webhook_path}"
|
webhook_url = f"https://{host_name}:{webhook_port}{webhook_path}"
|
||||||
|
|
@ -116,15 +118,21 @@ def main(arguments):
|
||||||
else:
|
else:
|
||||||
ssl_context = None
|
ssl_context = None
|
||||||
|
|
||||||
start_webhook(dispatcher, webhook_path,
|
start_webhook(
|
||||||
on_startup=functools.partial(on_startup, url=webhook_url, cert=cert),
|
dispatcher,
|
||||||
on_shutdown=on_shutdown,
|
webhook_path,
|
||||||
host=host, port=port, path=sock, ssl_context=ssl_context)
|
on_startup=functools.partial(on_startup, url=webhook_url, cert=cert),
|
||||||
|
on_shutdown=on_shutdown,
|
||||||
|
host=host,
|
||||||
|
port=port,
|
||||||
|
path=sock,
|
||||||
|
ssl_context=ssl_context,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
start_polling(dispatcher, on_startup=on_startup, on_shutdown=on_shutdown)
|
start_polling(dispatcher, on_startup=on_startup, on_shutdown=on_shutdown)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
|
|
||||||
if not len(argv):
|
if not len(argv):
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import logging
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
from aiogram.utils import exceptions, executor
|
from aiogram.utils import exceptions, executor
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
API_TOKEN = "BOT TOKEN HERE"
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
log = logging.getLogger('broadcast')
|
log = logging.getLogger("broadcast")
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
bot = Bot(token=API_TOKEN, loop=loop, parse_mode=types.ParseMode.HTML)
|
bot = Bot(token=API_TOKEN, loop=loop, parse_mode=types.ParseMode.HTML)
|
||||||
|
|
@ -61,15 +61,15 @@ async def broadcaster() -> int:
|
||||||
count = 0
|
count = 0
|
||||||
try:
|
try:
|
||||||
for user_id in get_users():
|
for user_id in get_users():
|
||||||
if await send_message(user_id, '<b>Hello!</b>'):
|
if await send_message(user_id, "<b>Hello!</b>"):
|
||||||
count += 1
|
count += 1
|
||||||
await asyncio.sleep(.05) # 20 messages per second (Limit: 30 messages per second)
|
await asyncio.sleep(0.05) # 20 messages per second (Limit: 30 messages per second)
|
||||||
finally:
|
finally:
|
||||||
log.info(f"{count} messages successful sent.")
|
log.info(f"{count} messages successful sent.")
|
||||||
|
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
# Execute broadcaster
|
# Execute broadcaster
|
||||||
executor.start(dp, broadcaster())
|
executor.start(dp, broadcaster())
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from aiogram.utils.exceptions import MessageNotModified, Throttled
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
API_TOKEN = "BOT TOKEN HERE"
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
bot = Bot(token=API_TOKEN, loop=loop, parse_mode=types.ParseMode.HTML)
|
bot = Bot(token=API_TOKEN, loop=loop, parse_mode=types.ParseMode.HTML)
|
||||||
|
|
@ -21,16 +21,17 @@ dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
POSTS = {
|
POSTS = {
|
||||||
str(uuid.uuid4()): {
|
str(uuid.uuid4()): {
|
||||||
'title': f"Post {index}",
|
"title": f"Post {index}",
|
||||||
'body': 'Lorem ipsum dolor sit amet, '
|
"body": "Lorem ipsum dolor sit amet, "
|
||||||
'consectetur adipiscing elit, '
|
"consectetur adipiscing elit, "
|
||||||
'sed do eiusmod tempor incididunt ut '
|
"sed do eiusmod tempor incididunt ut "
|
||||||
'labore et dolore magna aliqua',
|
"labore et dolore magna aliqua",
|
||||||
'votes': random.randint(-2, 5)
|
"votes": random.randint(-2, 5),
|
||||||
} for index in range(1, 6)
|
}
|
||||||
|
for index in range(1, 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
posts_cb = CallbackData('post', 'id', 'action') # post:<id>:<action>
|
posts_cb = CallbackData("post", "id", "action") # post:<id>:<action>
|
||||||
|
|
||||||
|
|
||||||
def get_keyboard() -> types.InlineKeyboardMarkup:
|
def get_keyboard() -> types.InlineKeyboardMarkup:
|
||||||
|
|
@ -41,69 +42,73 @@ def get_keyboard() -> types.InlineKeyboardMarkup:
|
||||||
for post_id, post in POSTS.items():
|
for post_id, post in POSTS.items():
|
||||||
markup.add(
|
markup.add(
|
||||||
types.InlineKeyboardButton(
|
types.InlineKeyboardButton(
|
||||||
post['title'],
|
post["title"], callback_data=posts_cb.new(id=post_id, action="view")
|
||||||
callback_data=posts_cb.new(id=post_id, action='view'))
|
)
|
||||||
)
|
)
|
||||||
return markup
|
return markup
|
||||||
|
|
||||||
|
|
||||||
def format_post(post_id: str, post: dict) -> (str, types.InlineKeyboardMarkup):
|
def format_post(post_id: str, post: dict) -> (str, types.InlineKeyboardMarkup):
|
||||||
text = f"{md.hbold(post['title'])}\n" \
|
text = (
|
||||||
f"{md.quote_html(post['body'])}\n" \
|
f"{md.hbold(post['title'])}\n"
|
||||||
f"\n" \
|
f"{md.quote_html(post['body'])}\n"
|
||||||
|
f"\n"
|
||||||
f"Votes: {post['votes']}"
|
f"Votes: {post['votes']}"
|
||||||
|
)
|
||||||
|
|
||||||
markup = types.InlineKeyboardMarkup()
|
markup = types.InlineKeyboardMarkup()
|
||||||
markup.row(
|
markup.row(
|
||||||
types.InlineKeyboardButton('👍', callback_data=posts_cb.new(id=post_id, action='like')),
|
types.InlineKeyboardButton("👍", callback_data=posts_cb.new(id=post_id, action="like")),
|
||||||
types.InlineKeyboardButton('👎', callback_data=posts_cb.new(id=post_id, action='unlike')),
|
types.InlineKeyboardButton("👎", callback_data=posts_cb.new(id=post_id, action="unlike")),
|
||||||
|
)
|
||||||
|
markup.add(
|
||||||
|
types.InlineKeyboardButton("<< Back", callback_data=posts_cb.new(id="-", action="list"))
|
||||||
)
|
)
|
||||||
markup.add(types.InlineKeyboardButton('<< Back', callback_data=posts_cb.new(id='-', action='list')))
|
|
||||||
return text, markup
|
return text, markup
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands='start')
|
@dp.message_handler(commands="start")
|
||||||
async def cmd_start(message: types.Message):
|
async def cmd_start(message: types.Message):
|
||||||
await message.reply('Posts', reply_markup=get_keyboard())
|
await message.reply("Posts", reply_markup=get_keyboard())
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query_handler(posts_cb.filter(action='list'))
|
@dp.callback_query_handler(posts_cb.filter(action="list"))
|
||||||
async def query_show_list(query: types.CallbackQuery):
|
async def query_show_list(query: types.CallbackQuery):
|
||||||
await query.message.edit_text('Posts', reply_markup=get_keyboard())
|
await query.message.edit_text("Posts", reply_markup=get_keyboard())
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query_handler(posts_cb.filter(action='view'))
|
@dp.callback_query_handler(posts_cb.filter(action="view"))
|
||||||
async def query_view(query: types.CallbackQuery, callback_data: dict):
|
async def query_view(query: types.CallbackQuery, callback_data: dict):
|
||||||
post_id = callback_data['id']
|
post_id = callback_data["id"]
|
||||||
|
|
||||||
post = POSTS.get(post_id, None)
|
post = POSTS.get(post_id, None)
|
||||||
if not post:
|
if not post:
|
||||||
return await query.answer('Unknown post!')
|
return await query.answer("Unknown post!")
|
||||||
|
|
||||||
text, markup = format_post(post_id, post)
|
text, markup = format_post(post_id, post)
|
||||||
await query.message.edit_text(text, reply_markup=markup)
|
await query.message.edit_text(text, reply_markup=markup)
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query_handler(posts_cb.filter(action=['like', 'unlike']))
|
@dp.callback_query_handler(posts_cb.filter(action=["like", "unlike"]))
|
||||||
async def query_post_vote(query: types.CallbackQuery, callback_data: dict):
|
async def query_post_vote(query: types.CallbackQuery, callback_data: dict):
|
||||||
try:
|
try:
|
||||||
await dp.throttle('vote', rate=1)
|
await dp.throttle("vote", rate=1)
|
||||||
except Throttled:
|
except Throttled:
|
||||||
return await query.answer('Too many requests.')
|
return await query.answer("Too many requests.")
|
||||||
|
|
||||||
post_id = callback_data['id']
|
post_id = callback_data["id"]
|
||||||
action = callback_data['action']
|
action = callback_data["action"]
|
||||||
|
|
||||||
post = POSTS.get(post_id, None)
|
post = POSTS.get(post_id, None)
|
||||||
if not post:
|
if not post:
|
||||||
return await query.answer('Unknown post!')
|
return await query.answer("Unknown post!")
|
||||||
|
|
||||||
if action == 'like':
|
if action == "like":
|
||||||
post['votes'] += 1
|
post["votes"] += 1
|
||||||
elif action == 'unlike':
|
elif action == "unlike":
|
||||||
post['votes'] -= 1
|
post["votes"] -= 1
|
||||||
|
|
||||||
await query.answer('Voted.')
|
await query.answer("Voted.")
|
||||||
text, markup = format_post(post_id, post)
|
text, markup = format_post(post_id, post)
|
||||||
await query.message.edit_text(text, reply_markup=markup)
|
await query.message.edit_text(text, reply_markup=markup)
|
||||||
|
|
||||||
|
|
@ -113,5 +118,5 @@ async def message_not_modified_handler(update, error):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
executor.start_polling(dp, loop=loop, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import logging
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, executor, md, types
|
from aiogram import Bot, Dispatcher, executor, md, types
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
API_TOKEN = "BOT TOKEN HERE"
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
@ -20,14 +20,17 @@ dp = Dispatcher(bot)
|
||||||
async def check_language(message: types.Message):
|
async def check_language(message: types.Message):
|
||||||
locale = message.from_user.locale
|
locale = message.from_user.locale
|
||||||
|
|
||||||
await message.reply(md.text(
|
await message.reply(
|
||||||
md.bold('Info about your language:'),
|
md.text(
|
||||||
md.text(' 🔸', md.bold('Code:'), md.italic(locale.locale)),
|
md.bold("Info about your language:"),
|
||||||
md.text(' 🔸', md.bold('Territory:'), md.italic(locale.territory or 'Unknown')),
|
md.text(" 🔸", md.bold("Code:"), md.italic(locale.locale)),
|
||||||
md.text(' 🔸', md.bold('Language name:'), md.italic(locale.language_name)),
|
md.text(" 🔸", md.bold("Territory:"), md.italic(locale.territory or "Unknown")),
|
||||||
md.text(' 🔸', md.bold('English language name:'), md.italic(locale.english_name)),
|
md.text(" 🔸", md.bold("Language name:"), md.italic(locale.language_name)),
|
||||||
sep='\n'))
|
md.text(" 🔸", md.bold("English language name:"), md.italic(locale.english_name)),
|
||||||
|
sep="\n",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
executor.start_polling(dp, loop=loop, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import logging
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, executor, types
|
from aiogram import Bot, Dispatcher, executor, types
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
API_TOKEN = "BOT TOKEN HERE"
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
@ -17,7 +17,7 @@ bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['start', 'help'])
|
@dp.message_handler(commands=["start", "help"])
|
||||||
async def send_welcome(message: types.Message):
|
async def send_welcome(message: types.Message):
|
||||||
"""
|
"""
|
||||||
This handler will be called when client send `/start` or `/help` commands.
|
This handler will be called when client send `/start` or `/help` commands.
|
||||||
|
|
@ -25,11 +25,15 @@ async def send_welcome(message: types.Message):
|
||||||
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
|
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(regexp='(^cat[s]?$|puss)')
|
@dp.message_handler(regexp="(^cat[s]?$|puss)")
|
||||||
async def cats(message: types.Message):
|
async def cats(message: types.Message):
|
||||||
with open('data/cats.jpg', 'rb') as photo:
|
with open("data/cats.jpg", "rb") as photo:
|
||||||
await bot.send_photo(message.chat.id, photo, caption='Cats is here 😺',
|
await bot.send_photo(
|
||||||
reply_to_message_id=message.message_id)
|
message.chat.id,
|
||||||
|
photo,
|
||||||
|
caption="Cats is here 😺",
|
||||||
|
reply_to_message_id=message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler()
|
@dp.message_handler()
|
||||||
|
|
@ -37,5 +41,5 @@ async def echo(message: types.Message):
|
||||||
await bot.send_message(message.chat.id, message.text)
|
await bot.send_message(message.chat.id, message.text)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
executor.start_polling(dp, skip_updates=True)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||||
from aiogram.types import ParseMode
|
from aiogram.types import ParseMode
|
||||||
from aiogram.utils import executor
|
from aiogram.utils import executor
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
API_TOKEN = "BOT TOKEN HERE"
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ class Form(StatesGroup):
|
||||||
gender = State() # Will be represented in storage as 'Form:gender'
|
gender = State() # Will be represented in storage as 'Form:gender'
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=["start"])
|
||||||
async def cmd_start(message: types.Message):
|
async def cmd_start(message: types.Message):
|
||||||
"""
|
"""
|
||||||
Conversation's entry point
|
Conversation's entry point
|
||||||
|
|
@ -39,9 +39,11 @@ async def cmd_start(message: types.Message):
|
||||||
|
|
||||||
|
|
||||||
# You can use state '*' if you need to handle all states
|
# You can use state '*' if you need to handle all states
|
||||||
@dp.message_handler(state='*', commands=['cancel'])
|
@dp.message_handler(state="*", commands=["cancel"])
|
||||||
@dp.message_handler(lambda message: message.text.lower() == 'cancel', state='*')
|
@dp.message_handler(lambda message: message.text.lower() == "cancel", state="*")
|
||||||
async def cancel_handler(message: types.Message, state: FSMContext, raw_state: Optional[str] = None):
|
async def cancel_handler(
|
||||||
|
message: types.Message, state: FSMContext, raw_state: Optional[str] = None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Allow user to cancel any action
|
Allow user to cancel any action
|
||||||
"""
|
"""
|
||||||
|
|
@ -51,7 +53,7 @@ async def cancel_handler(message: types.Message, state: FSMContext, raw_state: O
|
||||||
# Cancel state and inform user about it
|
# Cancel state and inform user about it
|
||||||
await state.finish()
|
await state.finish()
|
||||||
# And remove keyboard (just in case)
|
# And remove keyboard (just in case)
|
||||||
await message.reply('Canceled.', reply_markup=types.ReplyKeyboardRemove())
|
await message.reply("Canceled.", reply_markup=types.ReplyKeyboardRemove())
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(state=Form.name)
|
@dp.message_handler(state=Form.name)
|
||||||
|
|
@ -60,7 +62,7 @@ async def process_name(message: types.Message, state: FSMContext):
|
||||||
Process user name
|
Process user name
|
||||||
"""
|
"""
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['name'] = message.text
|
data["name"] = message.text
|
||||||
|
|
||||||
await Form.next()
|
await Form.next()
|
||||||
await message.reply("How old are you?")
|
await message.reply("How old are you?")
|
||||||
|
|
@ -89,7 +91,9 @@ async def process_age(message: types.Message, state: FSMContext):
|
||||||
await message.reply("What is your gender?", reply_markup=markup)
|
await message.reply("What is your gender?", reply_markup=markup)
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(lambda message: message.text not in ["Male", "Female", "Other"], state=Form.gender)
|
@dp.message_handler(
|
||||||
|
lambda message: message.text not in ["Male", "Female", "Other"], state=Form.gender
|
||||||
|
)
|
||||||
async def failed_process_gender(message: types.Message):
|
async def failed_process_gender(message: types.Message):
|
||||||
"""
|
"""
|
||||||
In this example gender has to be one of: Male, Female, Other.
|
In this example gender has to be one of: Male, Female, Other.
|
||||||
|
|
@ -100,21 +104,27 @@ async def failed_process_gender(message: types.Message):
|
||||||
@dp.message_handler(state=Form.gender)
|
@dp.message_handler(state=Form.gender)
|
||||||
async def process_gender(message: types.Message, state: FSMContext):
|
async def process_gender(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['gender'] = message.text
|
data["gender"] = message.text
|
||||||
|
|
||||||
# Remove keyboard
|
# Remove keyboard
|
||||||
markup = types.ReplyKeyboardRemove()
|
markup = types.ReplyKeyboardRemove()
|
||||||
|
|
||||||
# And send message
|
# And send message
|
||||||
await bot.send_message(message.chat.id, md.text(
|
await bot.send_message(
|
||||||
md.text('Hi! Nice to meet you,', md.bold(data['name'])),
|
message.chat.id,
|
||||||
md.text('Age:', data['age']),
|
md.text(
|
||||||
md.text('Gender:', data['gender']),
|
md.text("Hi! Nice to meet you,", md.bold(data["name"])),
|
||||||
sep='\n'), reply_markup=markup, parse_mode=ParseMode.MARKDOWN)
|
md.text("Age:", data["age"]),
|
||||||
|
md.text("Gender:", data["gender"]),
|
||||||
|
sep="\n",
|
||||||
|
),
|
||||||
|
reply_markup=markup,
|
||||||
|
parse_mode=ParseMode.MARKDOWN,
|
||||||
|
)
|
||||||
|
|
||||||
# Finish conversation
|
# Finish conversation
|
||||||
data.state = None
|
data.state = None
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
executor.start_polling(dp, loop=loop, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ from pathlib import Path
|
||||||
from aiogram import Bot, Dispatcher, executor, types
|
from aiogram import Bot, Dispatcher, executor, types
|
||||||
from aiogram.contrib.middlewares.i18n import I18nMiddleware
|
from aiogram.contrib.middlewares.i18n import I18nMiddleware
|
||||||
|
|
||||||
TOKEN = 'BOT TOKEN HERE'
|
TOKEN = "BOT TOKEN HERE"
|
||||||
I18N_DOMAIN = 'mybot'
|
I18N_DOMAIN = "mybot"
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).parent
|
BASE_DIR = Path(__file__).parent
|
||||||
LOCALES_DIR = BASE_DIR / 'locales'
|
LOCALES_DIR = BASE_DIR / "locales"
|
||||||
|
|
||||||
bot = Bot(TOKEN, parse_mode=types.ParseMode.HTML)
|
bot = Bot(TOKEN, parse_mode=types.ParseMode.HTML)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
@ -41,16 +41,16 @@ dp.middleware.setup(i18n)
|
||||||
_ = i18n.gettext
|
_ = i18n.gettext
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=["start"])
|
||||||
async def cmd_start(message: types.Message):
|
async def cmd_start(message: types.Message):
|
||||||
# Simply use `_('message')` instead of `'message'` and never use f-strings for translatable texts.
|
# Simply use `_('message')` instead of `'message'` and never use f-strings for translatable texts.
|
||||||
await message.reply(_('Hello, <b>{user}</b>!').format(user=message.from_user.full_name))
|
await message.reply(_("Hello, <b>{user}</b>!").format(user=message.from_user.full_name))
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['lang'])
|
@dp.message_handler(commands=["lang"])
|
||||||
async def cmd_lang(message: types.Message, locale):
|
async def cmd_lang(message: types.Message, locale):
|
||||||
await message.reply(_('Your current language: <i>{language}</i>').format(language=locale))
|
await message.reply(_("Your current language: <i>{language}</i>").format(language=locale))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
executor.start_polling(dp, skip_updates=True)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import logging
|
||||||
|
|
||||||
from aiogram import Bot, types, Dispatcher, executor
|
from aiogram import Bot, types, Dispatcher, executor
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
API_TOKEN = "BOT TOKEN HERE"
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
@ -14,11 +14,12 @@ dp = Dispatcher(bot)
|
||||||
|
|
||||||
@dp.inline_handler()
|
@dp.inline_handler()
|
||||||
async def inline_echo(inline_query: types.InlineQuery):
|
async def inline_echo(inline_query: types.InlineQuery):
|
||||||
input_content = types.InputTextMessageContent(inline_query.query or 'echo')
|
input_content = types.InputTextMessageContent(inline_query.query or "echo")
|
||||||
item = types.InlineQueryResultArticle(id='1', title='echo',
|
item = types.InlineQueryResultArticle(
|
||||||
input_message_content=input_content)
|
id="1", title="echo", input_message_content=input_content
|
||||||
|
)
|
||||||
await bot.answer_inline_query(inline_query.id, results=[item], cache_time=1)
|
await bot.answer_inline_query(inline_query.id, results=[item], cache_time=1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
executor.start_polling(dp, loop=loop, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import asyncio
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, executor, filters, types
|
from aiogram import Bot, Dispatcher, executor, filters, types
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
API_TOKEN = "BOT TOKEN HERE"
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
bot = Bot(token=API_TOKEN, loop=loop)
|
bot = Bot(token=API_TOKEN, loop=loop)
|
||||||
|
|
@ -24,13 +24,13 @@ async def send_welcome(message: types.Message):
|
||||||
media = types.MediaGroup()
|
media = types.MediaGroup()
|
||||||
|
|
||||||
# Attach local file
|
# Attach local file
|
||||||
media.attach_photo(types.InputFile('data/cat.jpg'), 'Cat!')
|
media.attach_photo(types.InputFile("data/cat.jpg"), "Cat!")
|
||||||
# More local files and more cats!
|
# More local files and more cats!
|
||||||
media.attach_photo(types.InputFile('data/cats.jpg'), 'More cats!')
|
media.attach_photo(types.InputFile("data/cats.jpg"), "More cats!")
|
||||||
|
|
||||||
# You can also use URL's
|
# You can also use URL's
|
||||||
# For example: get random puss:
|
# For example: get random puss:
|
||||||
media.attach_photo('http://lorempixel.com/400/200/cats/', 'Random cat.')
|
media.attach_photo("http://lorempixel.com/400/200/cats/", "Random cat.")
|
||||||
|
|
||||||
# And you can also use file ID:
|
# And you can also use file ID:
|
||||||
# media.attach_photo('<file_id>', 'cat-cat-cat.')
|
# media.attach_photo('<file_id>', 'cat-cat-cat.')
|
||||||
|
|
@ -39,5 +39,5 @@ async def send_welcome(message: types.Message):
|
||||||
await message.reply_media_group(media=media)
|
await message.reply_media_group(media=media)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
executor.start_polling(dp, loop=loop, skip_updates=True)
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue