mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Merge remote-tracking branch 'origin/dev-3.x' into dev-3.x
# Conflicts: # README.md # README.rst # aiogram/__init__.py # aiogram/bot/bot.py # aiogram/contrib/fsm_storage/redis.py # aiogram/contrib/middlewares/logging.py # aiogram/dispatcher/dispatcher.py # aiogram/dispatcher/filters/__init__.py # aiogram/dispatcher/filters/builtin.py # aiogram/dispatcher/filters/filters.py # aiogram/dispatcher/filters/state.py # aiogram/dispatcher/handler.py # aiogram/dispatcher/webhook.py # aiogram/types/base.py # aiogram/types/chat.py # aiogram/types/chat_member.py # aiogram/types/input_media.py # aiogram/types/message.py # aiogram/utils/callback_data.py # aiogram/utils/deprecated.py # aiogram/utils/exceptions.py # aiogram/utils/executor.py # aiogram/utils/helper.py # aiogram/utils/json.py # aiogram/utils/mixins.py # aiogram/utils/payload.py # dev_requirements.txt # docs/source/index.rst # examples/callback_data_factory.py # examples/check_user_language.py # examples/echo_bot.py # examples/finite_state_machine_example.py # examples/i18n_example.py # examples/inline_bot.py # examples/media_group.py # examples/middleware_and_antiflood.py # examples/payments.py # examples/proxy_and_emojize.py # examples/regexp_commands_filter_example.py # examples/throtling_example.py # examples/webhook_example.py # examples/webhook_example_2.py # setup.py # tests/test_bot.py # tests/test_token.py # tests/types/dataset.py
This commit is contained in:
commit
87393f2475
98 changed files with 5048 additions and 4854 deletions
10
.github/FUNDING.yml
vendored
10
.github/FUNDING.yml
vendored
|
|
@ -1,8 +1,2 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [JRootJunior]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: aiogram # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
custom: # Replace with a single custom sponsorship URL
|
||||
github: [JRootJunior]
|
||||
open_collective: aiogram
|
||||
|
|
|
|||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -8,7 +8,7 @@ Fixes # (issue)
|
|||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Documentstion (typos, code examples or any documentation update)
|
||||
- [ ] Documentation (typos, code examples or any documentation update)
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
|
|
|
|||
38
README.md
38
README.md
|
|
@ -1,17 +1,17 @@
|
|||
# AIOGram
|
||||
|
||||
[](https://opencollective.com/aiogram)
|
||||
[![\[Telegram\] aiogram live](https://img.shields.io/badge/telegram-aiogram-blue.svg?style=flat-square)](https://t.me/aiogram_live)
|
||||
[](https://pypi.python.org/pypi/aiogram)
|
||||
[](https://pypi.python.org/pypi/aiogram)
|
||||
[](https://pypi.python.org/pypi/aiogram)
|
||||
[](https://pypi.python.org/pypi/aiogram)
|
||||
[](https://core.telegram.org/bots/api)
|
||||
[](http://aiogram.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://github.com/python/black)
|
||||
[](https://core.telegram.org/bots/api)
|
||||
[](http://aiogram.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://github.com/aiogram/aiogram/issues)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
**aiogram** is a pretty simple and fully asynchronous library for [Telegram Bot API](https://core.telegram.org/bots/api) written in Python 3.7 with [asyncio](https://docs.python.org/3/library/asyncio.html) and [aiohttp](https://github.com/aio-libs/aiohttp). It helps you to make your bots faster and simpler.
|
||||
**aiogram** is a pretty simple and fully asynchronous framework for [Telegram Bot API](https://core.telegram.org/bots/api) written in Python 3.7 with [asyncio](https://docs.python.org/3/library/asyncio.html) and [aiohttp](https://github.com/aio-libs/aiohttp). It helps you to make your bots faster and simpler.
|
||||
|
||||
You can [read the docs here](http://aiogram.readthedocs.io/en/latest/).
|
||||
|
||||
|
|
@ -25,3 +25,33 @@ You can [read the docs here](http://aiogram.readthedocs.io/en/latest/).
|
|||
- Source: [Github repo](https://github.com/aiogram/aiogram)
|
||||
- Issues/Bug tracker: [Github issues tracker](https://github.com/aiogram/aiogram/issues)
|
||||
- Test bot: [@aiogram_bot](https://t.me/aiogram_bot)
|
||||
|
||||
## Contributors
|
||||
|
||||
### Code Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute. [[Code of conduct](CODE_OF_CONDUCT.md)].
|
||||
<a href="https://github.com/aiogram/aiogram/graphs/contributors"><img src="https://opencollective.com/aiogram/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
### Financial Contributors
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/aiogram/contribute)]
|
||||
|
||||
#### Individuals
|
||||
|
||||
<a href="https://opencollective.com/aiogram"><img src="https://opencollective.com/aiogram/individuals.svg?width=890"></a>
|
||||
|
||||
#### Organizations
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/aiogram/contribute)]
|
||||
|
||||
<a href="https://opencollective.com/aiogram/organization/0/website"><img src="https://opencollective.com/aiogram/organization/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/aiogram/organization/1/website"><img src="https://opencollective.com/aiogram/organization/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/aiogram/organization/2/website"><img src="https://opencollective.com/aiogram/organization/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/aiogram/organization/3/website"><img src="https://opencollective.com/aiogram/organization/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/aiogram/organization/4/website"><img src="https://opencollective.com/aiogram/organization/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/aiogram/organization/5/website"><img src="https://opencollective.com/aiogram/organization/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/aiogram/organization/6/website"><img src="https://opencollective.com/aiogram/organization/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/aiogram/organization/7/website"><img src="https://opencollective.com/aiogram/organization/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/aiogram/organization/8/website"><img src="https://opencollective.com/aiogram/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/aiogram/organization/9/website"><img src="https://opencollective.com/aiogram/organization/9/avatar.svg"></a>
|
||||
|
|
|
|||
12
README.rst
12
README.rst
|
|
@ -21,18 +21,14 @@ AIOGramBot
|
|||
:target: https://pypi.python.org/pypi/aiogram
|
||||
:alt: Supported python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.3-blue.svg?style=flat-square&logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.4-blue.svg?style=flat-square&logo=telegram
|
||||
:target: https://core.telegram.org/bots/api
|
||||
:alt: Telegram Bot API
|
||||
|
||||
.. image:: https://img.shields.io/readthedocs/pip/stable.svg?style=flat-square
|
||||
:target: http://aiogram.readthedocs.io/en/latest/?badge=latest?style=flat-square
|
||||
.. image:: https://img.shields.io/readthedocs/aiogram?style=flat-square
|
||||
:target: http://aiogram.readthedocs.io/en/latest/?badge=latest
|
||||
: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
|
||||
:target: https://github.com/aiogram/aiogram/issues
|
||||
:alt: Github issues
|
||||
|
|
@ -42,7 +38,7 @@ AIOGramBot
|
|||
:alt: MIT License
|
||||
|
||||
|
||||
**aiogram** is a pretty simple and fully asynchronous library for `Telegram Bot API <https://core.telegram.org/bots/api>`_ written in Python 3.7 with `asyncio <https://docs.python.org/3/library/asyncio.html>`_ and `aiohttp <https://github.com/aio-libs/aiohttp>`_. It helps you to make your bots faster and simpler.
|
||||
**aiogram** is a pretty simple and fully asynchronous framework for `Telegram Bot API <https://core.telegram.org/bots/api>`_ written in Python 3.7 with `asyncio <https://docs.python.org/3/library/asyncio.html>`_ and `aiohttp <https://github.com/aio-libs/aiohttp>`_. It helps you to make your bots faster and simpler.
|
||||
|
||||
You can `read the docs here <http://aiogram.readthedocs.io/en/latest/>`_.
|
||||
|
||||
|
|
|
|||
|
|
@ -17,26 +17,26 @@ try:
|
|||
except ImportError:
|
||||
uvloop = None
|
||||
else:
|
||||
if "DISABLE_UVLOOP" not in os.environ:
|
||||
if 'DISABLE_UVLOOP' not in os.environ:
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
|
||||
__all__ = [
|
||||
"Bot",
|
||||
"Dispatcher",
|
||||
"__api_version__",
|
||||
"__version__",
|
||||
"bot",
|
||||
"contrib",
|
||||
"dispatcher",
|
||||
"exceptions",
|
||||
"executor",
|
||||
"filters",
|
||||
"helper",
|
||||
"md",
|
||||
"middlewares",
|
||||
"types",
|
||||
"utils",
|
||||
'Bot',
|
||||
'Dispatcher',
|
||||
'__api_version__',
|
||||
'__version__',
|
||||
'bot',
|
||||
'contrib',
|
||||
'dispatcher',
|
||||
'exceptions',
|
||||
'executor',
|
||||
'filters',
|
||||
'helper',
|
||||
'md',
|
||||
'middlewares',
|
||||
'types',
|
||||
'utils'
|
||||
]
|
||||
|
||||
__version__ = "3.0.dev1"
|
||||
__api_version__ = "4.3"
|
||||
__version__ = '2.4'
|
||||
__api_version__ = '4.4'
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ class Methods(Helper):
|
|||
"""
|
||||
Helper for Telegram API Methods listed on https://core.telegram.org/bots/api
|
||||
|
||||
List is updated to Bot API 4.3
|
||||
List is updated to Bot API 4.4
|
||||
"""
|
||||
|
||||
mode = HelperMode.lowerCamelCase
|
||||
|
|
@ -191,6 +191,7 @@ class Methods(Helper):
|
|||
UNBAN_CHAT_MEMBER = Item() # unbanChatMember
|
||||
RESTRICT_CHAT_MEMBER = Item() # restrictChatMember
|
||||
PROMOTE_CHAT_MEMBER = Item() # promoteChatMember
|
||||
SET_CHAT_PERMISSIONS = Item() # setChatPermissions
|
||||
EXPORT_CHAT_INVITE_LINK = Item() # exportChatInviteLink
|
||||
SET_CHAT_PHOTO = Item() # setChatPhoto
|
||||
DELETE_CHAT_PHOTO = Item() # deleteChatPhoto
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from aiohttp.helpers import sentinel
|
|||
from . import api
|
||||
from ..types import ParseMode, base
|
||||
from ..utils import json
|
||||
from ..utils.auth_widget import check_token
|
||||
from ..utils.auth_widget import check_integrity
|
||||
|
||||
|
||||
class BaseBot:
|
||||
|
|
@ -115,6 +115,15 @@ class BaseBot:
|
|||
|
||||
self.parse_mode = parse_mode
|
||||
|
||||
def __del__(self):
|
||||
if not hasattr(self, 'loop'):
|
||||
return
|
||||
if self.loop.is_running():
|
||||
self.loop.create_task(self.close())
|
||||
return
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.run_until_complete(self.close())
|
||||
|
||||
@staticmethod
|
||||
def _prepare_timeout(
|
||||
value: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]]
|
||||
|
|
@ -302,4 +311,4 @@ class BaseBot:
|
|||
self.parse_mode = None
|
||||
|
||||
def check_auth_widget(self, data):
|
||||
return check_token(data, self.__token)
|
||||
return check_integrity(self.__token, data)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -20,6 +20,7 @@ class _FileStorage(MemoryStorage):
|
|||
pass
|
||||
|
||||
async def close(self):
|
||||
if self.data:
|
||||
self.write(self.path)
|
||||
await super(_FileStorage, self).close()
|
||||
|
||||
|
|
|
|||
200
aiogram/contrib/fsm_storage/mongo.py
Normal file
200
aiogram/contrib/fsm_storage/mongo.py
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
"""
|
||||
This module has mongo storage for finite-state machine
|
||||
based on `aiomongo <https://github.com/ZeoAlliance/aiomongo`_ driver
|
||||
"""
|
||||
|
||||
from typing import Union, Dict, Optional, List, Tuple, AnyStr
|
||||
|
||||
import aiomongo
|
||||
from aiomongo import AioMongoClient, Database
|
||||
|
||||
from ...dispatcher.storage import BaseStorage
|
||||
|
||||
STATE = 'aiogram_state'
|
||||
DATA = 'aiogram_data'
|
||||
BUCKET = 'aiogram_bucket'
|
||||
COLLECTIONS = (STATE, DATA, BUCKET)
|
||||
|
||||
|
||||
class MongoStorage(BaseStorage):
|
||||
"""
|
||||
Mongo-based storage for FSM.
|
||||
Usage:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
storage = MongoStorage(host='localhost', port=27017, db_name='aiogram_fsm')
|
||||
dp = Dispatcher(bot, storage=storage)
|
||||
|
||||
And need to close Mongo client connections when shutdown
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
await dp.storage.close()
|
||||
await dp.storage.wait_closed()
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, host='localhost', port=27017, db_name='aiogram_fsm',
|
||||
username=None, password=None, index=True, **kwargs):
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._db_name: str = db_name
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._kwargs = kwargs
|
||||
|
||||
self._mongo: Union[AioMongoClient, None] = None
|
||||
self._db: Union[Database, None] = None
|
||||
|
||||
self._index = index
|
||||
|
||||
async def get_client(self) -> AioMongoClient:
|
||||
if isinstance(self._mongo, AioMongoClient):
|
||||
return self._mongo
|
||||
|
||||
uri = 'mongodb://'
|
||||
|
||||
# set username + password
|
||||
if self._username and self._password:
|
||||
uri += f'{self._username}:{self._password}@'
|
||||
|
||||
# set host and port (optional)
|
||||
uri += f'{self._host}:{self._port}' if self._host else f'localhost:{self._port}'
|
||||
|
||||
# define and return client
|
||||
self._mongo = await aiomongo.create_client(uri)
|
||||
return self._mongo
|
||||
|
||||
async def get_db(self) -> Database:
|
||||
"""
|
||||
Get Mongo db
|
||||
|
||||
This property is awaitable.
|
||||
"""
|
||||
if isinstance(self._db, Database):
|
||||
return self._db
|
||||
|
||||
mongo = await self.get_client()
|
||||
self._db = mongo.get_database(self._db_name)
|
||||
|
||||
if self._index:
|
||||
await self.apply_index(self._db)
|
||||
return self._db
|
||||
|
||||
@staticmethod
|
||||
async def apply_index(db):
|
||||
for collection in COLLECTIONS:
|
||||
await db[collection].create_index(keys=[('chat', 1), ('user', 1)],
|
||||
name="chat_user_idx", unique=True, background=True)
|
||||
|
||||
async def close(self):
|
||||
if self._mongo:
|
||||
self._mongo.close()
|
||||
|
||||
async def wait_closed(self):
|
||||
if self._mongo:
|
||||
return await self._mongo.wait_closed()
|
||||
return True
|
||||
|
||||
async def set_state(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
state: Optional[AnyStr] = None):
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
|
||||
if state is None:
|
||||
await db[STATE].delete_one(filter={'chat': chat, 'user': user})
|
||||
else:
|
||||
await db[STATE].update_one(filter={'chat': chat, 'user': user},
|
||||
update={'$set': {'state': state}}, upsert=True)
|
||||
|
||||
async def get_state(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
default: Optional[str] = None) -> Optional[str]:
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
result = await db[STATE].find_one(filter={'chat': chat, 'user': user})
|
||||
|
||||
return result.get('state') if result else default
|
||||
|
||||
async def set_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
data: Dict = None):
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
|
||||
await db[DATA].update_one(filter={'chat': chat, 'user': user},
|
||||
update={'$set': {'data': data}}, upsert=True)
|
||||
|
||||
async def get_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
default: Optional[dict] = None) -> Dict:
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
result = await db[DATA].find_one(filter={'chat': chat, 'user': user})
|
||||
|
||||
return result.get('data') if result else default or {}
|
||||
|
||||
async def update_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
data: Dict = None, **kwargs):
|
||||
if data is None:
|
||||
data = {}
|
||||
temp_data = await self.get_data(chat=chat, user=user, default={})
|
||||
temp_data.update(data, **kwargs)
|
||||
await self.set_data(chat=chat, user=user, data=temp_data)
|
||||
|
||||
def has_bucket(self):
|
||||
return True
|
||||
|
||||
async def get_bucket(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
default: Optional[dict] = None) -> Dict:
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
result = await db[BUCKET].find_one(filter={'chat': chat, 'user': user})
|
||||
return result.get('bucket') if result else default or {}
|
||||
|
||||
async def set_bucket(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
bucket: Dict = None):
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
|
||||
await db[BUCKET].update_one(filter={'chat': chat, 'user': user},
|
||||
update={'$set': {'bucket': bucket}}, upsert=True)
|
||||
|
||||
async def update_bucket(self, *, chat: Union[str, int, None] = None,
|
||||
user: Union[str, int, None] = None,
|
||||
bucket: Dict = None, **kwargs):
|
||||
if bucket is None:
|
||||
bucket = {}
|
||||
temp_bucket = await self.get_bucket(chat=chat, user=user)
|
||||
temp_bucket.update(bucket, **kwargs)
|
||||
await self.set_bucket(chat=chat, user=user, bucket=temp_bucket)
|
||||
|
||||
async def reset_all(self, full=True):
|
||||
"""
|
||||
Reset states in DB
|
||||
|
||||
:param full: clean DB or clean only states
|
||||
:return:
|
||||
"""
|
||||
db = await self.get_db()
|
||||
|
||||
await db[STATE].drop()
|
||||
|
||||
if full:
|
||||
await db[DATA].drop()
|
||||
await db[BUCKET].drop()
|
||||
|
||||
async def get_states_list(self) -> List[Tuple[int, int]]:
|
||||
"""
|
||||
Get list of all stored chat's and user's
|
||||
|
||||
:return: list of tuples where first element is chat id and second is user id
|
||||
"""
|
||||
db = await self.get_db()
|
||||
result = []
|
||||
|
||||
items = await db[STATE].find().to_list()
|
||||
for item in items:
|
||||
result.append(
|
||||
(int(item['chat']), int(item['user']))
|
||||
)
|
||||
|
||||
return result
|
||||
|
|
@ -11,9 +11,9 @@ import aioredis
|
|||
from ...dispatcher.storage import BaseStorage
|
||||
from ...utils import json
|
||||
|
||||
STATE_KEY = "state"
|
||||
STATE_DATA_KEY = "data"
|
||||
STATE_BUCKET_KEY = "bucket"
|
||||
STATE_KEY = 'state'
|
||||
STATE_DATA_KEY = 'data'
|
||||
STATE_BUCKET_KEY = 'bucket'
|
||||
|
||||
|
||||
class RedisStorage(BaseStorage):
|
||||
|
|
@ -35,10 +35,7 @@ class RedisStorage(BaseStorage):
|
|||
await dp.storage.wait_closed()
|
||||
|
||||
"""
|
||||
|
||||
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._port = port
|
||||
self._db = db
|
||||
|
|
@ -64,28 +61,19 @@ class RedisStorage(BaseStorage):
|
|||
async def redis(self) -> aioredis.RedisConnection:
|
||||
"""
|
||||
Get Redis connection
|
||||
|
||||
This property is awaitable.
|
||||
"""
|
||||
# Use thread-safe asyncio Lock because this method without that is not safe
|
||||
async with self._connection_lock:
|
||||
if self._redis is None:
|
||||
self._redis = await aioredis.create_connection(
|
||||
(self._host, self._port),
|
||||
db=self._db,
|
||||
password=self._password,
|
||||
ssl=self._ssl,
|
||||
self._redis = await aioredis.create_connection((self._host, self._port),
|
||||
db=self._db, password=self._password, ssl=self._ssl,
|
||||
loop=self._loop,
|
||||
**self._kwargs,
|
||||
)
|
||||
**self._kwargs)
|
||||
return self._redis
|
||||
|
||||
async def get_record(
|
||||
self,
|
||||
*,
|
||||
async def get_record(self, *,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
) -> typing.Dict:
|
||||
user: typing.Union[str, int, None] = None) -> typing.Dict:
|
||||
"""
|
||||
Get record from storage
|
||||
|
||||
|
|
@ -97,20 +85,13 @@ class RedisStorage(BaseStorage):
|
|||
addr = f"fsm:{chat}:{user}"
|
||||
|
||||
conn = await self.redis()
|
||||
data = await conn.execute("GET", addr)
|
||||
data = await conn.execute('GET', addr)
|
||||
if data is None:
|
||||
return {"state": None, "data": {}}
|
||||
return {'state': None, 'data': {}}
|
||||
return json.loads(data)
|
||||
|
||||
async def set_record(
|
||||
self,
|
||||
*,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
state=None,
|
||||
data=None,
|
||||
bucket=None,
|
||||
):
|
||||
async def set_record(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
|
||||
|
||||
|
|
@ -129,65 +110,39 @@ class RedisStorage(BaseStorage):
|
|||
chat, user = self.check_address(chat=chat, user=user)
|
||||
addr = f"fsm:{chat}:{user}"
|
||||
|
||||
record = {"state": state, "data": data, "bucket": bucket}
|
||||
record = {'state': state, 'data': data, 'bucket': bucket}
|
||||
|
||||
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,
|
||||
default: typing.Optional[str] = None,
|
||||
) -> typing.Optional[str]:
|
||||
async def get_state(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)
|
||||
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,
|
||||
default: typing.Optional[str] = None,
|
||||
) -> typing.Dict:
|
||||
async def get_data(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)
|
||||
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,
|
||||
state: typing.Optional[typing.AnyStr] = None,
|
||||
):
|
||||
async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
state: typing.Optional[typing.AnyStr] = None):
|
||||
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,
|
||||
data: typing.Dict = None,
|
||||
):
|
||||
async def set_data(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)
|
||||
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,
|
||||
data: typing.Dict = None,
|
||||
**kwargs,
|
||||
):
|
||||
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
data: typing.Dict = None, **kwargs):
|
||||
if data is None:
|
||||
data = {}
|
||||
record = await self.get_record(chat=chat, user=user)
|
||||
record_data = record.get("data", {})
|
||||
record_data = record.get('data', {})
|
||||
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]]:
|
||||
"""
|
||||
|
|
@ -198,9 +153,9 @@ class RedisStorage(BaseStorage):
|
|||
conn = await self.redis()
|
||||
result = []
|
||||
|
||||
keys = await conn.execute("KEYS", "fsm:*")
|
||||
keys = await conn.execute('KEYS', 'fsm:*')
|
||||
for item in keys:
|
||||
*_, chat, user = item.decode("utf-8").split(":")
|
||||
*_, chat, user = item.decode('utf-8').split(':')
|
||||
result.append((chat, user))
|
||||
|
||||
return result
|
||||
|
|
@ -215,52 +170,33 @@ class RedisStorage(BaseStorage):
|
|||
conn = await self.redis()
|
||||
|
||||
if full:
|
||||
await conn.execute("FLUSHDB")
|
||||
await conn.execute('FLUSHDB')
|
||||
else:
|
||||
keys = await conn.execute("KEYS", "fsm:*")
|
||||
await conn.execute("DEL", *keys)
|
||||
keys = await conn.execute('KEYS', 'fsm:*')
|
||||
await conn.execute('DEL', *keys)
|
||||
|
||||
def has_bucket(self):
|
||||
return True
|
||||
|
||||
async def get_bucket(
|
||||
self,
|
||||
*,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
default: typing.Optional[str] = None,
|
||||
) -> typing.Dict:
|
||||
async def get_bucket(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)
|
||||
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,
|
||||
bucket: typing.Dict = None,
|
||||
):
|
||||
async def set_bucket(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)
|
||||
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(self, *, chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
bucket: typing.Dict = None,
|
||||
**kwargs,
|
||||
):
|
||||
bucket: typing.Dict = None, **kwargs):
|
||||
record = await self.get_record(chat=chat, user=user)
|
||||
record_bucket = record.get("bucket", {})
|
||||
record_bucket = record.get('bucket', {})
|
||||
if bucket is None:
|
||||
bucket = {}
|
||||
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):
|
||||
|
|
@ -283,19 +219,12 @@ class RedisStorage2(BaseStorage):
|
|||
await dp.storage.wait_closed()
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host="localhost",
|
||||
port=6379,
|
||||
db=None,
|
||||
password=None,
|
||||
ssl=None,
|
||||
pool_size=10,
|
||||
loop=None,
|
||||
prefix="fsm",
|
||||
**kwargs,
|
||||
):
|
||||
def __init__(self, host: str = 'localhost', port=6379, db=None, password=None,
|
||||
ssl=None, pool_size=10, loop=None, prefix='fsm',
|
||||
state_ttl: int = 0,
|
||||
data_ttl: int = 0,
|
||||
bucket_ttl: int = 0,
|
||||
**kwargs):
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._db = db
|
||||
|
|
@ -306,32 +235,28 @@ class RedisStorage2(BaseStorage):
|
|||
self._kwargs = kwargs
|
||||
self._prefix = (prefix,)
|
||||
|
||||
self._state_ttl = state_ttl
|
||||
self._data_ttl = data_ttl
|
||||
self._bucket_ttl = bucket_ttl
|
||||
|
||||
self._redis: aioredis.RedisConnection = None
|
||||
self._connection_lock = asyncio.Lock(loop=self._loop)
|
||||
|
||||
async def redis(self) -> aioredis.Redis:
|
||||
"""
|
||||
Get Redis connection
|
||||
|
||||
This property is awaitable.
|
||||
"""
|
||||
# Use thread-safe asyncio Lock because this method without that is not safe
|
||||
async with self._connection_lock:
|
||||
if self._redis is None:
|
||||
self._redis = await aioredis.create_redis_pool(
|
||||
(self._host, self._port),
|
||||
db=self._db,
|
||||
password=self._password,
|
||||
ssl=self._ssl,
|
||||
minsize=1,
|
||||
maxsize=self._pool_size,
|
||||
loop=self._loop,
|
||||
**self._kwargs,
|
||||
)
|
||||
self._redis = await aioredis.create_redis_pool((self._host, self._port),
|
||||
db=self._db, password=self._password, ssl=self._ssl,
|
||||
minsize=1, maxsize=self._pool_size,
|
||||
loop=self._loop, **self._kwargs)
|
||||
return self._redis
|
||||
|
||||
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 with self._connection_lock:
|
||||
|
|
@ -346,68 +271,42 @@ class RedisStorage2(BaseStorage):
|
|||
return await self._redis.wait_closed()
|
||||
return True
|
||||
|
||||
async def get_state(
|
||||
self,
|
||||
*,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
default: typing.Optional[str] = None,
|
||||
) -> typing.Optional[str]:
|
||||
async def get_state(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)
|
||||
key = self.generate_key(chat, user, STATE_KEY)
|
||||
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,
|
||||
default: typing.Optional[dict] = None,
|
||||
) -> typing.Dict:
|
||||
async def get_data(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)
|
||||
key = self.generate_key(chat, user, STATE_DATA_KEY)
|
||||
redis = await self.redis()
|
||||
raw_result = await redis.get(key, encoding="utf8")
|
||||
raw_result = await redis.get(key, encoding='utf8')
|
||||
if raw_result:
|
||||
return json.loads(raw_result)
|
||||
return default or {}
|
||||
|
||||
async def set_state(
|
||||
self,
|
||||
*,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
state: typing.Optional[typing.AnyStr] = None,
|
||||
):
|
||||
async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
state: typing.Optional[typing.AnyStr] = None):
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
key = self.generate_key(chat, user, STATE_KEY)
|
||||
redis = await self.redis()
|
||||
if state is None:
|
||||
await redis.delete(key)
|
||||
else:
|
||||
await redis.set(key, state)
|
||||
await redis.set(key, state, expire=self._state_ttl)
|
||||
|
||||
async def set_data(
|
||||
self,
|
||||
*,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
data: typing.Dict = None,
|
||||
):
|
||||
async def set_data(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)
|
||||
key = self.generate_key(chat, user, STATE_DATA_KEY)
|
||||
redis = await self.redis()
|
||||
await redis.set(key, json.dumps(data))
|
||||
await redis.set(key, json.dumps(data), expire=self._data_ttl)
|
||||
|
||||
async def update_data(
|
||||
self,
|
||||
*,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
data: typing.Dict = None,
|
||||
**kwargs,
|
||||
):
|
||||
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
data: typing.Dict = None, **kwargs):
|
||||
if data is None:
|
||||
data = {}
|
||||
temp_data = await self.get_data(chat=chat, user=user, default={})
|
||||
|
|
@ -417,46 +316,31 @@ class RedisStorage2(BaseStorage):
|
|||
def has_bucket(self):
|
||||
return True
|
||||
|
||||
async def get_bucket(
|
||||
self,
|
||||
*,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
default: typing.Optional[dict] = None,
|
||||
) -> typing.Dict:
|
||||
async def get_bucket(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)
|
||||
key = self.generate_key(chat, user, STATE_BUCKET_KEY)
|
||||
redis = await self.redis()
|
||||
raw_result = await redis.get(key, encoding="utf8")
|
||||
raw_result = await redis.get(key, encoding='utf8')
|
||||
if raw_result:
|
||||
return json.loads(raw_result)
|
||||
return default or {}
|
||||
|
||||
async def set_bucket(
|
||||
self,
|
||||
*,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
bucket: typing.Dict = None,
|
||||
):
|
||||
async def set_bucket(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)
|
||||
key = self.generate_key(chat, user, STATE_BUCKET_KEY)
|
||||
redis = await self.redis()
|
||||
await redis.set(key, json.dumps(bucket))
|
||||
await redis.set(key, json.dumps(bucket), expire=self._bucket_ttl)
|
||||
|
||||
async def update_bucket(
|
||||
self,
|
||||
*,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
bucket: typing.Dict = None,
|
||||
**kwargs,
|
||||
):
|
||||
bucket: typing.Dict = None, **kwargs):
|
||||
if bucket is None:
|
||||
bucket = {}
|
||||
temp_bucket = await self.get_bucket(chat=chat, user=user)
|
||||
temp_bucket.update(bucket, **kwargs)
|
||||
await self.set_bucket(chat=chat, user=user, data=temp_bucket)
|
||||
await self.set_bucket(chat=chat, user=user, bucket=temp_bucket)
|
||||
|
||||
async def reset_all(self, full=True):
|
||||
"""
|
||||
|
|
@ -470,7 +354,7 @@ class RedisStorage2(BaseStorage):
|
|||
if full:
|
||||
await conn.flushdb()
|
||||
else:
|
||||
keys = await conn.keys(self.generate_key("*"))
|
||||
keys = await conn.keys(self.generate_key('*'))
|
||||
await conn.delete(*keys)
|
||||
|
||||
async def get_states_list(self) -> typing.List[typing.Tuple[int]]:
|
||||
|
|
@ -482,9 +366,9 @@ class RedisStorage2(BaseStorage):
|
|||
conn = await self.redis()
|
||||
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:
|
||||
*_, chat, user, _ = item.split(":")
|
||||
*_, chat, user, _ = item.split(':')
|
||||
result.append((chat, user))
|
||||
|
||||
return result
|
||||
|
|
@ -506,7 +390,7 @@ async def migrate_redis1_to_redis2(storage1: RedisStorage, storage2: RedisStorag
|
|||
if not isinstance(storage2, RedisStorage):
|
||||
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():
|
||||
state = await storage1.get_state(chat=chat, user=user)
|
||||
|
|
|
|||
|
|
@ -97,17 +97,15 @@ class I18nMiddleware(BaseMiddleware):
|
|||
if locale not in self.locales:
|
||||
if n is 1:
|
||||
return singular
|
||||
else:
|
||||
return plural
|
||||
|
||||
translator = self.locales[locale]
|
||||
|
||||
if plural is None:
|
||||
return translator.gettext(singular)
|
||||
else:
|
||||
return translator.ngettext(singular, plural, n)
|
||||
|
||||
def lazy_gettext(self, singular, plural=None, n=1, locale=None) -> LazyProxy:
|
||||
def lazy_gettext(self, singular, plural=None, n=1, locale=None, enable_cache=False) -> LazyProxy:
|
||||
"""
|
||||
Lazy get text
|
||||
|
||||
|
|
@ -115,9 +113,10 @@ class I18nMiddleware(BaseMiddleware):
|
|||
:param plural:
|
||||
:param n:
|
||||
:param locale:
|
||||
:param enable_cache:
|
||||
:return:
|
||||
"""
|
||||
return LazyProxy(self.gettext, singular, plural, n, locale)
|
||||
return LazyProxy(self.gettext, singular, plural, n, locale, enable_cache=enable_cache)
|
||||
|
||||
# noinspection PyMethodMayBeStatic,PyUnusedLocal
|
||||
async def get_user_locale(self, action: str, args: Tuple[Any]) -> str:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import logging
|
|||
from aiogram import types
|
||||
from aiogram.dispatcher.middlewares import BaseMiddleware
|
||||
|
||||
HANDLED_STR = ["Unhandled", "Handled"]
|
||||
HANDLED_STR = ['Unhandled', 'Handled']
|
||||
|
||||
|
||||
class LoggingMiddleware(BaseMiddleware):
|
||||
|
|
@ -18,181 +18,128 @@ class LoggingMiddleware(BaseMiddleware):
|
|||
super(LoggingMiddleware, self).__init__()
|
||||
|
||||
def check_timeout(self, obj):
|
||||
start = obj.conf.get("_start", None)
|
||||
start = obj.conf.get('_start', None)
|
||||
if start:
|
||||
del obj.conf["_start"]
|
||||
del obj.conf['_start']
|
||||
return round((time.time() - start) * 1000)
|
||||
return -1
|
||||
|
||||
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}]")
|
||||
|
||||
async def on_post_process_update(self, update: types.Update, result, data: dict):
|
||||
timeout = self.check_timeout(update)
|
||||
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):
|
||||
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):
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
f"message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]"
|
||||
)
|
||||
self.logger.debug(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):
|
||||
self.logger.info(
|
||||
f"Received edited message [ID:{edited_message.message_id}] "
|
||||
f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]"
|
||||
)
|
||||
self.logger.info(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):
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
||||
f"edited message [ID:{edited_message.message_id}] "
|
||||
f"in chat [{edited_message.chat.type}:{edited_message.chat.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):
|
||||
self.logger.info(
|
||||
f"Received channel post [ID:{channel_post.message_id}] "
|
||||
f"in channel [ID:{channel_post.chat.id}]"
|
||||
)
|
||||
self.logger.info(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):
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
||||
f"channel post [ID:{channel_post.message_id}] "
|
||||
f"in chat [{channel_post.chat.type}:{channel_post.chat.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
|
||||
):
|
||||
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_pre_process_edited_channel_post(self, edited_channel_post: types.Message, data: dict):
|
||||
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
|
||||
):
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
async def on_post_process_edited_channel_post(self, edited_channel_post: types.Message, results, data: dict):
|
||||
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}]"
|
||||
)
|
||||
f"in channel [ID:{edited_channel_post.chat.id}]")
|
||||
|
||||
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}] "
|
||||
f"from user [ID:{inline_query.from_user.id}]"
|
||||
)
|
||||
self.logger.info(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
|
||||
):
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
async def on_post_process_inline_query(self, inline_query: types.InlineQuery, results, data: dict):
|
||||
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}]"
|
||||
)
|
||||
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
|
||||
):
|
||||
self.logger.info(
|
||||
f"Received chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] "
|
||||
async def on_pre_process_chosen_inline_result(self, chosen_inline_result: types.ChosenInlineResult, data: dict):
|
||||
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}]"
|
||||
)
|
||||
f"result [ID:{chosen_inline_result.result_id}]")
|
||||
|
||||
async def on_post_process_chosen_inline_result(
|
||||
self, chosen_inline_result, results, data: dict
|
||||
):
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
async def on_post_process_chosen_inline_result(self, chosen_inline_result, results, data: dict):
|
||||
self.logger.debug(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}]"
|
||||
)
|
||||
f"result [ID:{chosen_inline_result.result_id}]")
|
||||
|
||||
async def on_pre_process_callback_query(self, callback_query: types.CallbackQuery, data: dict):
|
||||
if callback_query.message:
|
||||
text = (f"Received callback query [ID:{callback_query.id}] "
|
||||
f"from user [ID:{callback_query.from_user.id}] "
|
||||
f"for message [ID:{callback_query.message.message_id}] "
|
||||
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]")
|
||||
|
||||
if callback_query.message.from_user:
|
||||
self.logger.info(
|
||||
f"Received callback query [ID:{callback_query.id}] "
|
||||
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}] "
|
||||
f"from user [ID:{callback_query.message.from_user.id}]"
|
||||
)
|
||||
text += f" originally posted by user [ID:{callback_query.message.from_user.id}]"
|
||||
|
||||
self.logger.info(text)
|
||||
|
||||
else:
|
||||
self.logger.info(
|
||||
f"Received callback query [ID:{callback_query.id}] "
|
||||
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]"
|
||||
)
|
||||
else:
|
||||
self.logger.info(
|
||||
f"Received callback query [ID:{callback_query.id}] "
|
||||
f"from inline message [ID:{callback_query.inline_message_id}] "
|
||||
f"from user [ID:{callback_query.from_user.id}]"
|
||||
)
|
||||
self.logger.info(f"Received callback query [ID:{callback_query.id}] "
|
||||
f"from user [ID:{callback_query.from_user.id}] "
|
||||
f"for inline message [ID:{callback_query.inline_message_id}] ")
|
||||
|
||||
async def on_post_process_callback_query(self, callback_query, results, data: dict):
|
||||
if callback_query.message:
|
||||
text = (f"{HANDLED_STR[bool(len(results))]} "
|
||||
f"callback query [ID:{callback_query.id}] "
|
||||
f"from user [ID:{callback_query.from_user.id}] "
|
||||
f"for message [ID:{callback_query.message.message_id}] "
|
||||
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]")
|
||||
|
||||
if callback_query.message.from_user:
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
f"callback query [ID:{callback_query.id}] "
|
||||
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}] "
|
||||
f"from user [ID:{callback_query.message.from_user.id}]"
|
||||
)
|
||||
text += f" originally posted by user [ID:{callback_query.message.from_user.id}]"
|
||||
|
||||
self.logger.info(text)
|
||||
|
||||
else:
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
||||
f"callback query [ID:{callback_query.id}] "
|
||||
f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]"
|
||||
)
|
||||
else:
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
f"callback query [ID:{callback_query.id}] "
|
||||
f"from inline message [ID:{callback_query.inline_message_id}] "
|
||||
f"from user [ID:{callback_query.from_user.id}]"
|
||||
)
|
||||
f"from inline message [ID:{callback_query.inline_message_id}]")
|
||||
|
||||
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}] "
|
||||
f"from user [ID:{shipping_query.from_user.id}]"
|
||||
)
|
||||
self.logger.info(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):
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
||||
f"shipping query [ID:{shipping_query.id}] "
|
||||
f"from user [ID:{shipping_query.from_user.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
|
||||
):
|
||||
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_pre_process_pre_checkout_query(self, pre_checkout_query: types.PreCheckoutQuery, data: dict):
|
||||
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):
|
||||
self.logger.debug(
|
||||
f"{HANDLED_STR[bool(len(results))]} "
|
||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
|
||||
f"pre-checkout query [ID:{pre_checkout_query.id}] "
|
||||
f"from user [ID:{pre_checkout_query.from_user.id}]"
|
||||
)
|
||||
f"from user [ID:{pre_checkout_query.from_user.id}]")
|
||||
|
||||
async def on_pre_process_error(self, update, error, data: dict):
|
||||
timeout = self.check_timeout(update)
|
||||
|
|
@ -226,7 +173,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 prefix: prefix for all records
|
||||
|
|
@ -258,34 +205,34 @@ class LoggingFilter(logging.Filter):
|
|||
:param update:
|
||||
:return:
|
||||
"""
|
||||
yield "update_id", update.update_id
|
||||
yield 'update_id', update.update_id
|
||||
|
||||
if update.message:
|
||||
yield "update_type", "message"
|
||||
yield 'update_type', 'message'
|
||||
yield from self.process_message(update.message)
|
||||
if update.edited_message:
|
||||
yield "update_type", "edited_message"
|
||||
yield 'update_type', 'edited_message'
|
||||
yield from self.process_message(update.edited_message)
|
||||
if update.channel_post:
|
||||
yield "update_type", "channel_post"
|
||||
yield 'update_type', 'channel_post'
|
||||
yield from self.process_message(update.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)
|
||||
if update.inline_query:
|
||||
yield "update_type", "inline_query"
|
||||
yield 'update_type', 'inline_query'
|
||||
yield from self.process_inline_query(update.inline_query)
|
||||
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)
|
||||
if update.callback_query:
|
||||
yield "update_type", "callback_query"
|
||||
yield 'update_type', 'callback_query'
|
||||
yield from self.process_callback_query(update.callback_query)
|
||||
if update.shipping_query:
|
||||
yield "update_type", "shipping_query"
|
||||
yield 'update_type', 'shipping_query'
|
||||
yield from self.process_shipping_query(update.shipping_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)
|
||||
|
||||
def make_prefix(self, prefix, iterable):
|
||||
|
|
@ -312,11 +259,11 @@ class LoggingFilter(logging.Filter):
|
|||
if not user:
|
||||
return
|
||||
|
||||
yield "user_id", user.id
|
||||
yield 'user_id', user.id
|
||||
if self.include_content:
|
||||
yield "user_full_name", user.full_name
|
||||
yield 'user_full_name', user.full_name
|
||||
if user.username:
|
||||
yield "user_name", f"@{user.username}"
|
||||
yield 'user_name', f"@{user.username}"
|
||||
|
||||
def process_chat(self, chat: types.Chat):
|
||||
"""
|
||||
|
|
@ -328,15 +275,15 @@ class LoggingFilter(logging.Filter):
|
|||
if not chat:
|
||||
return
|
||||
|
||||
yield "chat_id", chat.id
|
||||
yield "chat_type", chat.type
|
||||
yield 'chat_id', chat.id
|
||||
yield 'chat_type', chat.type
|
||||
if self.include_content:
|
||||
yield "chat_title", chat.full_name
|
||||
yield 'chat_title', chat.full_name
|
||||
if chat.username:
|
||||
yield "chat_name", f"@{chat.username}"
|
||||
yield 'chat_name', f"@{chat.username}"
|
||||
|
||||
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_chat(message.chat)
|
||||
|
||||
|
|
@ -344,84 +291,82 @@ class LoggingFilter(logging.Filter):
|
|||
return
|
||||
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
yield "message_forward_date", message.forward_date
|
||||
yield 'message_forward_date', message.forward_date
|
||||
if message.edit_date:
|
||||
yield "message_edit_date", message.edit_date
|
||||
yield 'message_edit_date', message.edit_date
|
||||
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:
|
||||
yield "message_author_signature", message.author_signature
|
||||
yield 'message_author_signature', message.author_signature
|
||||
|
||||
if message.text:
|
||||
yield "text", message.text or message.caption
|
||||
yield "html_text", message.html_text
|
||||
yield 'text', message.text or message.caption
|
||||
yield 'html_text', message.html_text
|
||||
elif message.audio:
|
||||
yield "audio", message.audio.file_id
|
||||
yield 'audio', message.audio.file_id
|
||||
elif message.animation:
|
||||
yield "animation", message.animation.file_id
|
||||
yield 'animation', message.animation.file_id
|
||||
elif message.document:
|
||||
yield "document", message.document.file_id
|
||||
yield 'document', message.document.file_id
|
||||
elif message.game:
|
||||
yield "game", message.game.title
|
||||
yield 'game', message.game.title
|
||||
elif message.photo:
|
||||
yield "photo", message.photo[-1].file_id
|
||||
yield 'photo', message.photo[-1].file_id
|
||||
elif message.sticker:
|
||||
yield "sticker", message.sticker.file_id
|
||||
yield 'sticker', message.sticker.file_id
|
||||
elif message.video:
|
||||
yield "video", message.video.file_id
|
||||
yield 'video', message.video.file_id
|
||||
elif message.video_note:
|
||||
yield "video_note", message.video_note.file_id
|
||||
yield 'video_note', message.video_note.file_id
|
||||
elif message.voice:
|
||||
yield "voice", message.voice.file_id
|
||||
yield 'voice', message.voice.file_id
|
||||
elif message.contact:
|
||||
yield "contact_full_name", message.contact.full_name
|
||||
yield "contact_phone_number", message.contact.phone_number
|
||||
yield 'contact_full_name', message.contact.full_name
|
||||
yield 'contact_phone_number', message.contact.phone_number
|
||||
elif message.venue:
|
||||
yield "venue_address", message.venue.address
|
||||
yield "location_latitude", message.venue.location.latitude
|
||||
yield "location_longitude", message.venue.location.longitude
|
||||
yield 'venue_address', message.venue.address
|
||||
yield 'location_latitude', message.venue.location.latitude
|
||||
yield 'location_longitude', message.venue.location.longitude
|
||||
elif message.location:
|
||||
yield "location_latitude", message.location.latitude
|
||||
yield "location_longitude", message.location.longitude
|
||||
yield 'location_latitude', message.location.latitude
|
||||
yield 'location_longitude', message.location.longitude
|
||||
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:
|
||||
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:
|
||||
yield "invoice_title", message.invoice.title
|
||||
yield "invoice_description", message.invoice.description
|
||||
yield "invoice_start_parameter", message.invoice.start_parameter
|
||||
yield "invoice_currency", message.invoice.currency
|
||||
yield "invoice_total_amount", message.invoice.total_amount
|
||||
yield 'invoice_title', message.invoice.title
|
||||
yield 'invoice_description', message.invoice.description
|
||||
yield 'invoice_start_parameter', message.invoice.start_parameter
|
||||
yield 'invoice_currency', message.invoice.currency
|
||||
yield 'invoice_total_amount', message.invoice.total_amount
|
||||
elif message.successful_payment:
|
||||
yield "successful_payment_currency", message.successful_payment.currency
|
||||
yield "successful_payment_total_amount", message.successful_payment.total_amount
|
||||
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_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_currency', message.successful_payment.currency
|
||||
yield 'successful_payment_total_amount', message.successful_payment.total_amount
|
||||
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_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
|
||||
elif message.connected_website:
|
||||
yield "connected_website", message.connected_website
|
||||
yield 'connected_website', message.connected_website
|
||||
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:
|
||||
yield "migrate_to_chat_id", message.migrate_to_chat_id
|
||||
yield 'migrate_to_chat_id', message.migrate_to_chat_id
|
||||
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:
|
||||
yield "new_chat_title", message.new_chat_title
|
||||
yield 'new_chat_title', message.new_chat_title
|
||||
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:
|
||||
# yield 'delete_chat_photo', message.delete_chat_photo
|
||||
# elif message.group_chat_created:
|
||||
|
|
@ -430,55 +375,53 @@ class LoggingFilter(logging.Filter):
|
|||
# yield 'passport_data', message.passport_data
|
||||
|
||||
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)
|
||||
|
||||
if self.include_content:
|
||||
yield "inline_query_text", inline_query.query
|
||||
yield 'inline_query_text', inline_query.query
|
||||
if inline_query.location:
|
||||
yield "location_latitude", inline_query.location.latitude
|
||||
yield "location_longitude", inline_query.location.longitude
|
||||
yield 'location_latitude', inline_query.location.latitude
|
||||
yield 'location_longitude', inline_query.location.longitude
|
||||
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):
|
||||
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)
|
||||
|
||||
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:
|
||||
yield "location_latitude", chosen_inline_result.location.latitude
|
||||
yield "location_longitude", chosen_inline_result.location.longitude
|
||||
yield 'location_latitude', chosen_inline_result.location.latitude
|
||||
yield 'location_longitude', chosen_inline_result.location.longitude
|
||||
|
||||
def process_callback_query(self, callback_query: types.CallbackQuery):
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
yield "callback_query_chat_instance", callback_query.chat_instance
|
||||
yield 'callback_query_chat_instance', callback_query.chat_instance
|
||||
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):
|
||||
yield "shipping_query_id", shipping_query.id
|
||||
yield 'shipping_query_id', shipping_query.id
|
||||
yield from self.process_user(shipping_query.from_user)
|
||||
|
||||
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):
|
||||
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)
|
||||
|
||||
if self.include_content:
|
||||
yield "pre_checkout_query_currency", pre_checkout_query.currency
|
||||
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_shipping_option_id", pre_checkout_query.shipping_option_id
|
||||
yield 'pre_checkout_query_currency', pre_checkout_query.currency
|
||||
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_shipping_option_id', pre_checkout_query.shipping_option_id
|
||||
|
|
|
|||
|
|
@ -8,29 +8,13 @@ import typing
|
|||
import aiohttp
|
||||
from aiohttp.helpers import sentinel
|
||||
|
||||
from .filters import (
|
||||
Command,
|
||||
ContentTypeFilter,
|
||||
ExceptionsFilter,
|
||||
FiltersFactory,
|
||||
HashTag,
|
||||
Regexp,
|
||||
RegexpCommandsFilter,
|
||||
StateFilter,
|
||||
Text,
|
||||
)
|
||||
from aiogram.utils.deprecated import renamed_argument
|
||||
from .filters import Command, ContentTypeFilter, ExceptionsFilter, FiltersFactory, HashTag, Regexp, \
|
||||
RegexpCommandsFilter, StateFilter, Text, IDFilter, AdminFilter, IsReplyFilter
|
||||
from .handler import Handler
|
||||
from .middlewares import MiddlewareManager
|
||||
from .storage import (
|
||||
BaseStorage,
|
||||
DELTA,
|
||||
DisabledStorage,
|
||||
EXCEEDED_COUNT,
|
||||
FSMContext,
|
||||
LAST_CALL,
|
||||
RATE_LIMIT,
|
||||
RESULT,
|
||||
)
|
||||
from .storage import BaseStorage, DELTA, DisabledStorage, EXCEEDED_COUNT, FSMContext, \
|
||||
LAST_CALL, RATE_LIMIT, RESULT
|
||||
from .webhook import BaseResponse
|
||||
from .. import types
|
||||
from ..bot import Bot
|
||||
|
|
@ -39,7 +23,7 @@ from ..utils.mixins import ContextInstanceMixin, DataMixin
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_RATE_LIMIT = 0.1
|
||||
DEFAULT_RATE_LIMIT = .1
|
||||
|
||||
|
||||
class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||
|
|
@ -50,21 +34,13 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
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__(self, bot, 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,
|
||||
):
|
||||
throttling_rate_limit=DEFAULT_RATE_LIMIT, no_throttle_error=False,
|
||||
filters_factory=None):
|
||||
|
||||
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:
|
||||
loop = bot.loop
|
||||
|
|
@ -82,18 +58,18 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
self.no_throttle_error = no_throttle_error
|
||||
|
||||
self.filters_factory: FiltersFactory = filters_factory
|
||||
self.updates_handler = Handler(self, middleware_key="update")
|
||||
self.message_handlers = Handler(self, middleware_key="message")
|
||||
self.edited_message_handlers = Handler(self, middleware_key="edited_message")
|
||||
self.channel_post_handlers = Handler(self, middleware_key="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.chosen_inline_result_handlers = Handler(self, middleware_key="chosen_inline_result")
|
||||
self.callback_query_handlers = Handler(self, middleware_key="callback_query")
|
||||
self.shipping_query_handlers = Handler(self, middleware_key="shipping_query")
|
||||
self.pre_checkout_query_handlers = Handler(self, middleware_key="pre_checkout_query")
|
||||
self.poll_handlers = Handler(self, middleware_key="poll")
|
||||
self.errors_handlers = Handler(self, once=False, middleware_key="error")
|
||||
self.updates_handler = Handler(self, middleware_key='update')
|
||||
self.message_handlers = Handler(self, middleware_key='message')
|
||||
self.edited_message_handlers = Handler(self, middleware_key='edited_message')
|
||||
self.channel_post_handlers = Handler(self, middleware_key='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.chosen_inline_result_handlers = Handler(self, middleware_key='chosen_inline_result')
|
||||
self.callback_query_handlers = Handler(self, middleware_key='callback_query')
|
||||
self.shipping_query_handlers = Handler(self, middleware_key='shipping_query')
|
||||
self.pre_checkout_query_handlers = Handler(self, middleware_key='pre_checkout_query')
|
||||
self.poll_handlers = Handler(self, middleware_key='poll')
|
||||
self.errors_handlers = Handler(self, once=False, middleware_key='error')
|
||||
|
||||
self.middleware = MiddlewareManager(self)
|
||||
|
||||
|
|
@ -108,57 +84,73 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
def _setup_filters(self):
|
||||
filters_factory = self.filters_factory
|
||||
|
||||
filters_factory.bind(
|
||||
StateFilter, exclude_event_handlers=[self.errors_handlers, self.poll_handlers]
|
||||
)
|
||||
filters_factory.bind(
|
||||
ContentTypeFilter,
|
||||
event_handlers=[
|
||||
filters_factory.bind(StateFilter, exclude_event_handlers=[
|
||||
self.errors_handlers,
|
||||
self.poll_handlers,
|
||||
])
|
||||
filters_factory.bind(ContentTypeFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
],
|
||||
),
|
||||
filters_factory.bind(
|
||||
Command, event_handlers=[self.message_handlers, self.edited_message_handlers]
|
||||
)
|
||||
filters_factory.bind(
|
||||
Text,
|
||||
event_handlers=[
|
||||
]),
|
||||
filters_factory.bind(Command, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers
|
||||
])
|
||||
filters_factory.bind(Text, 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(
|
||||
HashTag,
|
||||
event_handlers=[
|
||||
self.inline_query_handlers,
|
||||
])
|
||||
filters_factory.bind(HashTag, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
],
|
||||
)
|
||||
filters_factory.bind(
|
||||
Regexp,
|
||||
event_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])
|
||||
self.inline_query_handlers,
|
||||
])
|
||||
filters_factory.bind(RegexpCommandsFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
])
|
||||
filters_factory.bind(ExceptionsFilter, event_handlers=[
|
||||
self.errors_handlers,
|
||||
])
|
||||
filters_factory.bind(AdminFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers,
|
||||
self.inline_query_handlers,
|
||||
])
|
||||
filters_factory.bind(IDFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers,
|
||||
self.inline_query_handlers,
|
||||
])
|
||||
filters_factory.bind(IsReplyFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
])
|
||||
|
||||
def __del__(self):
|
||||
self.stop_polling()
|
||||
|
|
@ -254,15 +246,13 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
|
||||
return await self.bot.delete_webhook()
|
||||
|
||||
async def start_polling(
|
||||
self,
|
||||
async def start_polling(self,
|
||||
timeout=20,
|
||||
relax=0.1,
|
||||
limit=None,
|
||||
reset_webhook=None,
|
||||
fast: typing.Optional[bool] = True,
|
||||
error_sleep: int = 5,
|
||||
):
|
||||
error_sleep: int = 5):
|
||||
"""
|
||||
Start long-polling
|
||||
|
||||
|
|
@ -274,9 +264,9 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:return:
|
||||
"""
|
||||
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)
|
||||
Dispatcher.set_current(self)
|
||||
|
|
@ -292,20 +282,18 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
try:
|
||||
current_request_timeout = self.bot.timeout
|
||||
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:
|
||||
request_timeout = None
|
||||
|
||||
while self._polling:
|
||||
try:
|
||||
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 asyncio.CancelledError:
|
||||
break
|
||||
except:
|
||||
log.exception("Cause exception while getting updates.")
|
||||
log.exception('Cause exception while getting updates.')
|
||||
await asyncio.sleep(error_sleep)
|
||||
continue
|
||||
|
||||
|
|
@ -319,8 +307,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
await asyncio.sleep(relax)
|
||||
|
||||
finally:
|
||||
self._close_waiter._set_result(None)
|
||||
log.warning("Polling is stopped.")
|
||||
self._close_waiter.set_result(None)
|
||||
log.warning('Polling is stopped.')
|
||||
|
||||
async def _process_polling_updates(self, updates, fast: typing.Optional[bool] = True):
|
||||
"""
|
||||
|
|
@ -339,7 +327,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
try:
|
||||
asyncio.gather(*need_to_call)
|
||||
except TelegramAPIError:
|
||||
log.exception("Cause exception while processing updates.")
|
||||
log.exception('Cause exception while processing updates.')
|
||||
|
||||
def stop_polling(self):
|
||||
"""
|
||||
|
|
@ -347,8 +335,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
|
||||
:return:
|
||||
"""
|
||||
if hasattr(self, "_polling") and self._polling:
|
||||
log.info("Stop polling...")
|
||||
if hasattr(self, '_polling') and self._polling:
|
||||
log.info('Stop polling...')
|
||||
self._polling = False
|
||||
|
||||
async def wait_closed(self):
|
||||
|
|
@ -367,17 +355,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
return self._polling
|
||||
|
||||
def register_message_handler(
|
||||
self,
|
||||
callback,
|
||||
*custom_filters,
|
||||
commands=None,
|
||||
regexp=None,
|
||||
content_types=None,
|
||||
state=None,
|
||||
run_task=None,
|
||||
**kwargs,
|
||||
):
|
||||
def register_message_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None,
|
||||
state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
Register handler for message
|
||||
|
||||
|
|
@ -403,27 +382,17 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:param state:
|
||||
:return: decorated function
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.message_handlers,
|
||||
filters_set = self.filters_factory.resolve(self.message_handlers,
|
||||
*custom_filters,
|
||||
commands=commands,
|
||||
regexp=regexp,
|
||||
content_types=content_types,
|
||||
state=state,
|
||||
**kwargs,
|
||||
)
|
||||
**kwargs)
|
||||
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,
|
||||
run_task=None,
|
||||
**kwargs,
|
||||
):
|
||||
def message_handler(self, *custom_filters, commands=None, regexp=None, content_types=None, state=None,
|
||||
run_task=None, **kwargs):
|
||||
"""
|
||||
Decorator for message handler
|
||||
|
||||
|
|
@ -494,31 +463,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
|
||||
def decorator(callback):
|
||||
self.register_message_handler(
|
||||
callback,
|
||||
*custom_filters,
|
||||
commands=commands,
|
||||
regexp=regexp,
|
||||
content_types=content_types,
|
||||
state=state,
|
||||
run_task=run_task,
|
||||
**kwargs,
|
||||
)
|
||||
self.register_message_handler(callback, *custom_filters,
|
||||
commands=commands, regexp=regexp, content_types=content_types,
|
||||
state=state, run_task=run_task, **kwargs)
|
||||
return callback
|
||||
|
||||
return decorator
|
||||
|
||||
def register_edited_message_handler(
|
||||
self,
|
||||
callback,
|
||||
*custom_filters,
|
||||
commands=None,
|
||||
regexp=None,
|
||||
content_types=None,
|
||||
state=None,
|
||||
run_task=None,
|
||||
**kwargs,
|
||||
):
|
||||
def register_edited_message_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None,
|
||||
state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
Register handler for edited message
|
||||
|
||||
|
|
@ -532,29 +485,17 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:param kwargs:
|
||||
:return: decorated function
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.edited_message_handlers,
|
||||
filters_set = self.filters_factory.resolve(self.edited_message_handlers,
|
||||
*custom_filters,
|
||||
commands=commands,
|
||||
regexp=regexp,
|
||||
content_types=content_types,
|
||||
state=state,
|
||||
**kwargs,
|
||||
)
|
||||
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,
|
||||
state=None,
|
||||
run_task=None,
|
||||
**kwargs,
|
||||
):
|
||||
def edited_message_handler(self, *custom_filters, commands=None, regexp=None, content_types=None,
|
||||
state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
Decorator for edited message handler
|
||||
|
||||
|
|
@ -577,31 +518,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
|
||||
def decorator(callback):
|
||||
self.register_edited_message_handler(
|
||||
callback,
|
||||
*custom_filters,
|
||||
commands=commands,
|
||||
regexp=regexp,
|
||||
content_types=content_types,
|
||||
state=state,
|
||||
run_task=run_task,
|
||||
**kwargs,
|
||||
)
|
||||
self.register_edited_message_handler(callback, *custom_filters, commands=commands, regexp=regexp,
|
||||
content_types=content_types, state=state, run_task=run_task, **kwargs)
|
||||
return callback
|
||||
|
||||
return decorator
|
||||
|
||||
def register_channel_post_handler(
|
||||
self,
|
||||
callback,
|
||||
*custom_filters,
|
||||
commands=None,
|
||||
regexp=None,
|
||||
content_types=None,
|
||||
state=None,
|
||||
run_task=None,
|
||||
**kwargs,
|
||||
):
|
||||
def register_channel_post_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None,
|
||||
state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
Register handler for channel post
|
||||
|
||||
|
|
@ -615,27 +539,17 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:param kwargs:
|
||||
:return: decorated function
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.channel_post_handlers,
|
||||
filters_set = self.filters_factory.resolve(self.channel_post_handlers,
|
||||
*custom_filters,
|
||||
commands=commands,
|
||||
regexp=regexp,
|
||||
content_types=content_types,
|
||||
state=state,
|
||||
**kwargs,
|
||||
)
|
||||
**kwargs)
|
||||
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,
|
||||
state=None,
|
||||
run_task=None,
|
||||
**kwargs,
|
||||
):
|
||||
def channel_post_handler(self, *custom_filters, commands=None, regexp=None, content_types=None,
|
||||
state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
Decorator for channel post handler
|
||||
|
||||
|
|
@ -650,31 +564,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
|
||||
def decorator(callback):
|
||||
self.register_channel_post_handler(
|
||||
callback,
|
||||
*custom_filters,
|
||||
commands=commands,
|
||||
regexp=regexp,
|
||||
content_types=content_types,
|
||||
state=state,
|
||||
run_task=run_task,
|
||||
**kwargs,
|
||||
)
|
||||
self.register_channel_post_handler(callback, *custom_filters, commands=commands, regexp=regexp,
|
||||
content_types=content_types, state=state, run_task=run_task, **kwargs)
|
||||
return callback
|
||||
|
||||
return decorator
|
||||
|
||||
def register_edited_channel_post_handler(
|
||||
self,
|
||||
callback,
|
||||
*custom_filters,
|
||||
commands=None,
|
||||
regexp=None,
|
||||
content_types=None,
|
||||
state=None,
|
||||
run_task=None,
|
||||
**kwargs,
|
||||
):
|
||||
def register_edited_channel_post_handler(self, callback, *custom_filters, commands=None, regexp=None,
|
||||
content_types=None, state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
Register handler for edited channel post
|
||||
|
||||
|
|
@ -688,29 +585,17 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:param kwargs:
|
||||
:return: decorated function
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.edited_message_handlers,
|
||||
filters_set = self.filters_factory.resolve(self.edited_message_handlers,
|
||||
*custom_filters,
|
||||
commands=commands,
|
||||
regexp=regexp,
|
||||
content_types=content_types,
|
||||
state=state,
|
||||
**kwargs,
|
||||
)
|
||||
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,
|
||||
state=None,
|
||||
run_task=None,
|
||||
**kwargs,
|
||||
):
|
||||
def edited_channel_post_handler(self, *custom_filters, commands=None, regexp=None, content_types=None,
|
||||
state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
Decorator for edited channel post handler
|
||||
|
||||
|
|
@ -725,23 +610,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
|
||||
def decorator(callback):
|
||||
self.register_edited_channel_post_handler(
|
||||
callback,
|
||||
*custom_filters,
|
||||
commands=commands,
|
||||
regexp=regexp,
|
||||
content_types=content_types,
|
||||
state=state,
|
||||
run_task=run_task,
|
||||
**kwargs,
|
||||
)
|
||||
self.register_edited_channel_post_handler(callback, *custom_filters, commands=commands, regexp=regexp,
|
||||
content_types=content_types, state=state, run_task=run_task,
|
||||
**kwargs)
|
||||
return callback
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -760,9 +636,10 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
if custom_filters is None:
|
||||
custom_filters = []
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.inline_query_handlers, *custom_filters, state=state, **kwargs
|
||||
)
|
||||
filters_set = self.filters_factory.resolve(self.inline_query_handlers,
|
||||
*custom_filters,
|
||||
state=state,
|
||||
**kwargs)
|
||||
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):
|
||||
|
|
@ -784,16 +661,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
|
||||
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 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
|
||||
|
||||
|
|
@ -812,12 +685,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
if custom_filters is None:
|
||||
custom_filters = []
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.chosen_inline_result_handlers, *custom_filters, state=state, **kwargs
|
||||
)
|
||||
self.chosen_inline_result_handlers.register(
|
||||
self._wrap_async_task(callback, run_task), filters_set
|
||||
)
|
||||
filters_set = self.filters_factory.resolve(self.chosen_inline_result_handlers,
|
||||
*custom_filters,
|
||||
state=state,
|
||||
**kwargs)
|
||||
self.chosen_inline_result_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||
|
||||
def chosen_inline_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
|
|
@ -838,16 +710,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
|
||||
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 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
|
||||
|
||||
|
|
@ -863,12 +731,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:param run_task: run callback in task (no wait results)
|
||||
:param kwargs:
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.callback_query_handlers, *custom_filters, state=state, **kwargs
|
||||
)
|
||||
self.callback_query_handlers.register(
|
||||
self._wrap_async_task(callback, run_task), filters_set
|
||||
)
|
||||
filters_set = self.filters_factory.resolve(self.callback_query_handlers,
|
||||
*custom_filters,
|
||||
state=state,
|
||||
**kwargs)
|
||||
self.callback_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||
|
||||
def callback_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
|
|
@ -888,16 +755,13 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
|
||||
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 decorator
|
||||
|
||||
def register_shipping_query_handler(
|
||||
self, callback, *custom_filters, state=None, run_task=None, **kwargs
|
||||
):
|
||||
def register_shipping_query_handler(self, callback, *custom_filters, state=None, run_task=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Register handler for shipping query
|
||||
|
||||
|
|
@ -913,12 +777,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:param run_task: run callback in task (no wait results)
|
||||
:param kwargs:
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.shipping_query_handlers, *custom_filters, state=state, **kwargs
|
||||
)
|
||||
self.shipping_query_handlers.register(
|
||||
self._wrap_async_task(callback, run_task), filters_set
|
||||
)
|
||||
filters_set = self.filters_factory.resolve(self.shipping_query_handlers,
|
||||
*custom_filters,
|
||||
state=state,
|
||||
**kwargs)
|
||||
self.shipping_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||
|
||||
def shipping_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
|
|
@ -938,16 +801,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
|
||||
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 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
|
||||
|
||||
|
|
@ -963,12 +822,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:param run_task: run callback in task (no wait results)
|
||||
:param kwargs:
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.pre_checkout_query_handlers, *custom_filters, state=state, **kwargs
|
||||
)
|
||||
self.pre_checkout_query_handlers.register(
|
||||
self._wrap_async_task(callback, run_task), filters_set
|
||||
)
|
||||
filters_set = self.filters_factory.resolve(self.pre_checkout_query_handlers,
|
||||
*custom_filters,
|
||||
state=state,
|
||||
**kwargs)
|
||||
self.pre_checkout_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||
|
||||
def pre_checkout_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
|
||||
"""
|
||||
|
|
@ -988,27 +846,27 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
|
||||
def decorator(callback):
|
||||
self.register_pre_checkout_query_handler(
|
||||
callback, *custom_filters, state=state, run_task=run_task, **kwargs
|
||||
)
|
||||
self.register_pre_checkout_query_handler(callback, *custom_filters, state=state, run_task=run_task,
|
||||
**kwargs)
|
||||
return callback
|
||||
|
||||
return decorator
|
||||
|
||||
def register_poll_handler(self, callback, *custom_filters, run_task=None, **kwargs):
|
||||
filters_set = self.filters_factory.resolve(self.poll_handlers, *custom_filters, **kwargs)
|
||||
filters_set = self.filters_factory.resolve(self.poll_handlers,
|
||||
*custom_filters,
|
||||
**kwargs)
|
||||
self.poll_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
|
||||
|
||||
def poll_handler(self, *custom_filters, run_task=None, **kwargs):
|
||||
def decorator(callback):
|
||||
self.register_poll_handler(callback, *custom_filters, run_task=run_task, **kwargs)
|
||||
self.register_poll_handler(callback, *custom_filters, run_task=run_task,
|
||||
**kwargs)
|
||||
return callback
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -1016,9 +874,10 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:param exception: you can make handler for specific errors type
|
||||
:param run_task: run callback in task (no wait results)
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.errors_handlers, *custom_filters, exception=exception, **kwargs
|
||||
)
|
||||
filters_set = self.filters_factory.resolve(self.errors_handlers,
|
||||
*custom_filters,
|
||||
exception=exception,
|
||||
**kwargs)
|
||||
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):
|
||||
|
|
@ -1031,22 +890,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
"""
|
||||
|
||||
def decorator(callback):
|
||||
self.register_errors_handler(
|
||||
self._wrap_async_task(callback, run_task),
|
||||
*custom_filters,
|
||||
exception=exception,
|
||||
**kwargs,
|
||||
)
|
||||
self.register_errors_handler(self._wrap_async_task(callback, run_task),
|
||||
*custom_filters, exception=exception, **kwargs)
|
||||
return callback
|
||||
|
||||
return decorator
|
||||
|
||||
def current_state(
|
||||
self,
|
||||
*,
|
||||
def current_state(self, *,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
) -> FSMContext:
|
||||
user: typing.Union[str, int, None] = None) -> FSMContext:
|
||||
"""
|
||||
Get current state for user in chat as context
|
||||
|
||||
|
|
@ -1071,33 +923,35 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
|
||||
return FSMContext(storage=self.storage, chat=chat, user=user)
|
||||
|
||||
async def throttle(self, key, *, rate=None, user=None, chat=None, no_error=None) -> bool:
|
||||
@renamed_argument(old_name='user', new_name='user_id', until_version='3.0', stacklevel=3)
|
||||
@renamed_argument(old_name='chat', new_name='chat_id', until_version='3.0', stacklevel=4)
|
||||
async def throttle(self, key, *, rate=None, user_id=None, chat_id=None, no_error=None) -> bool:
|
||||
"""
|
||||
Execute throttling manager.
|
||||
Returns True if limit has not exceeded otherwise raises ThrottleError or returns False
|
||||
|
||||
:param key: key in storage
|
||||
:param rate: limit (by default is equal to default rate limit)
|
||||
:param user: user id
|
||||
:param chat: chat id
|
||||
:param user_id: user id
|
||||
:param chat_id: chat id
|
||||
:param no_error: return boolean value instead of raising error
|
||||
:return: bool
|
||||
"""
|
||||
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:
|
||||
no_error = self.no_throttle_error
|
||||
if rate is None:
|
||||
rate = self.throttling_rate_limit
|
||||
if user is None and chat is None:
|
||||
user = types.User.get_current()
|
||||
chat = types.Chat.get_current()
|
||||
if user_id is None and chat_id is None:
|
||||
user_id = types.User.get_current().id
|
||||
chat_id = types.Chat.get_current().id
|
||||
|
||||
# Detect current time
|
||||
now = time.time()
|
||||
|
||||
bucket = await self.storage.get_bucket(chat=chat, user=user)
|
||||
bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
|
||||
|
||||
# Fix bucket
|
||||
if bucket is None:
|
||||
|
|
@ -1121,53 +975,57 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
else:
|
||||
data[EXCEEDED_COUNT] = 1
|
||||
bucket[key].update(data)
|
||||
await self.storage.set_bucket(chat=chat, user=user, bucket=bucket)
|
||||
await self.storage.set_bucket(chat=chat_id, user=user_id, bucket=bucket)
|
||||
|
||||
if not result and not no_error:
|
||||
# Raise if it is allowed
|
||||
raise Throttled(key=key, chat=chat, user=user, **data)
|
||||
raise Throttled(key=key, chat=chat_id, user=user_id, **data)
|
||||
return result
|
||||
|
||||
async def check_key(self, key, chat=None, user=None):
|
||||
@renamed_argument(old_name='user', new_name='user_id', until_version='3.0', stacklevel=3)
|
||||
@renamed_argument(old_name='chat', new_name='chat_id', until_version='3.0', stacklevel=4)
|
||||
async def check_key(self, key, chat_id=None, user_id=None):
|
||||
"""
|
||||
Get information about key in bucket
|
||||
|
||||
:param key:
|
||||
:param chat:
|
||||
:param user:
|
||||
:param chat_id:
|
||||
:param user_id:
|
||||
:return:
|
||||
"""
|
||||
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:
|
||||
user = types.User.get_current()
|
||||
chat = types.Chat.get_current()
|
||||
if user_id is None and chat_id is None:
|
||||
user_id = types.User.get_current()
|
||||
chat_id = types.Chat.get_current()
|
||||
|
||||
bucket = await self.storage.get_bucket(chat=chat, user=user)
|
||||
bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
|
||||
data = bucket.get(key, {})
|
||||
return Throttled(key=key, chat=chat, user=user, **data)
|
||||
return Throttled(key=key, chat=chat_id, user=user_id, **data)
|
||||
|
||||
async def release_key(self, key, chat=None, user=None):
|
||||
@renamed_argument(old_name='user', new_name='user_id', until_version='3.0', stacklevel=3)
|
||||
@renamed_argument(old_name='chat', new_name='chat_id', until_version='3.0', stacklevel=4)
|
||||
async def release_key(self, key, chat_id=None, user_id=None):
|
||||
"""
|
||||
Release blocked key
|
||||
|
||||
:param key:
|
||||
:param chat:
|
||||
:param user:
|
||||
:param chat_id:
|
||||
:param user_id:
|
||||
:return:
|
||||
"""
|
||||
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:
|
||||
user = types.User.get_current()
|
||||
chat = types.Chat.get_current()
|
||||
if user_id is None and chat_id is None:
|
||||
user_id = types.User.get_current()
|
||||
chat_id = types.Chat.get_current()
|
||||
|
||||
bucket = await self.storage.get_bucket(chat=chat, user=user)
|
||||
bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
|
||||
if bucket and key in bucket:
|
||||
del bucket["key"]
|
||||
await self.storage.set_bucket(chat=chat, user=user, bucket=bucket)
|
||||
del bucket['key']
|
||||
await self.storage.set_bucket(chat=chat_id, user=user_id, bucket=bucket)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -1192,7 +1050,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
try:
|
||||
response = task.result()
|
||||
except Exception as e:
|
||||
self.loop.create_task(self.errors_handlers.notify(types.Update.get_current(), e))
|
||||
self.loop.create_task(
|
||||
self.errors_handlers.notify(types.Update.get_current(), e))
|
||||
else:
|
||||
if isinstance(response, BaseResponse):
|
||||
self.loop.create_task(response.execute_response(self.bot))
|
||||
|
|
@ -1211,3 +1070,64 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
if run_task:
|
||||
return self.async_task(callback)
|
||||
return callback
|
||||
|
||||
def throttled(self, on_throttled: typing.Optional[typing.Callable] = None,
|
||||
key=None, rate=None,
|
||||
user_id=None, chat_id=None):
|
||||
"""
|
||||
Meta-decorator for throttling.
|
||||
Invokes on_throttled if the handler was throttled.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
async def handler_throttled(message: types.Message, **kwargs):
|
||||
await message.answer("Throttled!")
|
||||
|
||||
@dp.throttled(handler_throttled)
|
||||
async def some_handler(message: types.Message):
|
||||
await message.answer("Didn't throttled!")
|
||||
|
||||
:param on_throttled: the callable object that should be either a function or return a coroutine
|
||||
:param key: key in storage
|
||||
:param rate: limit (by default is equal to default rate limit)
|
||||
:param user_id: user id
|
||||
:param chat_id: chat id
|
||||
:return: decorator
|
||||
"""
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapped(*args, **kwargs):
|
||||
is_not_throttled = await self.throttle(key if key is not None else func.__name__,
|
||||
rate=rate,
|
||||
user_id=user_id, chat_id=chat_id,
|
||||
no_error=True)
|
||||
if is_not_throttled:
|
||||
return await func(*args, **kwargs)
|
||||
else:
|
||||
kwargs.update(
|
||||
{
|
||||
'rate': rate,
|
||||
'key': key,
|
||||
'user_id': user_id,
|
||||
'chat_id': chat_id
|
||||
}
|
||||
) # update kwargs with parameters which were given to throttled
|
||||
|
||||
if on_throttled:
|
||||
if asyncio.iscoroutinefunction(on_throttled):
|
||||
await on_throttled(*args, **kwargs)
|
||||
else:
|
||||
kwargs.update(
|
||||
{
|
||||
'loop': asyncio.get_running_loop()
|
||||
}
|
||||
)
|
||||
partial_func = functools.partial(on_throttled, *args, **kwargs)
|
||||
asyncio.get_running_loop().run_in_executor(None,
|
||||
partial_func
|
||||
)
|
||||
return wrapped
|
||||
|
||||
return decorator
|
||||
|
|
|
|||
|
|
@ -1,51 +1,34 @@
|
|||
from .builtin import (
|
||||
Command,
|
||||
CommandHelp,
|
||||
CommandPrivacy,
|
||||
CommandSettings,
|
||||
CommandStart,
|
||||
ContentTypeFilter,
|
||||
ExceptionsFilter,
|
||||
HashTag,
|
||||
Regexp,
|
||||
RegexpCommandsFilter,
|
||||
StateFilter,
|
||||
Text,
|
||||
)
|
||||
from .builtin import Command, CommandHelp, CommandPrivacy, CommandSettings, CommandStart, ContentTypeFilter, \
|
||||
ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, \
|
||||
Text, IDFilter, AdminFilter, IsReplyFilter
|
||||
from .factory import FiltersFactory
|
||||
from .filters import (
|
||||
AbstractFilter,
|
||||
BoundFilter,
|
||||
Filter,
|
||||
FilterNotPassed,
|
||||
FilterRecord,
|
||||
execute_filter,
|
||||
check_filters,
|
||||
get_filter_spec,
|
||||
get_filters_spec,
|
||||
)
|
||||
from .filters import AbstractFilter, BoundFilter, Filter, FilterNotPassed, FilterRecord, execute_filter, \
|
||||
check_filters, get_filter_spec, get_filters_spec
|
||||
|
||||
__all__ = [
|
||||
"AbstractFilter",
|
||||
"BoundFilter",
|
||||
"Command",
|
||||
"CommandStart",
|
||||
"CommandHelp",
|
||||
"CommandPrivacy",
|
||||
"CommandSettings",
|
||||
"ContentTypeFilter",
|
||||
"ExceptionsFilter",
|
||||
"HashTag",
|
||||
"Filter",
|
||||
"FilterNotPassed",
|
||||
"FilterRecord",
|
||||
"FiltersFactory",
|
||||
"RegexpCommandsFilter",
|
||||
"Regexp",
|
||||
"StateFilter",
|
||||
"Text",
|
||||
"get_filter_spec",
|
||||
"get_filters_spec",
|
||||
"execute_filter",
|
||||
"check_filters",
|
||||
'AbstractFilter',
|
||||
'BoundFilter',
|
||||
'Command',
|
||||
'CommandStart',
|
||||
'CommandHelp',
|
||||
'CommandPrivacy',
|
||||
'CommandSettings',
|
||||
'ContentTypeFilter',
|
||||
'ExceptionsFilter',
|
||||
'HashTag',
|
||||
'Filter',
|
||||
'FilterNotPassed',
|
||||
'FilterRecord',
|
||||
'FiltersFactory',
|
||||
'RegexpCommandsFilter',
|
||||
'Regexp',
|
||||
'StateFilter',
|
||||
'Text',
|
||||
'IDFilter',
|
||||
'IsReplyFilter',
|
||||
'AdminFilter',
|
||||
'get_filter_spec',
|
||||
'get_filters_spec',
|
||||
'execute_filter',
|
||||
'check_filters',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from babel.support import LazyProxy
|
|||
|
||||
from aiogram import types
|
||||
from aiogram.dispatcher.filters.filters import BoundFilter, Filter
|
||||
from aiogram.types import CallbackQuery, Message, InlineQuery, Poll
|
||||
from aiogram.types import CallbackQuery, Message, InlineQuery, Poll, ChatType
|
||||
|
||||
|
||||
class Command(Filter):
|
||||
|
|
@ -21,13 +21,10 @@ class Command(Filter):
|
|||
By default this filter is registered for messages and edited messages handlers.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
commands: Union[Iterable, str],
|
||||
prefixes: Union[Iterable, str] = "/",
|
||||
def __init__(self, commands: Union[Iterable, str],
|
||||
prefixes: Union[Iterable, str] = '/',
|
||||
ignore_case: bool = True,
|
||||
ignore_mention: bool = False,
|
||||
):
|
||||
ignore_mention: bool = False):
|
||||
"""
|
||||
Filter can be initialized from filters factory or by simply creating instance of this class.
|
||||
|
||||
|
|
@ -69,38 +66,33 @@ class Command(Filter):
|
|||
:return: config or empty dict
|
||||
"""
|
||||
config = {}
|
||||
if "commands" in full_config:
|
||||
config["commands"] = full_config.pop("commands")
|
||||
if config and "commands_prefix" in full_config:
|
||||
config["prefixes"] = full_config.pop("commands_prefix")
|
||||
if config and "commands_ignore_mention" in full_config:
|
||||
config["ignore_mention"] = full_config.pop("commands_ignore_mention")
|
||||
if 'commands' in full_config:
|
||||
config['commands'] = full_config.pop('commands')
|
||||
if config and 'commands_prefix' in full_config:
|
||||
config['prefixes'] = full_config.pop('commands_prefix')
|
||||
if config and 'commands_ignore_mention' in full_config:
|
||||
config['ignore_mention'] = full_config.pop('commands_ignore_mention')
|
||||
return config
|
||||
|
||||
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
|
||||
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):
|
||||
if not message.text: # Prevent to use with non-text content types
|
||||
return False
|
||||
|
||||
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
|
||||
elif prefix not in prefixes:
|
||||
if prefix not in prefixes:
|
||||
return False
|
||||
elif (command.lower() if ignore_case else command) not in commands:
|
||||
if (command.lower() if ignore_case else command) not in commands:
|
||||
return False
|
||||
|
||||
return {"command": Command.CommandObj(command=command, prefix=prefix, mention=mention)}
|
||||
return {'command': Command.CommandObj(command=command, prefix=prefix, mention=mention)}
|
||||
|
||||
@dataclass
|
||||
class CommandObj:
|
||||
|
|
@ -111,9 +103,9 @@ class Command(Filter):
|
|||
"""
|
||||
|
||||
"""Command prefix"""
|
||||
prefix: str = "/"
|
||||
prefix: str = '/'
|
||||
"""Command without prefix and mention"""
|
||||
command: str = ""
|
||||
command: str = ''
|
||||
"""Mention (if available)"""
|
||||
mention: str = None
|
||||
"""Command argument"""
|
||||
|
|
@ -137,9 +129,9 @@ class Command(Filter):
|
|||
"""
|
||||
line = self.prefix + self.command
|
||||
if self.mentioned:
|
||||
line += "@" + self.mention
|
||||
line += '@' + self.mention
|
||||
if self.args:
|
||||
line += " " + self.args
|
||||
line += ' ' + self.args
|
||||
return line
|
||||
|
||||
|
||||
|
|
@ -160,7 +152,7 @@ class CommandStart(Command):
|
|||
|
||||
:param deep_link: string or compiled regular expression (by ``re.compile(...)``).
|
||||
"""
|
||||
super(CommandStart, self).__init__(["start"])
|
||||
super().__init__(['start'])
|
||||
self.deep_link = deep_link
|
||||
|
||||
async def check(self, message: types.Message):
|
||||
|
|
@ -170,7 +162,7 @@ class CommandStart(Command):
|
|||
:param message:
|
||||
:return:
|
||||
"""
|
||||
check = await super(CommandStart, self).check(message)
|
||||
check = await super().check(message)
|
||||
|
||||
if check and self.deep_link is not None:
|
||||
if not isinstance(self.deep_link, re.Pattern):
|
||||
|
|
@ -178,7 +170,7 @@ class CommandStart(Command):
|
|||
|
||||
match = self.deep_link.match(message.get_args())
|
||||
if match:
|
||||
return {"deep_link": match}
|
||||
return {'deep_link': match}
|
||||
return False
|
||||
|
||||
return check
|
||||
|
|
@ -190,7 +182,7 @@ class CommandHelp(Command):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(CommandHelp, self).__init__(["help"])
|
||||
super().__init__(['help'])
|
||||
|
||||
|
||||
class CommandSettings(Command):
|
||||
|
|
@ -199,7 +191,7 @@ class CommandSettings(Command):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(CommandSettings, self).__init__(["settings"])
|
||||
super().__init__(['settings'])
|
||||
|
||||
|
||||
class CommandPrivacy(Command):
|
||||
|
|
@ -208,7 +200,7 @@ class CommandPrivacy(Command):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(CommandPrivacy, self).__init__(["privacy"])
|
||||
super().__init__(['privacy'])
|
||||
|
||||
|
||||
class Text(Filter):
|
||||
|
|
@ -216,42 +208,44 @@ class Text(Filter):
|
|||
Simple text filter
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
equals: Optional[Union[str, LazyProxy]] = None,
|
||||
contains: Optional[Union[str, LazyProxy]] = None,
|
||||
startswith: Optional[Union[str, LazyProxy]] = None,
|
||||
endswith: Optional[Union[str, LazyProxy]] = None,
|
||||
ignore_case=False,
|
||||
):
|
||||
_default_params = (
|
||||
('text', 'equals'),
|
||||
('text_contains', 'contains'),
|
||||
('text_startswith', 'startswith'),
|
||||
('text_endswith', 'endswith'),
|
||||
)
|
||||
|
||||
def __init__(self,
|
||||
equals: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
||||
contains: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
||||
startswith: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
||||
endswith: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
||||
ignore_case=False):
|
||||
"""
|
||||
Check text for one of pattern. Only one mode can be used in one filter.
|
||||
In every pattern, a single string is treated as a list with 1 element.
|
||||
|
||||
:param equals:
|
||||
:param contains:
|
||||
:param startswith:
|
||||
:param endswith:
|
||||
:param equals: True if object's text in the list
|
||||
:param contains: True if object's text contains all strings from the list
|
||||
:param startswith: True if object's text starts with any of strings from the list
|
||||
:param endswith: True if object's text ends with any of strings from the list
|
||||
:param ignore_case: case insensitive
|
||||
"""
|
||||
# Only one mode can be used. check it.
|
||||
check = sum(map(bool, (equals, contains, startswith, endswith)))
|
||||
check = sum(map(lambda s: s is not None, (equals, contains, startswith, endswith)))
|
||||
if check > 1:
|
||||
args = "' and '".join(
|
||||
[
|
||||
arg[0]
|
||||
for arg in [
|
||||
("equals", equals),
|
||||
("contains", contains),
|
||||
("startswith", startswith),
|
||||
("endswith", endswith),
|
||||
]
|
||||
if arg[1]
|
||||
]
|
||||
)
|
||||
args = "' and '".join([arg[0] for arg in [('equals', equals),
|
||||
('contains', contains),
|
||||
('startswith', startswith),
|
||||
('endswith', endswith)
|
||||
] if arg[1] is not None])
|
||||
raise ValueError(f"Arguments '{args}' cannot be used together.")
|
||||
elif check == 0:
|
||||
raise ValueError(f"No one mode is specified!")
|
||||
|
||||
equals, contains, endswith, startswith = map(lambda e: [e] if isinstance(e, str) or isinstance(e, LazyProxy)
|
||||
else e,
|
||||
(equals, contains, endswith, startswith))
|
||||
self.equals = equals
|
||||
self.contains = contains
|
||||
self.endswith = endswith
|
||||
|
|
@ -260,18 +254,13 @@ class Text(Filter):
|
|||
|
||||
@classmethod
|
||||
def validate(cls, full_config: Dict[str, Any]):
|
||||
if "text" in full_config:
|
||||
return {"equals": full_config.pop("text")}
|
||||
elif "text_contains" in full_config:
|
||||
return {"contains": full_config.pop("text_contains")}
|
||||
elif "text_startswith" in full_config:
|
||||
return {"startswith": full_config.pop("text_startswith")}
|
||||
elif "text_endswith" in full_config:
|
||||
return {"endswith": full_config.pop("text_endswith")}
|
||||
for param, key in cls._default_params:
|
||||
if param in full_config:
|
||||
return {key: full_config.pop(param)}
|
||||
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]):
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, Poll]):
|
||||
if isinstance(obj, Message):
|
||||
text = obj.text or obj.caption or ""
|
||||
text = obj.text or obj.caption or ''
|
||||
if not text and obj.poll:
|
||||
text = obj.poll.question
|
||||
elif isinstance(obj, CallbackQuery):
|
||||
|
|
@ -285,15 +274,26 @@ class Text(Filter):
|
|||
|
||||
if self.ignore_case:
|
||||
text = text.lower()
|
||||
_pre_process_func = lambda s: str(s).lower()
|
||||
else:
|
||||
_pre_process_func = str
|
||||
|
||||
if self.equals:
|
||||
return text == str(self.equals)
|
||||
elif self.contains:
|
||||
return str(self.contains) in text
|
||||
elif self.startswith:
|
||||
return text.startswith(str(self.startswith))
|
||||
elif self.endswith:
|
||||
return text.endswith(str(self.endswith))
|
||||
# now check
|
||||
if self.equals is not None:
|
||||
equals = list(map(_pre_process_func, self.equals))
|
||||
return text in equals
|
||||
|
||||
if self.contains is not None:
|
||||
contains = list(map(_pre_process_func, self.contains))
|
||||
return all(map(text.__contains__, contains))
|
||||
|
||||
if self.startswith is not None:
|
||||
startswith = list(map(_pre_process_func, self.startswith))
|
||||
return any(map(text.startswith, startswith))
|
||||
|
||||
if self.endswith is not None:
|
||||
endswith = list(map(_pre_process_func, self.endswith))
|
||||
return any(map(text.endswith, endswith))
|
||||
|
||||
return False
|
||||
|
||||
|
|
@ -307,7 +307,7 @@ class HashTag(Filter):
|
|||
|
||||
def __init__(self, hashtags=None, cashtags=None):
|
||||
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:
|
||||
hashtags = []
|
||||
|
|
@ -327,10 +327,10 @@ class HashTag(Filter):
|
|||
@classmethod
|
||||
def validate(cls, full_config: Dict[str, Any]):
|
||||
config = {}
|
||||
if "hashtags" in full_config:
|
||||
config["hashtags"] = full_config.pop("hashtags")
|
||||
if "cashtags" in full_config:
|
||||
config["cashtags"] = full_config.pop("cashtags")
|
||||
if 'hashtags' in full_config:
|
||||
config['hashtags'] = full_config.pop('hashtags')
|
||||
if 'cashtags' in full_config:
|
||||
config['cashtags'] = full_config.pop('cashtags')
|
||||
return config
|
||||
|
||||
async def check(self, message: types.Message):
|
||||
|
|
@ -344,13 +344,9 @@ class HashTag(Filter):
|
|||
return False
|
||||
|
||||
hashtags, cashtags = self._get_tags(text, entities)
|
||||
if (
|
||||
self.hashtags
|
||||
and set(hashtags) & set(self.hashtags)
|
||||
or self.cashtags
|
||||
and set(cashtags) & set(self.cashtags)
|
||||
):
|
||||
return {"hashtags": hashtags, "cashtags": cashtags}
|
||||
if self.hashtags 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):
|
||||
hashtags = []
|
||||
|
|
@ -358,11 +354,11 @@ class HashTag(Filter):
|
|||
|
||||
for entity in entities:
|
||||
if entity.type == types.MessageEntityType.HASHTAG:
|
||||
value = entity.get_text(text).lstrip("#")
|
||||
value = entity.get_text(text).lstrip('#')
|
||||
hashtags.append(value)
|
||||
|
||||
elif entity.type == types.MessageEntityType.CASHTAG:
|
||||
value = entity.get_text(text).lstrip("$")
|
||||
value = entity.get_text(text).lstrip('$')
|
||||
cashtags.append(value)
|
||||
|
||||
return hashtags, cashtags
|
||||
|
|
@ -380,23 +376,27 @@ class Regexp(Filter):
|
|||
|
||||
@classmethod
|
||||
def validate(cls, full_config: Dict[str, Any]):
|
||||
if "regexp" in full_config:
|
||||
return {"regexp": full_config.pop("regexp")}
|
||||
if 'regexp' in full_config:
|
||||
return {'regexp': full_config.pop('regexp')}
|
||||
|
||||
async def check(self, obj: Union[Message, CallbackQuery]):
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, Poll]):
|
||||
if isinstance(obj, Message):
|
||||
content = obj.text or obj.caption or ""
|
||||
content = obj.text or obj.caption or ''
|
||||
if not content and obj.poll:
|
||||
content = obj.poll.question
|
||||
elif isinstance(obj, CallbackQuery) and obj.data:
|
||||
content = obj.data
|
||||
elif isinstance(obj, InlineQuery):
|
||||
content = obj.query
|
||||
elif isinstance(obj, Poll):
|
||||
content = obj.question
|
||||
else:
|
||||
return False
|
||||
|
||||
match = self.regexp.search(content)
|
||||
|
||||
if match:
|
||||
return {"regexp": match}
|
||||
return {'regexp': match}
|
||||
return False
|
||||
|
||||
|
||||
|
|
@ -405,19 +405,17 @@ class RegexpCommandsFilter(BoundFilter):
|
|||
Check commands by regexp in message
|
||||
"""
|
||||
|
||||
key = "regexp_commands"
|
||||
key = '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):
|
||||
if not message.is_command():
|
||||
return False
|
||||
|
||||
command = message.text.split()[0][1:]
|
||||
command, _, mention = command.partition("@")
|
||||
command, _, mention = command.partition('@')
|
||||
|
||||
if mention and mention != (await message.bot.me).username:
|
||||
return False
|
||||
|
|
@ -425,7 +423,7 @@ class RegexpCommandsFilter(BoundFilter):
|
|||
for command in self.regexp_commands:
|
||||
search = command.search(message.text)
|
||||
if search:
|
||||
return {"regexp_command": search}
|
||||
return {'regexp_command': search}
|
||||
return False
|
||||
|
||||
|
||||
|
|
@ -434,7 +432,7 @@ class ContentTypeFilter(BoundFilter):
|
|||
Check message content type
|
||||
"""
|
||||
|
||||
key = "content_types"
|
||||
key = 'content_types'
|
||||
required = True
|
||||
default = types.ContentTypes.TEXT
|
||||
|
||||
|
|
@ -442,21 +440,18 @@ class ContentTypeFilter(BoundFilter):
|
|||
self.content_types = content_types
|
||||
|
||||
async def check(self, message):
|
||||
return (
|
||||
types.ContentType.ANY in self.content_types
|
||||
or message.content_type in self.content_types
|
||||
)
|
||||
return types.ContentType.ANY in self.content_types or \
|
||||
message.content_type in self.content_types
|
||||
|
||||
|
||||
class StateFilter(BoundFilter):
|
||||
"""
|
||||
Check user state
|
||||
"""
|
||||
|
||||
key = "state"
|
||||
key = 'state'
|
||||
required = True
|
||||
|
||||
ctx_state = ContextVar("user_state")
|
||||
ctx_state = ContextVar('user_state')
|
||||
|
||||
def __init__(self, dispatcher, state):
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
|
|
@ -464,7 +459,7 @@ class StateFilter(BoundFilter):
|
|||
self.dispatcher = dispatcher
|
||||
states = []
|
||||
if not isinstance(state, (list, set, tuple, frozenset)) or state is None:
|
||||
state = [state]
|
||||
state = [state, ]
|
||||
for item in state:
|
||||
if isinstance(item, State):
|
||||
states.append(item.state)
|
||||
|
|
@ -475,14 +470,11 @@ class StateFilter(BoundFilter):
|
|||
self.states = states
|
||||
|
||||
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):
|
||||
if "*" in self.states:
|
||||
return {"state": self.dispatcher.current_state()}
|
||||
if '*' in self.states:
|
||||
return {'state': self.dispatcher.current_state()}
|
||||
|
||||
try:
|
||||
state = self.ctx_state.get()
|
||||
|
|
@ -493,11 +485,11 @@ class StateFilter(BoundFilter):
|
|||
state = await self.dispatcher.storage.get_state(chat=chat, user=user)
|
||||
self.ctx_state.set(state)
|
||||
if state in self.states:
|
||||
return {"state": self.dispatcher.current_state(), "raw_state": state}
|
||||
return {'state': self.dispatcher.current_state(), 'raw_state': state}
|
||||
|
||||
else:
|
||||
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
|
||||
|
||||
|
|
@ -507,7 +499,7 @@ class ExceptionsFilter(BoundFilter):
|
|||
Filter for exceptions
|
||||
"""
|
||||
|
||||
key = "exception"
|
||||
key = 'exception'
|
||||
|
||||
def __init__(self, exception):
|
||||
self.exception = exception
|
||||
|
|
@ -519,3 +511,136 @@ class ExceptionsFilter(BoundFilter):
|
|||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
class IDFilter(Filter):
|
||||
|
||||
def __init__(self,
|
||||
user_id: Optional[Union[Iterable[Union[int, str]], str, int]] = None,
|
||||
chat_id: Optional[Union[Iterable[Union[int, str]], str, int]] = None,
|
||||
):
|
||||
"""
|
||||
:param user_id:
|
||||
:param chat_id:
|
||||
"""
|
||||
if user_id is None and chat_id is None:
|
||||
raise ValueError("Both user_id and chat_id can't be None")
|
||||
|
||||
self.user_id = None
|
||||
self.chat_id = None
|
||||
if user_id:
|
||||
if isinstance(user_id, Iterable):
|
||||
self.user_id = list(map(int, user_id))
|
||||
else:
|
||||
self.user_id = [int(user_id), ]
|
||||
if chat_id:
|
||||
if isinstance(chat_id, Iterable):
|
||||
self.chat_id = list(map(int, chat_id))
|
||||
else:
|
||||
self.chat_id = [int(chat_id), ]
|
||||
|
||||
@classmethod
|
||||
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
|
||||
result = {}
|
||||
if 'user_id' in full_config:
|
||||
result['user_id'] = full_config.pop('user_id')
|
||||
|
||||
if 'chat_id' in full_config:
|
||||
result['chat_id'] = full_config.pop('chat_id')
|
||||
|
||||
return result
|
||||
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]):
|
||||
if isinstance(obj, Message):
|
||||
user_id = obj.from_user.id
|
||||
chat_id = obj.chat.id
|
||||
elif isinstance(obj, CallbackQuery):
|
||||
user_id = obj.from_user.id
|
||||
chat_id = None
|
||||
if obj.message is not None:
|
||||
# if the button was sent with message
|
||||
chat_id = obj.message.chat.id
|
||||
elif isinstance(obj, InlineQuery):
|
||||
user_id = obj.from_user.id
|
||||
chat_id = None
|
||||
else:
|
||||
return False
|
||||
|
||||
if self.user_id and self.chat_id:
|
||||
return user_id in self.user_id and chat_id in self.chat_id
|
||||
if self.user_id:
|
||||
return user_id in self.user_id
|
||||
if self.chat_id:
|
||||
return chat_id in self.chat_id
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class AdminFilter(Filter):
|
||||
"""
|
||||
Checks if user is admin in a chat.
|
||||
If is_chat_admin is not set, the filter will check in the current chat (correct only for messages).
|
||||
is_chat_admin is required for InlineQuery.
|
||||
"""
|
||||
|
||||
def __init__(self, is_chat_admin: Optional[Union[Iterable[Union[int, str]], str, int, bool]] = None):
|
||||
self._check_current = False
|
||||
self._chat_ids = None
|
||||
|
||||
if is_chat_admin is False:
|
||||
raise ValueError("is_chat_admin cannot be False")
|
||||
|
||||
if is_chat_admin:
|
||||
if isinstance(is_chat_admin, bool):
|
||||
self._check_current = is_chat_admin
|
||||
if isinstance(is_chat_admin, Iterable):
|
||||
self._chat_ids = list(is_chat_admin)
|
||||
else:
|
||||
self._chat_ids = [is_chat_admin]
|
||||
else:
|
||||
self._check_current = True
|
||||
|
||||
@classmethod
|
||||
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
|
||||
result = {}
|
||||
|
||||
if "is_chat_admin" in full_config:
|
||||
result["is_chat_admin"] = full_config.pop("is_chat_admin")
|
||||
|
||||
return result
|
||||
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]) -> bool:
|
||||
user_id = obj.from_user.id
|
||||
|
||||
if self._check_current:
|
||||
if isinstance(obj, Message):
|
||||
message = obj
|
||||
elif isinstance(obj, CallbackQuery) and obj.message:
|
||||
message = obj.message
|
||||
else:
|
||||
return False
|
||||
if ChatType.is_private(message): # there is no admin in private chats
|
||||
return False
|
||||
chat_ids = [message.chat.id]
|
||||
else:
|
||||
chat_ids = self._chat_ids
|
||||
|
||||
admins = [member.user.id for chat_id in chat_ids for member in await obj.bot.get_chat_administrators(chat_id)]
|
||||
|
||||
return user_id in admins
|
||||
|
||||
|
||||
class IsReplyFilter(BoundFilter):
|
||||
"""
|
||||
Check if message is replied and send reply message to handler
|
||||
"""
|
||||
key = 'is_reply'
|
||||
|
||||
def __init__(self, is_reply):
|
||||
self.is_reply = is_reply
|
||||
|
||||
async def check(self, msg: Message):
|
||||
if msg.reply_to_message and self.is_reply:
|
||||
return {'reply': msg.reply_to_message}
|
||||
elif not msg.reply_to_message and not self.is_reply:
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -13,11 +13,9 @@ def wrap_async(func):
|
|||
async def async_wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if (
|
||||
inspect.isawaitable(func)
|
||||
or inspect.iscoroutinefunction(func)
|
||||
or isinstance(func, AbstractFilter)
|
||||
):
|
||||
if inspect.isawaitable(func) \
|
||||
or inspect.iscoroutinefunction(func) \
|
||||
or isinstance(func, AbstractFilter):
|
||||
return func
|
||||
return async_wrapper
|
||||
|
||||
|
|
@ -25,16 +23,14 @@ def wrap_async(func):
|
|||
def get_filter_spec(dispatcher, filter_: callable):
|
||||
kwargs = {}
|
||||
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_)
|
||||
if "dispatcher" in spec:
|
||||
kwargs["dispatcher"] = dispatcher
|
||||
if (
|
||||
inspect.isawaitable(filter_)
|
||||
or inspect.iscoroutinefunction(filter_)
|
||||
or isinstance(filter_, AbstractFilter)
|
||||
):
|
||||
if 'dispatcher' in spec:
|
||||
kwargs['dispatcher'] = dispatcher
|
||||
if inspect.isawaitable(filter_) \
|
||||
or inspect.iscoroutinefunction(filter_) \
|
||||
or isinstance(filter_, AbstractFilter):
|
||||
return FilterObj(filter=filter_, kwargs=kwargs, is_async=True)
|
||||
else:
|
||||
return FilterObj(filter=filter_, kwargs=kwargs, is_async=False)
|
||||
|
|
@ -86,17 +82,12 @@ class FilterRecord:
|
|||
Filters record for factory
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: typing.Callable,
|
||||
def __init__(self, callback: typing.Union[typing.Callable, 'AbstractFilter'],
|
||||
validator: typing.Optional[typing.Callable] = None,
|
||||
event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
|
||||
exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
|
||||
):
|
||||
exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None):
|
||||
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.event_handlers = event_handlers
|
||||
|
|
@ -109,17 +100,17 @@ class FilterRecord:
|
|||
elif issubclass(callback, AbstractFilter):
|
||||
self.resolver = callback.validate
|
||||
else:
|
||||
raise RuntimeError("validator is required!")
|
||||
raise RuntimeError('validator is required!')
|
||||
|
||||
def resolve(self, dispatcher, event_handler, full_config):
|
||||
if not self._check_event_handler(event_handler):
|
||||
return
|
||||
config = self.resolver(full_config)
|
||||
if config:
|
||||
if "dispatcher" not in config:
|
||||
if 'dispatcher' not in config:
|
||||
spec = inspect.getfullargspec(self.callback)
|
||||
if "dispatcher" in spec.args:
|
||||
config["dispatcher"] = dispatcher
|
||||
if 'dispatcher' in spec.args:
|
||||
config['dispatcher'] = dispatcher
|
||||
|
||||
for key in config:
|
||||
if key in full_config:
|
||||
|
|
@ -142,9 +133,7 @@ class AbstractFilter(abc.ABC):
|
|||
|
||||
@classmethod
|
||||
@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.
|
||||
|
||||
|
|
@ -195,9 +184,7 @@ class Filter(AbstractFilter):
|
|||
"""
|
||||
|
||||
@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.
|
||||
If you need to use filter from filters factory you need to override this method.
|
||||
|
|
@ -216,12 +203,12 @@ class BoundFilter(Filter):
|
|||
and ``check`` method where you need to implement filter logic.
|
||||
"""
|
||||
|
||||
"""Unique name of the filter argument. You need to override this attribute."""
|
||||
key = None
|
||||
"""If :obj:`True` this filter will be added to the all of the registered handlers"""
|
||||
"""Unique name of the filter argument. You need to override this attribute."""
|
||||
required = False
|
||||
"""Default value for configure required filters"""
|
||||
"""If :obj:`True` this filter will be added to the all of the registered handlers"""
|
||||
default = None
|
||||
"""Default value for configure required filters"""
|
||||
|
||||
@classmethod
|
||||
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
|
||||
|
|
@ -241,7 +228,7 @@ class BoundFilter(Filter):
|
|||
class _LogicFilter(Filter):
|
||||
@classmethod
|
||||
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):
|
||||
|
|
@ -253,6 +240,7 @@ class NotFilter(_LogicFilter):
|
|||
|
||||
|
||||
class AndFilter(_LogicFilter):
|
||||
|
||||
def __init__(self, *targets):
|
||||
self.targets = list(wrap_async(target) for target in targets)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class State:
|
|||
@property
|
||||
def group(self):
|
||||
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
|
||||
|
||||
def get_root(self):
|
||||
|
|
@ -25,21 +25,21 @@ class State:
|
|||
|
||||
@property
|
||||
def state(self):
|
||||
if self._state is None:
|
||||
return None
|
||||
elif self._state == "*":
|
||||
if self._state is None or self._state == '*':
|
||||
return self._state
|
||||
elif self._group_name is None and self._group:
|
||||
|
||||
if self._group_name is None and self._group:
|
||||
group = self._group.__full_group_name__
|
||||
elif self._group_name:
|
||||
group = self._group_name
|
||||
else:
|
||||
group = "@"
|
||||
return f"{group}:{self._state}"
|
||||
group = '@'
|
||||
|
||||
return f'{group}:{self._state}'
|
||||
|
||||
def set_parent(self, group):
|
||||
if not issubclass(group, StatesGroup):
|
||||
raise ValueError("Group must be subclass of StatesGroup")
|
||||
raise ValueError('Group must be subclass of StatesGroup')
|
||||
self._group = group
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
|
|
@ -73,7 +73,6 @@ class StatesGroupMeta(type):
|
|||
elif inspect.isclass(prop) and issubclass(prop, StatesGroup):
|
||||
childs.append(prop)
|
||||
prop._parent = cls
|
||||
# continue
|
||||
|
||||
cls._parent = None
|
||||
cls._childs = tuple(childs)
|
||||
|
|
@ -83,13 +82,13 @@ class StatesGroupMeta(type):
|
|||
return cls
|
||||
|
||||
@property
|
||||
def __group_name__(cls):
|
||||
def __group_name__(cls) -> str:
|
||||
return cls._group_name
|
||||
|
||||
@property
|
||||
def __full_group_name__(cls):
|
||||
def __full_group_name__(cls) -> str:
|
||||
if cls._parent:
|
||||
return cls._parent.__full_group_name__ + "." + cls._group_name
|
||||
return '.'.join((cls._parent.__full_group_name__, cls._group_name))
|
||||
return cls._group_name
|
||||
|
||||
@property
|
||||
|
|
@ -97,7 +96,7 @@ class StatesGroupMeta(type):
|
|||
return cls._states
|
||||
|
||||
@property
|
||||
def childs(cls):
|
||||
def childs(cls) -> tuple:
|
||||
return cls._childs
|
||||
|
||||
@property
|
||||
|
|
@ -130,9 +129,9 @@ class StatesGroupMeta(type):
|
|||
def __contains__(cls, item):
|
||||
if isinstance(item, str):
|
||||
return item in cls.all_states_names
|
||||
elif isinstance(item, State):
|
||||
if isinstance(item, State):
|
||||
return item in cls.all_states
|
||||
elif isinstance(item, StatesGroup):
|
||||
if isinstance(item, StatesGroup):
|
||||
return item in cls.all_childs
|
||||
return False
|
||||
|
||||
|
|
@ -195,4 +194,4 @@ class StatesGroup(metaclass=StatesGroupMeta):
|
|||
|
||||
|
||||
default_state = State()
|
||||
any_state = State(state="*")
|
||||
any_state = State(state='*')
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import inspect
|
||||
from contextvars import ContextVar
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Iterable
|
||||
from typing import Optional, Iterable, List
|
||||
|
||||
ctx_data = ContextVar("ctx_handler_data")
|
||||
current_handler = ContextVar("current_handler")
|
||||
ctx_data = ContextVar('ctx_handler_data')
|
||||
current_handler = ContextVar('current_handler')
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -23,11 +23,10 @@ class CancelHandler(Exception):
|
|||
|
||||
|
||||
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__
|
||||
|
||||
spec = inspect.getfullargspec(func)
|
||||
return spec, func
|
||||
return spec
|
||||
|
||||
|
||||
def _check_spec(spec: inspect.FullArgSpec, kwargs: dict):
|
||||
|
|
@ -42,12 +41,10 @@ class Handler:
|
|||
self.dispatcher = dispatcher
|
||||
self.once = once
|
||||
|
||||
self.handlers = []
|
||||
self.handlers: List[Handler.HandlerObj] = []
|
||||
self.middleware_key = middleware_key
|
||||
|
||||
def register(self, handler, filters=None, index=None):
|
||||
from .filters import get_filters_spec
|
||||
|
||||
"""
|
||||
Register callback
|
||||
|
||||
|
|
@ -57,7 +54,9 @@ class Handler:
|
|||
:param filters: list of filters
|
||||
:param index: you can reorder handlers
|
||||
"""
|
||||
spec, handler = _get_spec(handler)
|
||||
from .filters import get_filters_spec
|
||||
|
||||
spec = _get_spec(handler)
|
||||
|
||||
if filters and not isinstance(filters, (list, tuple, set)):
|
||||
filters = [filters]
|
||||
|
|
@ -81,7 +80,7 @@ class Handler:
|
|||
if handler is registered:
|
||||
self.handlers.remove(handler_obj)
|
||||
return True
|
||||
raise ValueError("This handler is not registered!")
|
||||
raise ValueError('This handler is not registered!')
|
||||
|
||||
async def notify(self, *args):
|
||||
"""
|
||||
|
|
@ -99,9 +98,7 @@ class Handler:
|
|||
|
||||
if self.middleware_key:
|
||||
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
|
||||
return results
|
||||
|
||||
|
|
@ -115,9 +112,7 @@ class Handler:
|
|||
ctx_token = current_handler.set(handler_obj.handler)
|
||||
try:
|
||||
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)
|
||||
response = await handler_obj.handler(*args, **partial_data)
|
||||
if response is not None:
|
||||
|
|
@ -132,9 +127,8 @@ class Handler:
|
|||
current_handler.reset(ctx_token)
|
||||
finally:
|
||||
if self.middleware_key:
|
||||
await self.dispatcher.middleware.trigger(
|
||||
f"post_process_{self.middleware_key}", args + (results, data)
|
||||
)
|
||||
await self.dispatcher.middleware.trigger(f"post_process_{self.middleware_key}",
|
||||
args + (results, data,))
|
||||
|
||||
return results
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -7,6 +7,7 @@ from .callback_game import CallbackGame
|
|||
from .callback_query import CallbackQuery
|
||||
from .chat import Chat, ChatActions, ChatType
|
||||
from .chat_member import ChatMember, ChatMemberStatus
|
||||
from .chat_permissions import ChatPermissions
|
||||
from .chat_photo import ChatPhoto
|
||||
from .chosen_inline_result import ChosenInlineResult
|
||||
from .contact import Contact
|
||||
|
|
|
|||
|
|
@ -9,37 +9,31 @@ from babel.support import LazyProxy
|
|||
from .fields import BaseField
|
||||
from ..utils import json
|
||||
from ..utils.mixins import ContextInstanceMixin
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..bot.bot import Bot
|
||||
|
||||
__all__ = (
|
||||
"MetaTelegramObject",
|
||||
"TelegramObject",
|
||||
"InputFile",
|
||||
"String",
|
||||
"Integer",
|
||||
"Float",
|
||||
"Boolean",
|
||||
)
|
||||
__all__ = ('MetaTelegramObject', 'TelegramObject', 'InputFile', 'String', 'Integer', 'Float', 'Boolean')
|
||||
|
||||
PROPS_ATTR_NAME = "_props"
|
||||
VALUES_ATTR_NAME = "_values"
|
||||
ALIASES_ATTR_NAME = "_aliases"
|
||||
PROPS_ATTR_NAME = '_props'
|
||||
VALUES_ATTR_NAME = '_values'
|
||||
ALIASES_ATTR_NAME = '_aliases'
|
||||
|
||||
# Binding of builtin types
|
||||
InputFile = TypeVar("InputFile", "InputFile", io.BytesIO, io.FileIO, str)
|
||||
String = TypeVar("String", bound=str)
|
||||
Integer = TypeVar("Integer", bound=int)
|
||||
Float = TypeVar("Float", bound=float)
|
||||
Boolean = TypeVar("Boolean", bound=bool)
|
||||
InputFile = TypeVar('InputFile', 'InputFile', io.BytesIO, io.FileIO, str)
|
||||
String = TypeVar('String', bound=str)
|
||||
Integer = TypeVar('Integer', bound=int)
|
||||
Float = TypeVar('Float', bound=float)
|
||||
Boolean = TypeVar('Boolean', bound=bool)
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class MetaTelegramObject(type):
|
||||
"""
|
||||
Metaclass for telegram objects
|
||||
"""
|
||||
|
||||
_objects = {}
|
||||
|
||||
def __new__(mcs, name, bases, namespace, **kwargs):
|
||||
def __new__(mcs: typing.Type[T], name: str, bases: typing.Tuple[typing.Type], namespace: typing.Dict[str, typing.Any], **kwargs: typing.Any) -> T:
|
||||
cls = super(MetaTelegramObject, mcs).__new__(mcs, name, bases, namespace)
|
||||
|
||||
props = {}
|
||||
|
|
@ -55,9 +49,7 @@ class MetaTelegramObject(type):
|
|||
aliases.update(getattr(base, ALIASES_ATTR_NAME))
|
||||
|
||||
# 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
|
||||
if prop.default is not None:
|
||||
values[prop.alias] = prop.default
|
||||
|
|
@ -82,7 +74,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
Abstract class for telegram objects
|
||||
"""
|
||||
|
||||
def __init__(self, conf=None, **kwargs):
|
||||
def __init__(self, conf: typing.Dict[str, typing.Any]=None, **kwargs: typing.Any) -> None:
|
||||
"""
|
||||
Deserialize object
|
||||
|
||||
|
|
@ -128,7 +120,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
return getattr(self, ALIASES_ATTR_NAME, {})
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
def values(self) -> typing.Tuple[str]:
|
||||
"""
|
||||
Get values
|
||||
|
||||
|
|
@ -139,11 +131,11 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
return getattr(self, VALUES_ATTR_NAME)
|
||||
|
||||
@property
|
||||
def telegram_types(self):
|
||||
def telegram_types(self) -> typing.List[TelegramObject]:
|
||||
return type(self).telegram_types
|
||||
|
||||
@classmethod
|
||||
def to_object(cls, data):
|
||||
def to_object(cls: typing.Type[T], data: typing.Dict[str, typing.Any]) -> T:
|
||||
"""
|
||||
Deserialize object
|
||||
|
||||
|
|
@ -153,19 +145,17 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
return cls(**data)
|
||||
|
||||
@property
|
||||
def bot(self):
|
||||
def bot(self) -> Bot:
|
||||
from ..bot.bot import Bot
|
||||
|
||||
bot = Bot.get_current()
|
||||
if bot is None:
|
||||
raise RuntimeError(
|
||||
"Can't get bot instance from context. "
|
||||
raise RuntimeError("Can't get bot instance from context. "
|
||||
"You can fix it with setting current instance: "
|
||||
"'Bot.set_current(bot_instance)'"
|
||||
)
|
||||
"'Bot.set_current(bot_instance)'")
|
||||
return bot
|
||||
|
||||
def to_python(self) -> typing.Dict:
|
||||
def to_python(self) -> typing.Dict[str, typing.Any]:
|
||||
"""
|
||||
Get object as JSON serializable
|
||||
|
||||
|
|
@ -183,7 +173,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
result[self.props_aliases.get(name, name)] = value
|
||||
return result
|
||||
|
||||
def clean(self):
|
||||
def clean(self) -> None:
|
||||
"""
|
||||
Remove empty values
|
||||
"""
|
||||
|
|
@ -201,7 +191,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
return json.dumps(self.to_python())
|
||||
|
||||
@classmethod
|
||||
def create(cls, *args, **kwargs):
|
||||
def create(cls: Type[T], *args: typing.Any, **kwargs: typing.Any) -> T:
|
||||
raise NotImplemented
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
@ -212,7 +202,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
"""
|
||||
return self.as_json()
|
||||
|
||||
def __getitem__(self, item):
|
||||
def __getitem__(self, item: typing.Union[str, int]) -> typing.Any:
|
||||
"""
|
||||
Item getter (by key)
|
||||
|
||||
|
|
@ -223,7 +213,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
return self.props[item].get_value(self)
|
||||
raise KeyError(item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
def __setitem__(self, key: str, value: typing.Any) -> None:
|
||||
"""
|
||||
Item setter (by key)
|
||||
|
||||
|
|
@ -232,10 +222,10 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
:return:
|
||||
"""
|
||||
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)
|
||||
|
||||
def __contains__(self, item):
|
||||
def __contains__(self, item: typing.Dict[str, typing.Any]) -> bool:
|
||||
"""
|
||||
Check key contains in that object
|
||||
|
||||
|
|
@ -245,7 +235,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
self.clean()
|
||||
return item in self.values
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> typing.Iterator[str]:
|
||||
"""
|
||||
Iterate over items
|
||||
|
||||
|
|
@ -254,7 +244,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
for item in self.to_python().items():
|
||||
yield item
|
||||
|
||||
def iter_keys(self):
|
||||
def iter_keys(self) -> typing.Generator[typing.Any, None, None]:
|
||||
"""
|
||||
Iterate over keys
|
||||
|
||||
|
|
@ -263,7 +253,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
for key, _ in self:
|
||||
yield key
|
||||
|
||||
def iter_values(self):
|
||||
def iter_values(self) -> typing.Generator[typing.Any, None, None]:
|
||||
"""
|
||||
Iterate over values
|
||||
|
||||
|
|
@ -272,9 +262,9 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
for _, value in self:
|
||||
yield value
|
||||
|
||||
def __hash__(self):
|
||||
def _hash(obj):
|
||||
buf = 0
|
||||
def __hash__(self) -> int:
|
||||
def _hash(obj)-> int:
|
||||
buf: int = 0
|
||||
if isinstance(obj, list):
|
||||
for item in obj:
|
||||
buf += _hash(item)
|
||||
|
|
@ -294,5 +284,5 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
|
||||
return result
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other: TelegramObject) -> bool:
|
||||
return isinstance(other, self.__class__) and hash(other) == hash(self)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import typing
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
from .chat_permissions import ChatPermissions
|
||||
from .chat_photo import ChatPhoto
|
||||
from ..utils import helper
|
||||
from ..utils import markdown
|
||||
|
|
@ -16,7 +18,6 @@ class Chat(base.TelegramObject):
|
|||
|
||||
https://core.telegram.org/bots/api#chat
|
||||
"""
|
||||
|
||||
id: base.Integer = fields.Field()
|
||||
type: base.String = fields.Field()
|
||||
title: base.String = fields.Field()
|
||||
|
|
@ -27,7 +28,8 @@ class Chat(base.TelegramObject):
|
|||
photo: ChatPhoto = fields.Field(base=ChatPhoto)
|
||||
description: base.String = fields.Field()
|
||||
invite_link: base.String = fields.Field()
|
||||
pinned_message: "Message" = fields.Field(base="Message")
|
||||
pinned_message: 'Message' = fields.Field(base='Message')
|
||||
permissions: ChatPermissions = fields.Field(base=ChatPermissions)
|
||||
sticker_set_name: base.String = fields.Field()
|
||||
can_set_sticker_set: base.Boolean = fields.Field()
|
||||
|
||||
|
|
@ -39,7 +41,7 @@ class Chat(base.TelegramObject):
|
|||
if self.type == ChatType.PRIVATE:
|
||||
full_name = self.first_name
|
||||
if self.last_name:
|
||||
full_name += " " + self.last_name
|
||||
full_name += ' ' + self.last_name
|
||||
return full_name
|
||||
return self.title
|
||||
|
||||
|
|
@ -49,7 +51,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
|
||||
"""
|
||||
if self.username:
|
||||
return "@" + self.username
|
||||
return '@' + self.username
|
||||
if self.type == ChatType.PRIVATE:
|
||||
return self.full_name
|
||||
return None
|
||||
|
|
@ -57,7 +59,7 @@ class Chat(base.TelegramObject):
|
|||
@property
|
||||
def user_url(self):
|
||||
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}"
|
||||
|
||||
|
|
@ -80,7 +82,7 @@ class Chat(base.TelegramObject):
|
|||
return f"tg://user?id={self.id}"
|
||||
|
||||
if self.username:
|
||||
return f"https://t.me/{self.username}"
|
||||
return f'https://t.me/{self.username}'
|
||||
|
||||
if self.invite_link:
|
||||
return self.invite_link
|
||||
|
|
@ -162,9 +164,8 @@ class Chat(base.TelegramObject):
|
|||
"""
|
||||
return await self.bot.delete_chat_description(self.id, description)
|
||||
|
||||
async def kick(
|
||||
self, user_id: base.Integer, until_date: typing.Union[base.Integer, None] = None
|
||||
):
|
||||
async def kick(self, user_id: base.Integer,
|
||||
until_date: typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None] = None):
|
||||
"""
|
||||
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
|
||||
|
|
@ -203,15 +204,13 @@ class Chat(base.TelegramObject):
|
|||
"""
|
||||
return await self.bot.unban_chat_member(self.id, user_id=user_id)
|
||||
|
||||
async def restrict(
|
||||
self,
|
||||
user_id: base.Integer,
|
||||
until_date: typing.Union[base.Integer, None] = None,
|
||||
async def restrict(self, user_id: base.Integer,
|
||||
permissions: typing.Optional[ChatPermissions] = None,
|
||||
until_date: typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None] = None,
|
||||
can_send_messages: typing.Union[base.Boolean, None] = None,
|
||||
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:
|
||||
can_add_web_page_previews: typing.Union[base.Boolean, None] = None) -> base.Boolean:
|
||||
"""
|
||||
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.
|
||||
|
|
@ -221,6 +220,8 @@ class Chat(base.TelegramObject):
|
|||
|
||||
:param user_id: Unique identifier of the target user
|
||||
:type user_id: :obj:`base.Integer`
|
||||
:param permissions: New user permissions
|
||||
:type permissions: :obj:`ChatPermissions`
|
||||
:param until_date: Date when restrictions will be lifted for the user, unix time.
|
||||
:type until_date: :obj:`typing.Union[base.Integer, None]`
|
||||
:param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues
|
||||
|
|
@ -237,19 +238,15 @@ class Chat(base.TelegramObject):
|
|||
:return: Returns True on success.
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
return await self.bot.restrict_chat_member(
|
||||
self.id,
|
||||
user_id=user_id,
|
||||
return await self.bot.restrict_chat_member(self.id, user_id=user_id,
|
||||
permissions=permissions,
|
||||
until_date=until_date,
|
||||
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,
|
||||
)
|
||||
can_add_web_page_previews=can_add_web_page_previews)
|
||||
|
||||
async def promote(
|
||||
self,
|
||||
user_id: base.Integer,
|
||||
async def promote(self, user_id: base.Integer,
|
||||
can_change_info: typing.Union[base.Boolean, None] = None,
|
||||
can_post_messages: typing.Union[base.Boolean, None] = None,
|
||||
can_edit_messages: typing.Union[base.Boolean, None] = None,
|
||||
|
|
@ -257,8 +254,7 @@ class Chat(base.TelegramObject):
|
|||
can_invite_users: typing.Union[base.Boolean, None] = None,
|
||||
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:
|
||||
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.
|
||||
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
|
|
@ -289,8 +285,7 @@ class Chat(base.TelegramObject):
|
|||
:return: Returns True on success.
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
return await self.bot.promote_chat_member(
|
||||
self.id,
|
||||
return await self.bot.promote_chat_member(self.id,
|
||||
user_id=user_id,
|
||||
can_change_info=can_change_info,
|
||||
can_post_messages=can_post_messages,
|
||||
|
|
@ -299,8 +294,7 @@ class Chat(base.TelegramObject):
|
|||
can_invite_users=can_invite_users,
|
||||
can_restrict_members=can_restrict_members,
|
||||
can_pin_messages=can_pin_messages,
|
||||
can_promote_members=can_promote_members,
|
||||
)
|
||||
can_promote_members=can_promote_members)
|
||||
|
||||
async def pin_message(self, message_id: int, disable_notification: bool = False):
|
||||
"""
|
||||
|
|
@ -436,9 +430,9 @@ class ChatType(helper.Helper):
|
|||
|
||||
@staticmethod
|
||||
def _check(obj, chat_types) -> bool:
|
||||
if hasattr(obj, "chat"):
|
||||
if hasattr(obj, 'chat'):
|
||||
obj = obj.chat
|
||||
if not hasattr(obj, "type"):
|
||||
if not hasattr(obj, 'type'):
|
||||
return False
|
||||
return obj.type in chat_types
|
||||
|
||||
|
|
@ -525,13 +519,12 @@ class ChatActions(helper.Helper):
|
|||
@classmethod
|
||||
async def _do(cls, action: str, sleep=None):
|
||||
from aiogram import Bot
|
||||
|
||||
await Bot.get_current().send_chat_action(Chat.get_current().id, action)
|
||||
if sleep:
|
||||
await asyncio.sleep(sleep)
|
||||
|
||||
@classmethod
|
||||
def calc_timeout(cls, text, timeout=0.8):
|
||||
def calc_timeout(cls, text, timeout=.8):
|
||||
"""
|
||||
Calculate timeout for text
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import datetime
|
||||
import warnings
|
||||
from typing import Optional
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
|
|
@ -13,7 +14,6 @@ class ChatMember(base.TelegramObject):
|
|||
|
||||
https://core.telegram.org/bots/api#chatmember
|
||||
"""
|
||||
|
||||
user: User = fields.Field(base=User)
|
||||
status: base.String = fields.Field()
|
||||
until_date: datetime.datetime = fields.DateTimeField()
|
||||
|
|
@ -29,25 +29,17 @@ class ChatMember(base.TelegramObject):
|
|||
is_member: base.Boolean = fields.Field()
|
||||
can_send_messages: base.Boolean = fields.Field()
|
||||
can_send_media_messages: base.Boolean = fields.Field()
|
||||
can_send_polls: base.Boolean = fields.Field()
|
||||
can_send_other_messages: base.Boolean = fields.Field()
|
||||
can_add_web_page_previews: base.Boolean = fields.Field()
|
||||
|
||||
def is_admin(self):
|
||||
warnings.warn(
|
||||
"`is_admin` method deprecated due to updates in Bot API 4.2. "
|
||||
"This method renamed to `is_chat_admin` and will be available until aiogram 2.3",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.is_chat_admin()
|
||||
def is_chat_admin(self) -> bool:
|
||||
return ChatMemberStatus.is_chat_admin(self.status)
|
||||
|
||||
def is_chat_admin(self):
|
||||
return ChatMemberStatus.is_admin(self.status)
|
||||
def is_chat_member(self) -> bool:
|
||||
return ChatMemberStatus.is_chat_member(self.status)
|
||||
|
||||
def is_chat_member(self):
|
||||
return ChatMemberStatus.is_member(self.status)
|
||||
|
||||
def __int__(self):
|
||||
def __int__(self) -> int:
|
||||
return self.user.id
|
||||
|
||||
|
||||
|
|
@ -55,39 +47,19 @@ class ChatMemberStatus(helper.Helper):
|
|||
"""
|
||||
Chat member status
|
||||
"""
|
||||
|
||||
mode = helper.HelperMode.lowercase
|
||||
|
||||
CREATOR = helper.Item() # creator
|
||||
ADMINISTRATOR = helper.Item() # administrator
|
||||
MEMBER = helper.Item() # member
|
||||
RESTRICTED = helper.Item() # restricted
|
||||
LEFT = helper.Item() # left
|
||||
KICKED = helper.Item() # kicked
|
||||
|
||||
@classmethod
|
||||
def is_admin(cls, role):
|
||||
warnings.warn(
|
||||
"`is_admin` method deprecated due to updates in Bot API 4.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)
|
||||
|
||||
@classmethod
|
||||
def is_member(cls, role):
|
||||
warnings.warn(
|
||||
"`is_member` method deprecated due to updates in Bot API 4.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)
|
||||
|
||||
@classmethod
|
||||
def is_chat_admin(cls, role):
|
||||
def is_chat_admin(cls, role: str) -> bool:
|
||||
return role in [cls.ADMINISTRATOR, cls.CREATOR]
|
||||
|
||||
@classmethod
|
||||
def is_chat_member(cls, role):
|
||||
return role in [cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR]
|
||||
def is_chat_member(cls, role: str) -> bool:
|
||||
return role in [cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR, cls.RESTRICTED]
|
||||
|
|
|
|||
39
aiogram/types/chat_permissions.py
Normal file
39
aiogram/types/chat_permissions.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from . import base
|
||||
from . import fields
|
||||
|
||||
|
||||
class ChatPermissions(base.TelegramObject):
|
||||
"""
|
||||
Describes actions that a non-administrator user is allowed to take in a chat.
|
||||
|
||||
https://core.telegram.org/bots/api#chatpermissions
|
||||
"""
|
||||
can_send_messages: base.Boolean = fields.Field()
|
||||
can_send_media_messages: base.Boolean = fields.Field()
|
||||
can_send_polls: base.Boolean = fields.Field()
|
||||
can_send_other_messages: base.Boolean = fields.Field()
|
||||
can_add_web_page_previews: base.Boolean = fields.Field()
|
||||
can_change_info: base.Boolean = fields.Field()
|
||||
can_invite_users: base.Boolean = fields.Field()
|
||||
can_pin_messages: base.Boolean = fields.Field()
|
||||
|
||||
def __init__(self,
|
||||
can_send_messages: base.Boolean = None,
|
||||
can_send_media_messages: base.Boolean = None,
|
||||
can_send_polls: base.Boolean = None,
|
||||
can_send_other_messages: base.Boolean = None,
|
||||
can_add_web_page_previews: base.Boolean = None,
|
||||
can_change_info: base.Boolean = None,
|
||||
can_invite_users: base.Boolean = None,
|
||||
can_pin_messages: base.Boolean = None,
|
||||
**kwargs):
|
||||
super(ChatPermissions, self).__init__(
|
||||
can_send_messages=can_send_messages,
|
||||
can_send_media_messages=can_send_media_messages,
|
||||
can_send_polls=can_send_polls,
|
||||
can_send_other_messages=can_send_other_messages,
|
||||
can_add_web_page_previews=can_add_web_page_previews,
|
||||
can_change_info=can_change_info,
|
||||
can_invite_users=can_invite_users,
|
||||
can_pin_messages=can_pin_messages,
|
||||
)
|
||||
|
|
@ -6,7 +6,7 @@ from . import base
|
|||
from . import fields
|
||||
from .input_file import InputFile
|
||||
|
||||
ATTACHMENT_PREFIX = "attach://"
|
||||
ATTACHMENT_PREFIX = 'attach://'
|
||||
|
||||
|
||||
class InputMedia(base.TelegramObject):
|
||||
|
|
@ -22,26 +22,23 @@ class InputMedia(base.TelegramObject):
|
|||
|
||||
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")
|
||||
thumb: typing.Union[base.InputFile, base.String] = fields.Field(
|
||||
alias="thumb", on_change="_thumb_changed"
|
||||
)
|
||||
type: base.String = fields.Field(default='photo')
|
||||
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()
|
||||
parse_mode: base.Boolean = fields.Field()
|
||||
parse_mode: base.String = fields.Field()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._thumb_file = None
|
||||
self._media_file = None
|
||||
|
||||
media = kwargs.pop("media", None)
|
||||
media = kwargs.pop('media', None)
|
||||
if isinstance(media, (io.IOBase, InputFile)):
|
||||
self.file = media
|
||||
elif media is not None:
|
||||
self.media = media
|
||||
|
||||
thumb = kwargs.pop("thumb", None)
|
||||
thumb = kwargs.pop('thumb', None)
|
||||
if isinstance(thumb, (io.IOBase, InputFile)):
|
||||
self.thumb_file = thumb
|
||||
elif thumb is not None:
|
||||
|
|
@ -61,7 +58,7 @@ class InputMedia(base.TelegramObject):
|
|||
|
||||
@file.setter
|
||||
def file(self, file: io.IOBase):
|
||||
self.media = "attach://" + secrets.token_urlsafe(16)
|
||||
self.media = 'attach://' + secrets.token_urlsafe(16)
|
||||
self._media_file = file
|
||||
|
||||
@file.deleter
|
||||
|
|
@ -70,7 +67,7 @@ class InputMedia(base.TelegramObject):
|
|||
self._media_file = None
|
||||
|
||||
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
|
||||
|
||||
@property
|
||||
|
|
@ -79,7 +76,7 @@ class InputMedia(base.TelegramObject):
|
|||
|
||||
@thumb_file.setter
|
||||
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
|
||||
|
||||
@thumb_file.deleter
|
||||
|
|
@ -88,7 +85,7 @@ class InputMedia(base.TelegramObject):
|
|||
self._thumb_file = None
|
||||
|
||||
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
|
||||
|
||||
def get_files(self):
|
||||
|
|
@ -109,28 +106,14 @@ class InputMediaAnimation(InputMedia):
|
|||
height: base.Integer = fields.Field()
|
||||
duration: base.Integer = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
media: base.InputFile,
|
||||
def __init__(self, media: base.InputFile,
|
||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
caption: base.String = None,
|
||||
width: base.Integer = None,
|
||||
height: base.Integer = None,
|
||||
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,
|
||||
)
|
||||
width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None,
|
||||
parse_mode: base.String = 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):
|
||||
|
|
@ -140,22 +123,11 @@ class InputMediaDocument(InputMedia):
|
|||
https://core.telegram.org/bots/api#inputmediadocument
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
media: base.InputFile,
|
||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
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,
|
||||
)
|
||||
def __init__(self, media: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
caption: base.String = None, parse_mode: base.String = None, **kwargs):
|
||||
super(InputMediaDocument, self).__init__(type='document', media=media, thumb=thumb,
|
||||
caption=caption, parse_mode=parse_mode,
|
||||
conf=kwargs)
|
||||
|
||||
|
||||
class InputMediaAudio(InputMedia):
|
||||
|
|
@ -171,32 +143,18 @@ class InputMediaAudio(InputMedia):
|
|||
performer: base.String = fields.Field()
|
||||
title: base.String = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
media: base.InputFile,
|
||||
def __init__(self, media: base.InputFile,
|
||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
caption: base.String = None,
|
||||
width: base.Integer = None,
|
||||
height: base.Integer = None,
|
||||
width: base.Integer = None, height: base.Integer = None,
|
||||
duration: base.Integer = None,
|
||||
performer: base.String = None,
|
||||
title: base.String = None,
|
||||
parse_mode: base.Boolean = None,
|
||||
**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,
|
||||
)
|
||||
parse_mode: base.String = None, **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):
|
||||
|
|
@ -206,22 +164,11 @@ class InputMediaPhoto(InputMedia):
|
|||
https://core.telegram.org/bots/api#inputmediaphoto
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
media: base.InputFile,
|
||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
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,
|
||||
)
|
||||
def __init__(self, media: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
caption: base.String = None, parse_mode: base.String = None, **kwargs):
|
||||
super(InputMediaPhoto, self).__init__(type='photo', media=media, thumb=thumb,
|
||||
caption=caption, parse_mode=parse_mode,
|
||||
conf=kwargs)
|
||||
|
||||
|
||||
class InputMediaVideo(InputMedia):
|
||||
|
|
@ -230,36 +177,21 @@ class InputMediaVideo(InputMedia):
|
|||
|
||||
https://core.telegram.org/bots/api#inputmediavideo
|
||||
"""
|
||||
|
||||
width: base.Integer = fields.Field()
|
||||
height: base.Integer = fields.Field()
|
||||
duration: base.Integer = fields.Field()
|
||||
supports_streaming: base.Boolean = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
media: base.InputFile,
|
||||
def __init__(self, media: base.InputFile,
|
||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
caption: base.String = None,
|
||||
width: base.Integer = None,
|
||||
height: base.Integer = None,
|
||||
duration: base.Integer = None,
|
||||
parse_mode: base.Boolean = None,
|
||||
supports_streaming: base.Boolean = None,
|
||||
**kwargs,
|
||||
):
|
||||
super(InputMediaVideo, self).__init__(
|
||||
type="video",
|
||||
media=media,
|
||||
thumb=thumb,
|
||||
caption=caption,
|
||||
width=width,
|
||||
height=height,
|
||||
duration=duration,
|
||||
width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None,
|
||||
parse_mode: base.String = None,
|
||||
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,
|
||||
)
|
||||
supports_streaming=supports_streaming, conf=kwargs)
|
||||
|
||||
|
||||
class MediaGroup(base.TelegramObject):
|
||||
|
|
@ -267,9 +199,7 @@ class MediaGroup(base.TelegramObject):
|
|||
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__()
|
||||
self.media = []
|
||||
|
||||
|
|
@ -292,13 +222,13 @@ class MediaGroup(base.TelegramObject):
|
|||
:param media:
|
||||
"""
|
||||
if isinstance(media, dict):
|
||||
if "type" not in media:
|
||||
if 'type' not in media:
|
||||
raise ValueError(f"Invalid media!")
|
||||
|
||||
media_type = media["type"]
|
||||
if media_type == "photo":
|
||||
media_type = media['type']
|
||||
if media_type == 'photo':
|
||||
media = InputMediaPhoto(**media)
|
||||
elif media_type == "video":
|
||||
elif media_type == 'video':
|
||||
media = InputMediaVideo(**media)
|
||||
# elif media_type == 'document':
|
||||
# media = InputMediaDocument(**media)
|
||||
|
|
@ -310,11 +240,9 @@ class MediaGroup(base.TelegramObject):
|
|||
raise TypeError(f"Invalid media type '{media_type}'!")
|
||||
|
||||
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!")
|
||||
|
||||
self.media.append(media)
|
||||
|
|
@ -349,7 +277,7 @@ class MediaGroup(base.TelegramObject):
|
|||
duration: base.Integer = None,
|
||||
performer: base.String = None,
|
||||
title: base.String = None,
|
||||
parse_mode: base.Boolean = None):
|
||||
parse_mode: base.String = None):
|
||||
"""
|
||||
Attach animation
|
||||
|
||||
|
|
@ -371,7 +299,7 @@ class MediaGroup(base.TelegramObject):
|
|||
self.attach(audio)
|
||||
|
||||
def attach_document(self, document: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
caption: base.String = None, parse_mode: base.Boolean = None):
|
||||
caption: base.String = None, parse_mode: base.String = None):
|
||||
"""
|
||||
Attach document
|
||||
|
||||
|
|
@ -385,9 +313,8 @@ class MediaGroup(base.TelegramObject):
|
|||
self.attach(document)
|
||||
'''
|
||||
|
||||
def attach_photo(
|
||||
self, photo: typing.Union[InputMediaPhoto, base.InputFile], caption: base.String = None
|
||||
):
|
||||
def attach_photo(self, photo: typing.Union[InputMediaPhoto, base.InputFile],
|
||||
caption: base.String = None):
|
||||
"""
|
||||
Attach photo
|
||||
|
||||
|
|
@ -398,15 +325,10 @@ class MediaGroup(base.TelegramObject):
|
|||
photo = InputMediaPhoto(media=photo, caption=caption)
|
||||
self.attach(photo)
|
||||
|
||||
def attach_video(
|
||||
self,
|
||||
video: typing.Union[InputMediaVideo, base.InputFile],
|
||||
def attach_video(self, video: typing.Union[InputMediaVideo, base.InputFile],
|
||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
caption: base.String = None,
|
||||
width: base.Integer = None,
|
||||
height: base.Integer = None,
|
||||
duration: base.Integer = None,
|
||||
):
|
||||
width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None):
|
||||
"""
|
||||
Attach video
|
||||
|
||||
|
|
@ -417,14 +339,8 @@ class MediaGroup(base.TelegramObject):
|
|||
:param duration:
|
||||
"""
|
||||
if not isinstance(video, InputMedia):
|
||||
video = InputMediaVideo(
|
||||
media=video,
|
||||
thumb=thumb,
|
||||
caption=caption,
|
||||
width=width,
|
||||
height=height,
|
||||
duration=duration,
|
||||
)
|
||||
video = InputMediaVideo(media=video, thumb=thumb, caption=caption,
|
||||
width=width, height=height, duration=duration)
|
||||
self.attach(video)
|
||||
|
||||
def to_python(self) -> typing.List:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -50,31 +50,26 @@ class MessageEntity(base.TelegramObject):
|
|||
entity_text = self.get_text(text)
|
||||
|
||||
if self.type == MessageEntityType.BOLD:
|
||||
if as_html:
|
||||
return markdown.hbold(entity_text)
|
||||
return markdown.bold(entity_text)
|
||||
elif self.type == MessageEntityType.ITALIC:
|
||||
if as_html:
|
||||
return markdown.hitalic(entity_text)
|
||||
return markdown.italic(entity_text)
|
||||
elif self.type == MessageEntityType.PRE:
|
||||
if as_html:
|
||||
return markdown.hpre(entity_text)
|
||||
return markdown.pre(entity_text)
|
||||
elif self.type == MessageEntityType.CODE:
|
||||
if as_html:
|
||||
return markdown.hcode(entity_text)
|
||||
return markdown.code(entity_text)
|
||||
elif self.type == MessageEntityType.URL:
|
||||
if as_html:
|
||||
return markdown.hlink(entity_text, entity_text)
|
||||
return markdown.link(entity_text, entity_text)
|
||||
elif self.type == MessageEntityType.TEXT_LINK:
|
||||
if as_html:
|
||||
return markdown.hlink(entity_text, self.url)
|
||||
return markdown.link(entity_text, self.url)
|
||||
elif self.type == MessageEntityType.TEXT_MENTION and self.user:
|
||||
return self.user.get_mention(entity_text)
|
||||
method = markdown.hbold if as_html else markdown.bold
|
||||
return method(entity_text)
|
||||
if self.type == MessageEntityType.ITALIC:
|
||||
method = markdown.hitalic if as_html else markdown.italic
|
||||
return method(entity_text)
|
||||
if self.type == MessageEntityType.PRE:
|
||||
method = markdown.hpre if as_html else markdown.pre
|
||||
return method(entity_text)
|
||||
if self.type == MessageEntityType.CODE:
|
||||
method = markdown.hcode if as_html else markdown.code
|
||||
return method(entity_text)
|
||||
if self.type == MessageEntityType.URL:
|
||||
method = markdown.hlink if as_html else markdown.link
|
||||
return method(entity_text, entity_text)
|
||||
if self.type == MessageEntityType.TEXT_LINK:
|
||||
method = markdown.hlink if as_html else markdown.link
|
||||
return method(entity_text, self.url)
|
||||
if self.type == MessageEntityType.TEXT_MENTION and self.user:
|
||||
return self.user.get_mention(entity_text, as_html=as_html)
|
||||
|
||||
return entity_text
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class Downloadable:
|
|||
if destination is None:
|
||||
destination = file.file_path
|
||||
elif isinstance(destination, (str, pathlib.Path)) and os.path.isdir(destination):
|
||||
os.path.join(destination, file.file_path)
|
||||
destination = os.path.join(destination, file.file_path)
|
||||
else:
|
||||
is_path = False
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class Sticker(base.TelegramObject, mixins.Downloadable):
|
|||
file_id: base.String = fields.Field()
|
||||
width: base.Integer = fields.Field()
|
||||
height: base.Integer = fields.Field()
|
||||
is_animated: base.Boolean = fields.Field()
|
||||
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
||||
emoji: base.String = fields.Field()
|
||||
set_name: base.String = fields.Field()
|
||||
|
|
|
|||
|
|
@ -14,5 +14,6 @@ class StickerSet(base.TelegramObject):
|
|||
|
||||
name: base.String = fields.Field()
|
||||
title: base.String = fields.Field()
|
||||
is_animated: base.Boolean = fields.Field()
|
||||
contains_masks: base.Boolean = fields.Field()
|
||||
stickers: typing.List[Sticker] = fields.ListField(base=Sticker)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import babel
|
||||
|
||||
from . import base
|
||||
|
|
@ -46,7 +48,7 @@ class User(base.TelegramObject):
|
|||
return self.full_name
|
||||
|
||||
@property
|
||||
def locale(self) -> babel.core.Locale or None:
|
||||
def locale(self) -> Optional[babel.core.Locale]:
|
||||
"""
|
||||
Get user's locale
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import collections
|
|||
import hashlib
|
||||
import hmac
|
||||
|
||||
from aiogram.utils.deprecated import deprecated
|
||||
|
||||
|
||||
@deprecated('`generate_hash` is outdated, please use `check_signature` or `check_integrity`', stacklevel=3)
|
||||
def generate_hash(data: dict, token: str) -> str:
|
||||
"""
|
||||
Generate secret hash
|
||||
|
|
@ -24,6 +27,7 @@ def generate_hash(data: dict, token: str) -> str:
|
|||
return hmac.new(secret.digest(), msg.encode("utf-8"), digestmod=hashlib.sha256).hexdigest()
|
||||
|
||||
|
||||
@deprecated('`check_token` helper was renamed to `check_integrity`', stacklevel=3)
|
||||
def check_token(data: dict, token: str) -> bool:
|
||||
"""
|
||||
Validate auth token
|
||||
|
|
@ -34,3 +38,32 @@ def check_token(data: dict, token: str) -> bool:
|
|||
"""
|
||||
param_hash = data.get("hash", "") or ""
|
||||
return param_hash == generate_hash(data, token)
|
||||
|
||||
|
||||
def check_signature(token: str, hash: str, **kwargs) -> bool:
|
||||
"""
|
||||
Generate hexadecimal representation
|
||||
of the HMAC-SHA-256 signature of the data-check-string
|
||||
with the SHA256 hash of the bot's token used as a secret key
|
||||
|
||||
:param token:
|
||||
:param hash:
|
||||
:param kwargs: all params received on auth
|
||||
:return:
|
||||
"""
|
||||
secret = hashlib.sha256(token.encode('utf-8'))
|
||||
check_string = '\n'.join(map(lambda k: f'{k}={kwargs[k]}', sorted(kwargs)))
|
||||
hmac_string = hmac.new(secret.digest(), check_string.encode('utf-8'), digestmod=hashlib.sha256).hexdigest()
|
||||
return hmac_string == hash
|
||||
|
||||
|
||||
def check_integrity(token: str, data: dict) -> bool:
|
||||
"""
|
||||
Verify the authentication and the integrity
|
||||
of the data received on user's auth
|
||||
|
||||
:param token: Bot's token
|
||||
:param data: all data that came on auth
|
||||
:return:
|
||||
"""
|
||||
return check_signature(token, **data)
|
||||
|
|
|
|||
|
|
@ -26,15 +26,15 @@ class CallbackData:
|
|||
Callback data factory
|
||||
"""
|
||||
|
||||
def __init__(self, prefix, *parts, sep=":"):
|
||||
def __init__(self, prefix, *parts, sep=':'):
|
||||
if not isinstance(prefix, str):
|
||||
raise TypeError(f"Prefix must be instance of str not {type(prefix).__name__}")
|
||||
elif not prefix:
|
||||
raise TypeError(f'Prefix must be instance of str not {type(prefix).__name__}')
|
||||
if not prefix:
|
||||
raise ValueError("Prefix can't be empty")
|
||||
elif sep in prefix:
|
||||
raise ValueError(f"Separator '{sep}' can't be used in prefix")
|
||||
elif not parts:
|
||||
raise TypeError("Parts is not passed!")
|
||||
if sep in prefix:
|
||||
raise ValueError(f"Separator {sep!r} can't be used in prefix")
|
||||
if not parts:
|
||||
raise TypeError('Parts were not passed!')
|
||||
|
||||
self.prefix = prefix
|
||||
self.sep = sep
|
||||
|
|
@ -59,24 +59,24 @@ class CallbackData:
|
|||
if args:
|
||||
value = args.pop(0)
|
||||
else:
|
||||
raise ValueError(f"Value for '{part}' is not passed!")
|
||||
raise ValueError(f'Value for {part!r} was not passed!')
|
||||
|
||||
if value is not None and not isinstance(value, str):
|
||||
value = str(value)
|
||||
|
||||
if not value:
|
||||
raise ValueError(f"Value for part {part} can't be empty!'")
|
||||
elif self.sep in value:
|
||||
raise ValueError(f"Symbol defined as separator can't be used in values of parts")
|
||||
raise ValueError(f"Value for part {part!r} can't be empty!'")
|
||||
if self.sep in value:
|
||||
raise ValueError(f"Symbol {self.sep!r} is defined as the separator and can't be used in parts' values")
|
||||
|
||||
data.append(value)
|
||||
|
||||
if args or kwargs:
|
||||
raise TypeError("Too many arguments is passed!")
|
||||
raise TypeError('Too many arguments were passed!')
|
||||
|
||||
callback_data = self.sep.join(data)
|
||||
if len(callback_data) > 64:
|
||||
raise ValueError("Resulted callback data is too long!")
|
||||
raise ValueError('Resulted callback data is too long!')
|
||||
|
||||
return callback_data
|
||||
|
||||
|
|
@ -91,9 +91,9 @@ class CallbackData:
|
|||
if prefix != self.prefix:
|
||||
raise ValueError("Passed callback data can't be parsed with that prefix.")
|
||||
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))
|
||||
return result
|
||||
|
||||
|
|
@ -106,11 +106,12 @@ class CallbackData:
|
|||
"""
|
||||
for key in config.keys():
|
||||
if key not in self._part_names:
|
||||
raise ValueError(f"Invalid field name '{key}'")
|
||||
raise ValueError(f'Invalid field name {key!r}')
|
||||
return CallbackDataFilter(self, config)
|
||||
|
||||
|
||||
class CallbackDataFilter(Filter):
|
||||
|
||||
def __init__(self, factory: CallbackData, config: typing.Dict[str, str]):
|
||||
self.config = config
|
||||
self.factory = factory
|
||||
|
|
@ -124,12 +125,12 @@ class CallbackDataFilter(Filter):
|
|||
data = self.factory.parse(query.data)
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
|
||||
for key, value in self.config.items():
|
||||
if isinstance(value, (list, tuple, set)):
|
||||
if isinstance(value, (list, tuple, set, frozenset)):
|
||||
if data.get(key) not in value:
|
||||
return False
|
||||
else:
|
||||
if value != data.get(key):
|
||||
if data.get(key) != value:
|
||||
return False
|
||||
return {"callback_data": data}
|
||||
return {'callback_data': data}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
"""
|
||||
Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically
|
||||
"""
|
||||
|
||||
import functools
|
||||
import asyncio
|
||||
import inspect
|
||||
import warnings
|
||||
import functools
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def deprecated(reason):
|
||||
def deprecated(reason, stacklevel=2) -> Callable:
|
||||
"""
|
||||
This is a decorator which can be used to mark functions
|
||||
as deprecated. It will result in a warning being emitted
|
||||
when the function is used.
|
||||
|
||||
Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically
|
||||
"""
|
||||
|
||||
if isinstance(reason, str):
|
||||
|
|
@ -33,15 +33,15 @@ def deprecated(reason):
|
|||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
warn_deprecated(msg.format(name=func.__name__, reason=reason))
|
||||
warnings.simplefilter("default", DeprecationWarning)
|
||||
warn_deprecated(msg.format(name=func.__name__, reason=reason), stacklevel=stacklevel)
|
||||
warnings.simplefilter('default', DeprecationWarning)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
elif inspect.isclass(reason) or inspect.isfunction(reason):
|
||||
if inspect.isclass(reason) or inspect.isfunction(reason):
|
||||
|
||||
# The @deprecated is used without any 'reason'.
|
||||
#
|
||||
|
|
@ -60,16 +60,76 @@ def deprecated(reason):
|
|||
|
||||
@functools.wraps(func1)
|
||||
def wrapper1(*args, **kwargs):
|
||||
warn_deprecated(msg1.format(name=func1.__name__))
|
||||
warn_deprecated(msg1.format(name=func1.__name__), stacklevel=stacklevel)
|
||||
return func1(*args, **kwargs)
|
||||
|
||||
return wrapper1
|
||||
|
||||
else:
|
||||
raise TypeError(repr(type(reason)))
|
||||
|
||||
|
||||
def warn_deprecated(message, warning=DeprecationWarning, stacklevel=2):
|
||||
warnings.simplefilter("always", warning)
|
||||
warnings.simplefilter('always', warning)
|
||||
warnings.warn(message, category=warning, stacklevel=stacklevel)
|
||||
warnings.simplefilter("default", warning)
|
||||
warnings.simplefilter('default', warning)
|
||||
|
||||
|
||||
def renamed_argument(old_name: str, new_name: str, until_version: str, stacklevel: int = 3):
|
||||
"""
|
||||
A meta-decorator to mark an argument as deprecated.
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
@renamed_argument("chat", "chat_id", "3.0") # stacklevel=3 by default
|
||||
@renamed_argument("user", "user_id", "3.0", stacklevel=4)
|
||||
def some_function(user_id, chat_id=None):
|
||||
print(f"user_id={user_id}, chat_id={chat_id}")
|
||||
|
||||
some_function(user=123) # prints 'user_id=123, chat_id=None' with warning
|
||||
some_function(123) # prints 'user_id=123, chat_id=None' without warning
|
||||
some_function(user_id=123) # prints 'user_id=123, chat_id=None' without warning
|
||||
|
||||
|
||||
:param old_name:
|
||||
:param new_name:
|
||||
:param until_version: the version in which the argument is scheduled to be removed
|
||||
:param stacklevel: leave it to default if it's the first decorator used.
|
||||
Increment with any new decorator used.
|
||||
:return: decorator
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapped(*args, **kwargs):
|
||||
if old_name in kwargs:
|
||||
warn_deprecated(f"In coroutine '{func.__name__}' argument '{old_name}' "
|
||||
f"is renamed to '{new_name}' "
|
||||
f"and will be removed in aiogram {until_version}",
|
||||
stacklevel=stacklevel)
|
||||
kwargs.update(
|
||||
{
|
||||
new_name: kwargs[old_name],
|
||||
}
|
||||
)
|
||||
kwargs.pop(old_name)
|
||||
return await func(*args, **kwargs)
|
||||
else:
|
||||
@functools.wraps(func)
|
||||
def wrapped(*args, **kwargs):
|
||||
if old_name in kwargs:
|
||||
warn_deprecated(f"In function `{func.__name__}` argument `{old_name}` "
|
||||
f"is renamed to `{new_name}` "
|
||||
f"and will be removed in aiogram {until_version}",
|
||||
stacklevel=stacklevel)
|
||||
kwargs.update(
|
||||
{
|
||||
new_name: kwargs[old_name],
|
||||
}
|
||||
)
|
||||
kwargs.pop(old_name)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
return decorator
|
||||
|
|
|
|||
|
|
@ -1,102 +1,100 @@
|
|||
"""
|
||||
TelegramAPIError
|
||||
ValidationError
|
||||
Throttled
|
||||
BadRequest
|
||||
MessageError
|
||||
MessageNotModified
|
||||
MessageToForwardNotFound
|
||||
MessageToDeleteNotFound
|
||||
MessageIdentifierNotSpecified
|
||||
MessageTextIsEmpty
|
||||
MessageCantBeEdited
|
||||
MessageCantBeDeleted
|
||||
MessageToEditNotFound
|
||||
MessageToReplyNotFound
|
||||
ToMuchMessages
|
||||
PollError
|
||||
PollCantBeStopped
|
||||
PollHasAlreadyClosed
|
||||
PollsCantBeSentToPrivateChats
|
||||
PollSizeError
|
||||
PollMustHaveMoreOptions
|
||||
PollCantHaveMoreOptions
|
||||
PollsOptionsLengthTooLong
|
||||
PollOptionsMustBeNonEmpty
|
||||
PollQuestionMustBeNonEmpty
|
||||
MessageWithPollNotFound (with MessageError)
|
||||
MessageIsNotAPoll (with MessageError)
|
||||
ObjectExpectedAsReplyMarkup
|
||||
InlineKeyboardExpected
|
||||
ChatNotFound
|
||||
ChatDescriptionIsNotModified
|
||||
InvalidQueryID
|
||||
InvalidPeerID
|
||||
InvalidHTTPUrlContent
|
||||
ButtonURLInvalid
|
||||
URLHostIsEmpty
|
||||
StartParamInvalid
|
||||
ButtonDataInvalid
|
||||
WrongFileIdentifier
|
||||
GroupDeactivated
|
||||
BadWebhook
|
||||
WebhookRequireHTTPS
|
||||
BadWebhookPort
|
||||
BadWebhookAddrInfo
|
||||
BadWebhookNoAddressAssociatedWithHostname
|
||||
NotFound
|
||||
MethodNotKnown
|
||||
PhotoAsInputFileRequired
|
||||
InvalidStickersSet
|
||||
NoStickerInRequest
|
||||
ChatAdminRequired
|
||||
NeedAdministratorRightsInTheChannel
|
||||
MethodNotAvailableInPrivateChats
|
||||
CantDemoteChatCreator
|
||||
CantRestrictSelf
|
||||
NotEnoughRightsToRestrict
|
||||
PhotoDimensions
|
||||
UnavailableMembers
|
||||
TypeOfFileMismatch
|
||||
WrongRemoteFileIdSpecified
|
||||
PaymentProviderInvalid
|
||||
CurrencyTotalAmountInvalid
|
||||
CantParseUrl
|
||||
UnsupportedUrlProtocol
|
||||
CantParseEntities
|
||||
ResultIdDuplicate
|
||||
ConflictError
|
||||
TerminatedByOtherGetUpdates
|
||||
CantGetUpdates
|
||||
Unauthorized
|
||||
BotKicked
|
||||
BotBlocked
|
||||
UserDeactivated
|
||||
CantInitiateConversation
|
||||
CantTalkWithBots
|
||||
NetworkError
|
||||
RetryAfter
|
||||
MigrateToChat
|
||||
RestartingTelegram
|
||||
- TelegramAPIError
|
||||
- ValidationError
|
||||
- Throttled
|
||||
- BadRequest
|
||||
- MessageError
|
||||
- MessageNotModified
|
||||
- MessageToForwardNotFound
|
||||
- MessageToDeleteNotFound
|
||||
- MessageIdentifierNotSpecified
|
||||
- MessageTextIsEmpty
|
||||
- MessageCantBeEdited
|
||||
- MessageCantBeDeleted
|
||||
- MessageToEditNotFound
|
||||
- MessageToReplyNotFound
|
||||
- ToMuchMessages
|
||||
- PollError
|
||||
- PollCantBeStopped
|
||||
- PollHasAlreadyClosed
|
||||
- PollsCantBeSentToPrivateChats
|
||||
- PollSizeError
|
||||
- PollMustHaveMoreOptions
|
||||
- PollCantHaveMoreOptions
|
||||
- PollsOptionsLengthTooLong
|
||||
- PollOptionsMustBeNonEmpty
|
||||
- PollQuestionMustBeNonEmpty
|
||||
- MessageWithPollNotFound (with MessageError)
|
||||
- MessageIsNotAPoll (with MessageError)
|
||||
- ObjectExpectedAsReplyMarkup
|
||||
- InlineKeyboardExpected
|
||||
- ChatNotFound
|
||||
- ChatDescriptionIsNotModified
|
||||
- InvalidQueryID
|
||||
- InvalidPeerID
|
||||
- InvalidHTTPUrlContent
|
||||
- ButtonURLInvalid
|
||||
- URLHostIsEmpty
|
||||
- StartParamInvalid
|
||||
- ButtonDataInvalid
|
||||
- WrongFileIdentifier
|
||||
- GroupDeactivated
|
||||
- BadWebhook
|
||||
- WebhookRequireHTTPS
|
||||
- BadWebhookPort
|
||||
- BadWebhookAddrInfo
|
||||
- BadWebhookNoAddressAssociatedWithHostname
|
||||
- NotFound
|
||||
- MethodNotKnown
|
||||
- PhotoAsInputFileRequired
|
||||
- InvalidStickersSet
|
||||
- NoStickerInRequest
|
||||
- ChatAdminRequired
|
||||
- NeedAdministratorRightsInTheChannel
|
||||
- MethodNotAvailableInPrivateChats
|
||||
- CantDemoteChatCreator
|
||||
- CantRestrictSelf
|
||||
- NotEnoughRightsToRestrict
|
||||
- PhotoDimensions
|
||||
- UnavailableMembers
|
||||
- TypeOfFileMismatch
|
||||
- WrongRemoteFileIdSpecified
|
||||
- PaymentProviderInvalid
|
||||
- CurrencyTotalAmountInvalid
|
||||
- CantParseUrl
|
||||
- UnsupportedUrlProtocol
|
||||
- CantParseEntities
|
||||
- ResultIdDuplicate
|
||||
- ConflictError
|
||||
- TerminatedByOtherGetUpdates
|
||||
- CantGetUpdates
|
||||
- Unauthorized
|
||||
- BotKicked
|
||||
- BotBlocked
|
||||
- UserDeactivated
|
||||
- CantInitiateConversation
|
||||
- CantTalkWithBots
|
||||
- NetworkError
|
||||
- RetryAfter
|
||||
- MigrateToChat
|
||||
- RestartingTelegram
|
||||
|
||||
|
||||
TODO: aiogram.utils.exceptions.BadRequest: Bad request: can't parse entities: unsupported start tag "function" at byte offset 0
|
||||
TODO: aiogram.utils.exceptions.TelegramAPIError: Gateway Timeout
|
||||
|
||||
AIOGramWarning
|
||||
TimeoutWarning
|
||||
- AIOGramWarning
|
||||
- TimeoutWarning
|
||||
"""
|
||||
import time
|
||||
|
||||
# TODO: Use exceptions detector from `aiograph`.
|
||||
# TODO: aiogram.utils.exceptions.BadRequest: Bad request: can't parse entities: unsupported start tag "function" at byte offset 0
|
||||
# TODO: aiogram.utils.exceptions.TelegramAPIError: Gateway Timeout
|
||||
|
||||
_PREFIXES = ["error: ", "[error]: ", "bad request: ", "conflict: ", "not found: "]
|
||||
_PREFIXES = ['error: ', '[error]: ', 'bad request: ', 'conflict: ', 'not found: ']
|
||||
|
||||
|
||||
def _clean_message(text):
|
||||
for prefix in _PREFIXES:
|
||||
if text.startswith(prefix):
|
||||
text = text[len(prefix) :]
|
||||
text = text[len(prefix):]
|
||||
return (text[0].upper() + text[1:]).strip()
|
||||
|
||||
|
||||
|
|
@ -106,7 +104,7 @@ class TelegramAPIError(Exception):
|
|||
|
||||
|
||||
class _MatchErrorMixin:
|
||||
match = ""
|
||||
match = ''
|
||||
text = None
|
||||
|
||||
__subclasses = []
|
||||
|
|
@ -166,72 +164,67 @@ class MessageNotModified(MessageError):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
match = "message identifier is not specified"
|
||||
match = 'message identifier is not specified'
|
||||
|
||||
|
||||
class MessageTextIsEmpty(MessageError):
|
||||
match = "Message text is empty"
|
||||
match = 'Message text is empty'
|
||||
|
||||
|
||||
class MessageCantBeEdited(MessageError):
|
||||
match = "message can't be edited"
|
||||
match = 'message can\'t be edited'
|
||||
|
||||
|
||||
class MessageCantBeDeleted(MessageError):
|
||||
match = "message can't be deleted"
|
||||
match = 'message can\'t be deleted'
|
||||
|
||||
|
||||
class MessageToEditNotFound(MessageError):
|
||||
match = "message to edit not found"
|
||||
match = 'message to edit not found'
|
||||
|
||||
|
||||
class MessageIsTooLong(MessageError):
|
||||
match = "message is too long"
|
||||
match = 'message is too long'
|
||||
|
||||
|
||||
class ToMuchMessages(MessageError):
|
||||
"""
|
||||
Will be raised when you try to send media group with more than 10 items.
|
||||
"""
|
||||
|
||||
match = "Too much messages to send as an album"
|
||||
match = 'Too much messages to send as an album'
|
||||
|
||||
|
||||
class ObjectExpectedAsReplyMarkup(BadRequest):
|
||||
match = "object expected as reply markup"
|
||||
match = 'object expected as reply markup'
|
||||
|
||||
|
||||
class InlineKeyboardExpected(BadRequest):
|
||||
match = "inline keyboard expected"
|
||||
match = 'inline keyboard expected'
|
||||
|
||||
|
||||
class PollError(BadRequest):
|
||||
|
|
@ -243,7 +236,7 @@ class PollCantBeStopped(PollError):
|
|||
|
||||
|
||||
class PollHasAlreadyBeenClosed(PollError):
|
||||
match = "poll has already been closed"
|
||||
match = 'poll has already been closed'
|
||||
|
||||
|
||||
class PollsCantBeSentToPrivateChats(PollError):
|
||||
|
|
@ -282,112 +275,109 @@ class MessageWithPollNotFound(PollError, MessageError):
|
|||
"""
|
||||
Will be raised when you try to stop poll with message without poll
|
||||
"""
|
||||
|
||||
match = "message with poll to stop not found"
|
||||
match = 'message with poll to stop not found'
|
||||
|
||||
|
||||
class MessageIsNotAPoll(PollError, MessageError):
|
||||
"""
|
||||
Will be raised when you try to stop poll with message without poll
|
||||
"""
|
||||
|
||||
match = "message is not a poll"
|
||||
match = 'message is not a poll'
|
||||
|
||||
|
||||
class ChatNotFound(BadRequest):
|
||||
match = "chat not found"
|
||||
match = 'chat not found'
|
||||
|
||||
|
||||
class ChatIdIsEmpty(BadRequest):
|
||||
match = "chat_id is empty"
|
||||
match = 'chat_id is empty'
|
||||
|
||||
|
||||
class InvalidUserId(BadRequest):
|
||||
match = "user_id_invalid"
|
||||
text = "Invalid user id"
|
||||
match = 'user_id_invalid'
|
||||
text = 'Invalid user id'
|
||||
|
||||
|
||||
class ChatDescriptionIsNotModified(BadRequest):
|
||||
match = "chat description is not modified"
|
||||
match = 'chat description is not modified'
|
||||
|
||||
|
||||
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):
|
||||
match = "PEER_ID_INVALID"
|
||||
text = "Invalid peer ID"
|
||||
match = 'PEER_ID_INVALID'
|
||||
text = 'Invalid peer ID'
|
||||
|
||||
|
||||
class InvalidHTTPUrlContent(BadRequest):
|
||||
match = "Failed to get HTTP URL content"
|
||||
match = 'Failed to get HTTP URL content'
|
||||
|
||||
|
||||
class ButtonURLInvalid(BadRequest):
|
||||
match = "BUTTON_URL_INVALID"
|
||||
text = "Button URL invalid"
|
||||
match = 'BUTTON_URL_INVALID'
|
||||
text = 'Button URL invalid'
|
||||
|
||||
|
||||
class URLHostIsEmpty(BadRequest):
|
||||
match = "URL host is empty"
|
||||
match = 'URL host is empty'
|
||||
|
||||
|
||||
class StartParamInvalid(BadRequest):
|
||||
match = "START_PARAM_INVALID"
|
||||
text = "Start param invalid"
|
||||
match = 'START_PARAM_INVALID'
|
||||
text = 'Start param invalid'
|
||||
|
||||
|
||||
class ButtonDataInvalid(BadRequest):
|
||||
match = "BUTTON_DATA_INVALID"
|
||||
text = "Button data invalid"
|
||||
match = 'BUTTON_DATA_INVALID'
|
||||
text = 'Button data invalid'
|
||||
|
||||
|
||||
class WrongFileIdentifier(BadRequest):
|
||||
match = "wrong file identifier/HTTP URL specified"
|
||||
match = 'wrong file identifier/HTTP URL specified'
|
||||
|
||||
|
||||
class GroupDeactivated(BadRequest):
|
||||
match = "group is deactivated"
|
||||
match = 'group is deactivated'
|
||||
|
||||
|
||||
class PhotoAsInputFileRequired(BadRequest):
|
||||
"""
|
||||
Will be raised when you try to set chat photo from file ID.
|
||||
"""
|
||||
|
||||
match = "Photo should be uploaded as an InputFile"
|
||||
match = 'Photo should be uploaded as an InputFile'
|
||||
|
||||
|
||||
class InvalidStickersSet(BadRequest):
|
||||
match = "STICKERSET_INVALID"
|
||||
text = "Stickers set is invalid"
|
||||
match = 'STICKERSET_INVALID'
|
||||
text = 'Stickers set is invalid'
|
||||
|
||||
|
||||
class NoStickerInRequest(BadRequest):
|
||||
match = "there is no sticker in the request"
|
||||
match = 'there is no sticker in the request'
|
||||
|
||||
|
||||
class ChatAdminRequired(BadRequest):
|
||||
match = "CHAT_ADMIN_REQUIRED"
|
||||
text = "Admin permissions is required!"
|
||||
match = 'CHAT_ADMIN_REQUIRED'
|
||||
text = 'Admin permissions is required!'
|
||||
|
||||
|
||||
class NeedAdministratorRightsInTheChannel(BadRequest):
|
||||
match = "need administrator rights in the channel chat"
|
||||
text = "Admin permissions is required!"
|
||||
match = 'need administrator rights in the channel chat'
|
||||
text = 'Admin permissions is required!'
|
||||
|
||||
|
||||
class NotEnoughRightsToPinMessage(BadRequest):
|
||||
match = "not enough rights to pin a message"
|
||||
match = 'not enough rights to pin a message'
|
||||
|
||||
|
||||
class MethodNotAvailableInPrivateChats(BadRequest):
|
||||
match = "method is available only for supergroups and channel"
|
||||
match = 'method is available only for supergroups and channel'
|
||||
|
||||
|
||||
class CantDemoteChatCreator(BadRequest):
|
||||
match = "can't demote chat creator"
|
||||
match = 'can\'t demote chat creator'
|
||||
|
||||
|
||||
class CantRestrictSelf(BadRequest):
|
||||
|
|
@ -396,34 +386,34 @@ class CantRestrictSelf(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):
|
||||
match = "PHOTO_INVALID_DIMENSIONS"
|
||||
text = "Invalid photo dimensions"
|
||||
match = 'PHOTO_INVALID_DIMENSIONS'
|
||||
text = 'Invalid photo dimensions'
|
||||
|
||||
|
||||
class UnavailableMembers(BadRequest):
|
||||
match = "supergroup members are unavailable"
|
||||
match = 'supergroup members are unavailable'
|
||||
|
||||
|
||||
class TypeOfFileMismatch(BadRequest):
|
||||
match = "type of file mismatch"
|
||||
match = 'type of file mismatch'
|
||||
|
||||
|
||||
class WrongRemoteFileIdSpecified(BadRequest):
|
||||
match = "wrong remote file id specified"
|
||||
match = 'wrong remote file id specified'
|
||||
|
||||
|
||||
class PaymentProviderInvalid(BadRequest):
|
||||
match = "PAYMENT_PROVIDER_INVALID"
|
||||
text = "payment provider invalid"
|
||||
match = 'PAYMENT_PROVIDER_INVALID'
|
||||
text = 'payment provider invalid'
|
||||
|
||||
|
||||
class CurrencyTotalAmountInvalid(BadRequest):
|
||||
match = "currency_total_amount_invalid"
|
||||
text = "currency total amount invalid"
|
||||
match = 'currency_total_amount_invalid'
|
||||
text = 'currency total amount invalid'
|
||||
|
||||
|
||||
class BadWebhook(BadRequest):
|
||||
|
|
@ -431,44 +421,44 @@ class BadWebhook(BadRequest):
|
|||
|
||||
|
||||
class WebhookRequireHTTPS(BadWebhook):
|
||||
match = "HTTPS url must be provided for webhook"
|
||||
text = "bad webhook: " + match
|
||||
match = 'HTTPS url must be provided for webhook'
|
||||
text = 'bad webhook: ' + match
|
||||
|
||||
|
||||
class BadWebhookPort(BadWebhook):
|
||||
match = "Webhook can be set up only on ports 80, 88, 443 or 8443"
|
||||
text = "bad webhook: " + match
|
||||
match = 'Webhook can be set up only on ports 80, 88, 443 or 8443'
|
||||
text = 'bad webhook: ' + match
|
||||
|
||||
|
||||
class BadWebhookAddrInfo(BadWebhook):
|
||||
match = "getaddrinfo: Temporary failure in name resolution"
|
||||
text = "bad webhook: " + match
|
||||
match = 'getaddrinfo: Temporary failure in name resolution'
|
||||
text = 'bad webhook: ' + match
|
||||
|
||||
|
||||
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):
|
||||
match = "can't parse URL"
|
||||
match = 'can\'t parse URL'
|
||||
|
||||
|
||||
class UnsupportedUrlProtocol(BadRequest):
|
||||
match = "unsupported URL protocol"
|
||||
match = 'unsupported URL protocol'
|
||||
|
||||
|
||||
class CantParseEntities(BadRequest):
|
||||
match = "can't parse entities"
|
||||
match = 'can\'t parse entities'
|
||||
|
||||
|
||||
class ResultIdDuplicate(BadRequest):
|
||||
match = "result_id_duplicate"
|
||||
text = "Result ID duplicate"
|
||||
match = 'result_id_duplicate'
|
||||
text = 'Result ID duplicate'
|
||||
|
||||
|
||||
class BotDomainInvalid(BadRequest):
|
||||
match = "bot_domain_invalid"
|
||||
text = "Invalid bot domain"
|
||||
match = 'bot_domain_invalid'
|
||||
text = 'Invalid bot domain'
|
||||
|
||||
|
||||
class NotFound(TelegramAPIError, _MatchErrorMixin):
|
||||
|
|
@ -476,7 +466,7 @@ class NotFound(TelegramAPIError, _MatchErrorMixin):
|
|||
|
||||
|
||||
class MethodNotKnown(NotFound):
|
||||
match = "method not found"
|
||||
match = 'method not found'
|
||||
|
||||
|
||||
class ConflictError(TelegramAPIError, _MatchErrorMixin):
|
||||
|
|
@ -484,15 +474,13 @@ class ConflictError(TelegramAPIError, _MatchErrorMixin):
|
|||
|
||||
|
||||
class TerminatedByOtherGetUpdates(ConflictError):
|
||||
match = "terminated by other getUpdates request"
|
||||
text = (
|
||||
"Terminated by other getUpdates request; "
|
||||
"Make sure that only one bot instance is running"
|
||||
)
|
||||
match = 'terminated by other getUpdates request'
|
||||
text = 'Terminated by other getUpdates request; ' \
|
||||
'Make sure that only one bot instance is running'
|
||||
|
||||
|
||||
class CantGetUpdates(ConflictError):
|
||||
match = "can't use getUpdates method while webhook is active"
|
||||
match = 'can\'t use getUpdates method while webhook is active'
|
||||
|
||||
|
||||
class Unauthorized(TelegramAPIError, _MatchErrorMixin):
|
||||
|
|
@ -500,23 +488,23 @@ class Unauthorized(TelegramAPIError, _MatchErrorMixin):
|
|||
|
||||
|
||||
class BotKicked(Unauthorized):
|
||||
match = "Bot was kicked from a chat"
|
||||
match = 'bot was kicked from a chat'
|
||||
|
||||
|
||||
class BotBlocked(Unauthorized):
|
||||
match = "bot was blocked by the user"
|
||||
match = 'bot was blocked by the user'
|
||||
|
||||
|
||||
class UserDeactivated(Unauthorized):
|
||||
match = "user is deactivated"
|
||||
match = 'user is deactivated'
|
||||
|
||||
|
||||
class CantInitiateConversation(Unauthorized):
|
||||
match = "bot can't initiate conversation with a user"
|
||||
match = 'bot can\'t initiate conversation with a user'
|
||||
|
||||
|
||||
class CantTalkWithBots(Unauthorized):
|
||||
match = "bot can't send messages to bots"
|
||||
match = 'bot can\'t send messages to bots'
|
||||
|
||||
|
||||
class NetworkError(TelegramAPIError):
|
||||
|
|
@ -525,43 +513,34 @@ class NetworkError(TelegramAPIError):
|
|||
|
||||
class RestartingTelegram(TelegramAPIError):
|
||||
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):
|
||||
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
|
||||
|
||||
|
||||
class MigrateToChat(TelegramAPIError):
|
||||
def __init__(self, chat_id):
|
||||
super(MigrateToChat, self).__init__(
|
||||
f"The group has been migrated to a supergroup. New id: {chat_id}."
|
||||
)
|
||||
super(MigrateToChat, self).__init__(f"The group has been migrated to a supergroup. New id: {chat_id}.")
|
||||
self.migrate_to_chat_id = chat_id
|
||||
|
||||
|
||||
class Throttled(TelegramAPIError):
|
||||
def __init__(self, **kwargs):
|
||||
from ..dispatcher.storage import DELTA, EXCEEDED_COUNT, KEY, LAST_CALL, RATE_LIMIT, RESULT
|
||||
|
||||
self.key = kwargs.pop(KEY, "<None>")
|
||||
self.key = kwargs.pop(KEY, '<None>')
|
||||
self.called_at = kwargs.pop(LAST_CALL, time.time())
|
||||
self.rate = kwargs.pop(RATE_LIMIT, None)
|
||||
self.result = kwargs.pop(RESULT, False)
|
||||
self.exceeded_count = kwargs.pop(EXCEEDED_COUNT, 0)
|
||||
self.delta = kwargs.pop(DELTA, 0)
|
||||
self.user = kwargs.pop("user", None)
|
||||
self.chat = kwargs.pop("chat", None)
|
||||
self.user = kwargs.pop('user', None)
|
||||
self.chat = kwargs.pop('chat', None)
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"Rate limit exceeded! (Limit: {self.rate} s, "
|
||||
f"exceeded: {self.exceeded_count}, "
|
||||
return f"Rate limit exceeded! (Limit: {self.rate} s, " \
|
||||
f"exceeded: {self.exceeded_count}, " \
|
||||
f"time delta: {round(self.delta, 3)} s)"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,27 +12,18 @@ from ..bot.api import log
|
|||
from ..dispatcher.dispatcher import Dispatcher
|
||||
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: 'Executor', on_startup=None, on_shutdown=None):
|
||||
if on_startup is not None:
|
||||
executor.on_startup(on_startup)
|
||||
if on_shutdown is not None:
|
||||
executor.on_shutdown(on_shutdown)
|
||||
|
||||
|
||||
def start_polling(
|
||||
dispatcher,
|
||||
*,
|
||||
loop=None,
|
||||
skip_updates=False,
|
||||
reset_webhook=True,
|
||||
on_startup=None,
|
||||
on_shutdown=None,
|
||||
timeout=20,
|
||||
fast=True,
|
||||
):
|
||||
def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=True,
|
||||
on_startup=None, on_shutdown=None, timeout=20, relax=0.1, fast=True):
|
||||
"""
|
||||
Start bot in long-polling mode
|
||||
|
||||
|
|
@ -47,22 +38,14 @@ def start_polling(
|
|||
executor = Executor(dispatcher, skip_updates=skip_updates, loop=loop)
|
||||
_setup_callbacks(executor, on_startup, on_shutdown)
|
||||
|
||||
executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, fast=fast)
|
||||
executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, relax=relax, fast=fast)
|
||||
|
||||
|
||||
def set_webhook(
|
||||
dispatcher: Dispatcher,
|
||||
webhook_path: str,
|
||||
*,
|
||||
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,
|
||||
):
|
||||
def set_webhook(dispatcher: Dispatcher, webhook_path: str, *, 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
|
||||
|
||||
|
|
@ -78,32 +61,17 @@ def set_webhook(
|
|||
:param web_app: Optional[Application] (default: None)
|
||||
:return:
|
||||
"""
|
||||
executor = Executor(
|
||||
dispatcher,
|
||||
skip_updates=skip_updates,
|
||||
check_ip=check_ip,
|
||||
retry_after=retry_after,
|
||||
loop=loop,
|
||||
)
|
||||
executor = Executor(dispatcher, skip_updates=skip_updates, check_ip=check_ip, retry_after=retry_after,
|
||||
loop=loop)
|
||||
_setup_callbacks(executor, on_startup, on_shutdown)
|
||||
|
||||
executor.set_webhook(webhook_path, route_name=route_name, web_app=web_app)
|
||||
return executor
|
||||
|
||||
|
||||
def start_webhook(
|
||||
dispatcher,
|
||||
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,
|
||||
):
|
||||
def start_webhook(dispatcher, 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
|
||||
|
||||
|
|
@ -118,8 +86,7 @@ def start_webhook(
|
|||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
executor = set_webhook(
|
||||
dispatcher=dispatcher,
|
||||
executor = set_webhook(dispatcher=dispatcher,
|
||||
webhook_path=webhook_path,
|
||||
loop=loop,
|
||||
skip_updates=skip_updates,
|
||||
|
|
@ -127,12 +94,12 @@ def start_webhook(
|
|||
on_shutdown=on_shutdown,
|
||||
check_ip=check_ip,
|
||||
retry_after=retry_after,
|
||||
route_name=route_name,
|
||||
)
|
||||
route_name=route_name)
|
||||
executor.run_app(**kwargs)
|
||||
|
||||
|
||||
def start(dispatcher, future, *, loop=None, skip_updates=None, on_startup=None, on_shutdown=None):
|
||||
def start(dispatcher, future, *, loop=None, skip_updates=None,
|
||||
on_startup=None, on_shutdown=None):
|
||||
"""
|
||||
Execute Future.
|
||||
|
||||
|
|
@ -175,7 +142,6 @@ class Executor:
|
|||
self._freeze = False
|
||||
|
||||
from aiogram import Bot, Dispatcher
|
||||
|
||||
Bot.set_current(dispatcher.bot)
|
||||
Dispatcher.set_current(dispatcher)
|
||||
|
||||
|
|
@ -194,7 +160,7 @@ class Executor:
|
|||
@property
|
||||
def web_app(self) -> web.Application:
|
||||
if self._web_app is None:
|
||||
raise RuntimeError("web.Application() is not configured!")
|
||||
raise RuntimeError('web.Application() is not configured!')
|
||||
return self._web_app
|
||||
|
||||
def on_startup(self, callback: callable, polling=True, webhook=True):
|
||||
|
|
@ -207,7 +173,7 @@ class Executor:
|
|||
"""
|
||||
self._check_frozen()
|
||||
if not webhook and not polling:
|
||||
warn("This action has no effect!", UserWarning)
|
||||
warn('This action has no effect!', UserWarning)
|
||||
return
|
||||
|
||||
if isinstance(callback, (list, tuple, set)):
|
||||
|
|
@ -230,7 +196,7 @@ class Executor:
|
|||
"""
|
||||
self._check_frozen()
|
||||
if not webhook and not polling:
|
||||
warn("This action has no effect!", UserWarning)
|
||||
warn('This action has no effect!', UserWarning)
|
||||
return
|
||||
|
||||
if isinstance(callback, (list, tuple, set)):
|
||||
|
|
@ -245,7 +211,7 @@ class Executor:
|
|||
|
||||
def _check_frozen(self):
|
||||
if self.frozen:
|
||||
raise RuntimeError("Executor is frozen!")
|
||||
raise RuntimeError('Executor is frozen!')
|
||||
|
||||
def _prepare_polling(self):
|
||||
self._check_frozen()
|
||||
|
|
@ -253,9 +219,7 @@ class Executor:
|
|||
|
||||
# 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._freeze = True
|
||||
|
||||
|
|
@ -269,14 +233,14 @@ class Executor:
|
|||
raise RuntimeError("web.Application() is already configured!")
|
||||
|
||||
if self.retry_after:
|
||||
app["RETRY_AFTER"] = self.retry_after
|
||||
app['RETRY_AFTER'] = self.retry_after
|
||||
|
||||
if self._identity == app.get(self._identity):
|
||||
# App is already configured
|
||||
return
|
||||
|
||||
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, _):
|
||||
return await cb(self.dispatcher)
|
||||
|
|
@ -294,15 +258,10 @@ class Executor:
|
|||
app[APP_EXECUTOR_KEY] = self
|
||||
app[BOT_DISPATCHER_KEY] = self.dispatcher
|
||||
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,
|
||||
route_name: str = DEFAULT_ROUTE_NAME,
|
||||
web_app: Optional[Application] = None,
|
||||
):
|
||||
def set_webhook(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
|
||||
|
||||
|
|
@ -318,13 +277,8 @@ class Executor:
|
|||
def run_app(self, **kwargs):
|
||||
web.run_app(self._web_app, **kwargs)
|
||||
|
||||
def start_webhook(
|
||||
self,
|
||||
webhook_path=None,
|
||||
request_handler=WebhookRequestHandler,
|
||||
route_name=DEFAULT_ROUTE_NAME,
|
||||
**kwargs,
|
||||
):
|
||||
def start_webhook(self, webhook_path=None, request_handler=WebhookRequestHandler, route_name=DEFAULT_ROUTE_NAME,
|
||||
**kwargs):
|
||||
"""
|
||||
Start bot in webhook mode
|
||||
|
||||
|
|
@ -334,12 +288,10 @@ class Executor:
|
|||
:param kwargs:
|
||||
: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)
|
||||
|
||||
def start_polling(self, reset_webhook=None, timeout=20, fast=True):
|
||||
def start_polling(self, reset_webhook=None, timeout=20, relax=0.1, fast=True):
|
||||
"""
|
||||
Start bot in long-polling mode
|
||||
|
||||
|
|
@ -351,11 +303,8 @@ class Executor:
|
|||
|
||||
try:
|
||||
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,
|
||||
relax=relax, fast=fast))
|
||||
loop.run_forever()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
# loop.stop()
|
||||
|
|
@ -391,7 +340,7 @@ class Executor:
|
|||
async def _skip_updates(self):
|
||||
await self.dispatcher.reset_webhook(True)
|
||||
await self.dispatcher.skip_updates()
|
||||
log.warning(f"Updates are skipped successfully.")
|
||||
log.warning(f'Updates were skipped successfully.')
|
||||
|
||||
async def _welcome(self):
|
||||
user = await self.dispatcher.bot.me
|
||||
|
|
@ -412,11 +361,11 @@ class Executor:
|
|||
await callback(self.dispatcher)
|
||||
|
||||
async def _shutdown_polling(self, wait_closed=False):
|
||||
await self._shutdown()
|
||||
|
||||
for callback in self._on_shutdown_polling:
|
||||
await callback(self.dispatcher)
|
||||
|
||||
await self._shutdown()
|
||||
|
||||
if wait_closed:
|
||||
await self.dispatcher.wait_closed()
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,13 @@ Example:
|
|||
>>> print(MyHelper.all())
|
||||
<<< ['barItem', 'bazItem', 'fooItem', 'lorem']
|
||||
"""
|
||||
from typing import List
|
||||
|
||||
PROPS_KEYS_ATTR_NAME = '_props_keys'
|
||||
|
||||
|
||||
class Helper:
|
||||
mode = ""
|
||||
mode = ''
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
|
|
@ -37,13 +40,13 @@ class Helper:
|
|||
|
||||
|
||||
class HelperMode(Helper):
|
||||
mode = "original"
|
||||
mode = 'original'
|
||||
|
||||
SCREAMING_SNAKE_CASE = "SCREAMING_SNAKE_CASE"
|
||||
lowerCamelCase = "lowerCamelCase"
|
||||
CamelCase = "CamelCase"
|
||||
snake_case = "snake_case"
|
||||
lowercase = "lowercase"
|
||||
SCREAMING_SNAKE_CASE = 'SCREAMING_SNAKE_CASE'
|
||||
lowerCamelCase = 'lowerCamelCase'
|
||||
CamelCase = 'CamelCase'
|
||||
snake_case = 'snake_case'
|
||||
lowercase = 'lowercase'
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
|
|
@ -65,10 +68,10 @@ class HelperMode(Helper):
|
|||
"""
|
||||
if text.isupper():
|
||||
return text
|
||||
result = ""
|
||||
result = ''
|
||||
for pos, symbol in enumerate(text):
|
||||
if symbol.isupper() and pos > 0:
|
||||
result += "_" + symbol
|
||||
result += '_' + symbol
|
||||
else:
|
||||
result += symbol.upper()
|
||||
return result
|
||||
|
|
@ -94,10 +97,10 @@ class HelperMode(Helper):
|
|||
:param first_upper: first symbol must be upper?
|
||||
:return:
|
||||
"""
|
||||
result = ""
|
||||
result = ''
|
||||
need_upper = False
|
||||
for pos, symbol in enumerate(text):
|
||||
if symbol == "_" and pos > 0:
|
||||
if symbol == '_' and pos > 0:
|
||||
need_upper = True
|
||||
else:
|
||||
if need_upper:
|
||||
|
|
@ -120,15 +123,15 @@ class HelperMode(Helper):
|
|||
"""
|
||||
if mode == cls.SCREAMING_SNAKE_CASE:
|
||||
return cls._screaming_snake_case(text)
|
||||
elif mode == cls.snake_case:
|
||||
if mode == cls.snake_case:
|
||||
return cls._snake_case(text)
|
||||
elif mode == cls.lowercase:
|
||||
return cls._snake_case(text).replace("_", "")
|
||||
elif mode == cls.lowerCamelCase:
|
||||
if mode == cls.lowercase:
|
||||
return cls._snake_case(text).replace('_', '')
|
||||
if mode == cls.lowerCamelCase:
|
||||
return cls._camel_case(text)
|
||||
elif mode == cls.CamelCase:
|
||||
if mode == cls.CamelCase:
|
||||
return cls._camel_case(text, True)
|
||||
elif callable(mode):
|
||||
if callable(mode):
|
||||
return mode(text)
|
||||
return text
|
||||
|
||||
|
|
@ -149,10 +152,10 @@ class Item:
|
|||
|
||||
def __set_name__(self, owner, name):
|
||||
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 hasattr(owner, "mode"):
|
||||
self._value = HelperMode.apply(name, getattr(owner, "mode"))
|
||||
if hasattr(owner, 'mode'):
|
||||
self._value = HelperMode.apply(name, getattr(owner, 'mode'))
|
||||
|
||||
|
||||
class ListItem(Item):
|
||||
|
|
@ -191,3 +194,36 @@ class ItemsList(list):
|
|||
return self
|
||||
|
||||
__iadd__ = __add__ = __rand__ = __and__ = __ror__ = __or__ = add
|
||||
|
||||
|
||||
class OrderedHelperMeta(type):
|
||||
|
||||
def __new__(mcs, name, bases, namespace, **kwargs):
|
||||
cls = super().__new__(mcs, name, bases, namespace)
|
||||
|
||||
props_keys = []
|
||||
|
||||
for prop_name in (name for name, prop in namespace.items() if isinstance(prop, (Item, ListItem))):
|
||||
props_keys.append(prop_name)
|
||||
|
||||
setattr(cls, PROPS_KEYS_ATTR_NAME, props_keys)
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
class OrderedHelper(metaclass=OrderedHelperMeta):
|
||||
mode = ''
|
||||
|
||||
@classmethod
|
||||
def all(cls) -> List[str]:
|
||||
"""
|
||||
Get all Items values
|
||||
"""
|
||||
result = []
|
||||
for name in getattr(cls, PROPS_KEYS_ATTR_NAME, []):
|
||||
value = getattr(cls, name)
|
||||
if isinstance(value, ItemsList):
|
||||
result.append(value[0])
|
||||
else:
|
||||
result.append(value)
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import importlib
|
||||
import os
|
||||
|
||||
JSON = "json"
|
||||
RAPIDJSON = "rapidjson"
|
||||
UJSON = "ujson"
|
||||
JSON = 'json'
|
||||
RAPIDJSON = 'rapidjson'
|
||||
UJSON = 'ujson'
|
||||
|
||||
# Detect mode
|
||||
mode = JSON
|
||||
for json_lib in (RAPIDJSON, UJSON):
|
||||
if "DISABLE_" + json_lib.upper() in os.environ:
|
||||
if 'DISABLE_' + json_lib.upper() in os.environ:
|
||||
continue
|
||||
|
||||
try:
|
||||
|
|
@ -20,35 +20,28 @@ for json_lib in (RAPIDJSON, UJSON):
|
|||
break
|
||||
|
||||
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 loads(data):
|
||||
return json.loads(
|
||||
data, number_mode=json.NM_NATIVE, datetime_mode=json.DM_ISO8601 | json.DM_NAIVE_IS_UTC
|
||||
)
|
||||
|
||||
|
||||
elif mode == UJSON:
|
||||
|
||||
def loads(data):
|
||||
return json.loads(data)
|
||||
|
||||
def dumps(data):
|
||||
return json.dumps(data, ensure_ascii=False)
|
||||
|
||||
|
||||
def loads(data):
|
||||
return json.loads(data, number_mode=json.NM_NATIVE)
|
||||
|
||||
elif mode == UJSON:
|
||||
def loads(data):
|
||||
return json.loads(data)
|
||||
|
||||
|
||||
def dumps(data):
|
||||
return json.dumps(data, ensure_ascii=False)
|
||||
|
||||
else:
|
||||
import json
|
||||
|
||||
|
||||
def dumps(data):
|
||||
return json.dumps(data, ensure_ascii=False)
|
||||
|
||||
|
||||
def loads(data):
|
||||
return json.loads(data)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import contextvars
|
||||
from typing import TypeVar, Type
|
||||
|
||||
__all__ = ("DataMixin", "ContextInstanceMixin")
|
||||
__all__ = ('DataMixin', 'ContextInstanceMixin')
|
||||
|
||||
|
||||
class DataMixin:
|
||||
@property
|
||||
def data(self):
|
||||
data = getattr(self, "_data", None)
|
||||
data = getattr(self, '_data', None)
|
||||
if data is None:
|
||||
data = {}
|
||||
setattr(self, "_data", data)
|
||||
setattr(self, '_data', data)
|
||||
return data
|
||||
|
||||
def __getitem__(self, item):
|
||||
|
|
@ -26,12 +26,12 @@ class DataMixin:
|
|||
return self.data.get(key, default)
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class ContextInstanceMixin:
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
cls.__context_instance = contextvars.ContextVar("instance_" + cls.__name__)
|
||||
cls.__context_instance = contextvars.ContextVar(f'instance_{cls.__name__}')
|
||||
return cls
|
||||
|
||||
@classmethod
|
||||
|
|
@ -43,7 +43,5 @@ class ContextInstanceMixin:
|
|||
@classmethod
|
||||
def set_current(cls: Type[T], value: T):
|
||||
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__!r} not {type(value).__name__!r}')
|
||||
cls.__context_instance.set(value)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from babel.support import LazyProxy
|
|||
from aiogram import types
|
||||
from . import json
|
||||
|
||||
DEFAULT_FILTER = ["self", "cls"]
|
||||
DEFAULT_FILTER = ['self', 'cls']
|
||||
|
||||
|
||||
def generate_payload(exclude=None, **kwargs):
|
||||
|
|
@ -21,11 +21,10 @@ def generate_payload(exclude=None, **kwargs):
|
|||
"""
|
||||
if exclude is None:
|
||||
exclude = []
|
||||
return {
|
||||
key: value
|
||||
for key, value in kwargs.items()
|
||||
if key not in exclude + DEFAULT_FILTER and value is not None and not key.startswith("_")
|
||||
}
|
||||
return {key: value for key, value in kwargs.items() if
|
||||
key not in exclude + DEFAULT_FILTER
|
||||
and value is not None
|
||||
and not key.startswith('_')}
|
||||
|
||||
|
||||
def _normalize(obj):
|
||||
|
|
@ -39,7 +38,7 @@ def _normalize(obj):
|
|||
return [_normalize(item) for item in obj]
|
||||
elif isinstance(obj, dict):
|
||||
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
|
||||
|
||||
|
|
@ -53,14 +52,14 @@ def prepare_arg(value):
|
|||
"""
|
||||
if value is None:
|
||||
return value
|
||||
elif isinstance(value, (list, dict)) or hasattr(value, "to_python"):
|
||||
if isinstance(value, (list, dict)) or hasattr(value, 'to_python'):
|
||||
return json.dumps(_normalize(value))
|
||||
elif isinstance(value, datetime.timedelta):
|
||||
if isinstance(value, datetime.timedelta):
|
||||
now = datetime.datetime.now()
|
||||
return int((now + value).timestamp())
|
||||
elif isinstance(value, datetime.datetime):
|
||||
if isinstance(value, datetime.datetime):
|
||||
return round(value.timestamp())
|
||||
elif isinstance(value, LazyProxy):
|
||||
if isinstance(value, LazyProxy):
|
||||
return str(value)
|
||||
return value
|
||||
|
||||
|
|
|
|||
|
|
@ -15,5 +15,4 @@ sphinx-rtd-theme>=0.4.3
|
|||
sphinxcontrib-programoutput>=0.14
|
||||
aiohttp-socks>=0.2.2
|
||||
rethinkdb>=2.4.1
|
||||
lxml==4.3.4
|
||||
requests==2.22.0
|
||||
coverage==4.5.3
|
||||
|
|
|
|||
|
|
@ -111,6 +111,30 @@ ExceptionsFilter
|
|||
:show-inheritance:
|
||||
|
||||
|
||||
IDFilter
|
||||
----------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.IDFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
AdminFilter
|
||||
----------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.AdminFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
IsReplyFilter
|
||||
-------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.filters.IsReplyFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Making own filters (Custom filters)
|
||||
===================================
|
||||
|
||||
|
|
@ -156,3 +180,4 @@ BoundFilter
|
|||
|
||||
|
||||
dp.filters_factory.bind(ChatIdFilter, event_handlers=[dp.message_handlers])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
.. Autogenerated file at 2018-10-28 19:31:48.335963
|
||||
|
||||
=========================
|
||||
Adwanced executor example
|
||||
Advanced executor example
|
||||
=========================
|
||||
|
||||
!/usr/bin/env python3
|
||||
**This example is outdated**
|
||||
In this example used ArgumentParser for configuring Your bot.
|
||||
Provided to start bot with webhook:
|
||||
python adwanced_executor_example.py \
|
||||
python advanced_executor_example.py \
|
||||
--token TOKEN_HERE \
|
||||
--host 0.0.0.0 \
|
||||
--port 8084 \
|
||||
--host-name example.com \
|
||||
--webhook-port 443
|
||||
Or long polling:
|
||||
python adwanced_executor_example.py --token TOKEN_HERE
|
||||
python advanced_executor_example.py --token TOKEN_HERE
|
||||
So... In this example found small trouble:
|
||||
can't get bot instance in handlers.
|
||||
If you want to automatic change getting updates method use executor utils (from aiogram.utils.executor)
|
||||
TODO: Move token to environment variables.
|
||||
|
||||
.. literalinclude:: ../../../examples/adwanced_executor_example.py
|
||||
:caption: adwanced_executor_example.py
|
||||
.. literalinclude:: ../../../examples/advanced_executor_example.py
|
||||
:caption: advanced_executor_example.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 25-
|
||||
|
|
@ -6,7 +6,7 @@ Examples
|
|||
|
||||
echo_bot
|
||||
inline_bot
|
||||
adwanced_executor_example
|
||||
advanced_executor_example
|
||||
proxy_and_emojize
|
||||
finite_state_machine_example
|
||||
throtling_example
|
||||
|
|
|
|||
|
|
@ -22,18 +22,14 @@ Welcome to aiogram's documentation!
|
|||
:target: https://pypi.python.org/pypi/aiogram
|
||||
:alt: Supported python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.3-blue.svg?style=flat-square&logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.4-blue.svg?style=flat-square&logo=telegram
|
||||
:target: https://core.telegram.org/bots/api
|
||||
:alt: Telegram Bot API
|
||||
|
||||
.. image:: https://img.shields.io/readthedocs/pip/stable.svg?style=flat-square
|
||||
:target: http://aiogram.readthedocs.io/en/latest/?badge=latest?style=flat-square
|
||||
.. image:: https://img.shields.io/readthedocs/aiogram?style=flat-square
|
||||
:target: http://aiogram.readthedocs.io/en/latest/?badge=latest
|
||||
: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
|
||||
:target: https://github.com/aiogram/aiogram/issues
|
||||
:alt: Github issues
|
||||
|
|
@ -43,7 +39,7 @@ Welcome to aiogram's documentation!
|
|||
:alt: MIT License
|
||||
|
||||
|
||||
**aiogram** is a pretty simple and fully asynchronous library for `Telegram Bot API <https://core.telegram.org/bots/api>`_ written in Python 3.7 with `asyncio <https://docs.python.org/3/library/asyncio.html>`_ and `aiohttp <https://github.com/aio-libs/aiohttp>`_. It helps you to make your bots faster and simpler.
|
||||
**aiogram** is a pretty simple and fully asynchronous framework for `Telegram Bot API <https://core.telegram.org/bots/api>`_ written in Python 3.7 with `asyncio <https://docs.python.org/3/library/asyncio.html>`_ and `aiohttp <https://github.com/aio-libs/aiohttp>`_. It helps you to make your bots faster and simpler.
|
||||
|
||||
|
||||
Official aiogram resources
|
||||
|
|
|
|||
|
|
@ -7,21 +7,34 @@ Using PIP
|
|||
|
||||
$ pip install -U aiogram
|
||||
|
||||
Using Pipenv
|
||||
------------
|
||||
.. code-block:: bash
|
||||
|
||||
$ pipenv install aiogram
|
||||
|
||||
Using AUR
|
||||
---------
|
||||
*aiogram* is also available in Arch User Repository, so you can install this library on any Arch-based distribution like ArchLinux, Antergos, Manjaro, etc. To do this, use your favorite AUR-helper and install `python-aiogram <https://aur.archlinux.org/packages/python-aiogram/>`_ package.
|
||||
|
||||
From sources
|
||||
------------
|
||||
|
||||
Development versions:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git clone https://github.com/aiogram/aiogram.git
|
||||
$ cd aiogram
|
||||
$ python setup.py install
|
||||
|
||||
or if you want to install development version (maybe unstable):
|
||||
Or if you want to install stable version (The same with version form PyPi):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git clone https://github.com/aiogram/aiogram.git
|
||||
$ cd aiogram
|
||||
$ git checkout dev-2.x
|
||||
$ git checkout master
|
||||
$ python setup.py install
|
||||
|
||||
|
||||
|
|
@ -49,4 +62,36 @@ You can speedup your bots by following next instructions:
|
|||
|
||||
$ pip install ujson
|
||||
|
||||
- Use aiohttp speedups
|
||||
|
||||
- Use `cchardet <https://github.com/PyYoshi/cChardet>`_ instead of chardet module.
|
||||
|
||||
*cChardet* is high speed universal character encoding detector.
|
||||
|
||||
**Installation:**
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pip install cchardet
|
||||
|
||||
- Use `aiodns <https://github.com/saghul/aiodns>`_ for speeding up DNS resolving.
|
||||
|
||||
*aiodns* provides a simple way for doing asynchronous DNS resolutions.
|
||||
|
||||
**Installation:**
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pip install aiodns
|
||||
|
||||
- Installing speedups altogether.
|
||||
|
||||
The following will get you ``aiohttp`` along with ``cchardet``, ``aiodns`` and ``brotlipy`` in one bundle.
|
||||
|
||||
**Installation:**
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pip install aiohttp[speedups]
|
||||
|
||||
In addition, you don't need do nothing, *aiogram* is automatically starts using that if is found in your environment.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
===========
|
||||
Auth Widget
|
||||
===========
|
||||
Coming soon...
|
||||
|
||||
.. automodule:: aiogram.utils.auth_widget
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
=======
|
||||
Context
|
||||
=======
|
||||
Coming soon...
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
==========
|
||||
Deprecated
|
||||
==========
|
||||
Coming soon...
|
||||
|
||||
.. automodule:: aiogram.utils.deprecated
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
=====
|
||||
Emoji
|
||||
=====
|
||||
Coming soon...
|
||||
|
||||
.. automodule:: aiogram.utils.emoji
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
==========
|
||||
Exceptions
|
||||
==========
|
||||
Coming soon...
|
||||
|
||||
.. automodule:: aiogram.utils.exceptions
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
========
|
||||
Executor
|
||||
========
|
||||
Coming soon...
|
||||
|
||||
.. automodule:: aiogram.utils.executor
|
||||
:members:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
======
|
||||
Helper
|
||||
======
|
||||
Coming soon...
|
||||
|
||||
.. automodule:: aiogram.utils.helper
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ Utils
|
|||
|
||||
.. toctree::
|
||||
|
||||
auth_widget
|
||||
executor
|
||||
exceptions
|
||||
context
|
||||
markdown
|
||||
helper
|
||||
auth_widget
|
||||
deprecated
|
||||
payload
|
||||
parts
|
||||
json
|
||||
emoji
|
||||
deprecated
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
====
|
||||
JSON
|
||||
====
|
||||
Coming soon...
|
||||
|
||||
.. automodule:: aiogram.utils.json
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
========
|
||||
Markdown
|
||||
========
|
||||
Coming soon...
|
||||
|
||||
.. automodule:: aiogram.utils.markdown
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
=====
|
||||
Parts
|
||||
=====
|
||||
Coming soon...
|
||||
|
||||
.. automodule:: aiogram.utils.parts
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
=======
|
||||
Payload
|
||||
=======
|
||||
Coming soon...
|
||||
|
||||
.. automodule:: aiogram.utils.payload
|
||||
:members:
|
||||
|
|
|
|||
33
examples/admin_filter_example.py
Normal file
33
examples/admin_filter_example.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import logging
|
||||
|
||||
from aiogram import Bot, Dispatcher, types, executor
|
||||
|
||||
API_TOKEN = 'API_TOKEN_HERE'
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot=bot)
|
||||
|
||||
|
||||
# checks specified chat
|
||||
@dp.message_handler(is_chat_admin=-1001241113577)
|
||||
async def handle_specified(msg: types.Message):
|
||||
await msg.answer("You are an admin of the specified chat!")
|
||||
|
||||
|
||||
# checks multiple chats
|
||||
@dp.message_handler(is_chat_admin=[-1001241113577, -320463906])
|
||||
async def handle_multiple(msg: types.Message):
|
||||
await msg.answer("You are an admin of multiple chats!")
|
||||
|
||||
|
||||
# checks current chat
|
||||
@dp.message_handler(is_chat_admin=True)
|
||||
async def handler3(msg: types.Message):
|
||||
await msg.answer("You are an admin of the current chat!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp)
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
In this example used ArgumentParser for configuring Your bot.
|
||||
|
||||
Provided to start bot with webhook:
|
||||
python adwanced_executor_example.py \
|
||||
python advanced_executor_example.py \
|
||||
--token TOKEN_HERE \
|
||||
--host 0.0.0.0 \
|
||||
--port 8084 \
|
||||
|
|
@ -12,7 +12,7 @@ Provided to start bot with webhook:
|
|||
--webhook-port 443
|
||||
|
||||
Or long polling:
|
||||
python adwanced_executor_example.py --token TOKEN_HERE
|
||||
python advanced_executor_example.py --token TOKEN_HERE
|
||||
|
||||
So... In this example found small trouble:
|
||||
can't get bot instance in handlers.
|
||||
|
|
@ -9,9 +9,8 @@ API_TOKEN = "BOT TOKEN HERE"
|
|||
logging.basicConfig(level=logging.INFO)
|
||||
log = logging.getLogger("broadcast")
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
bot = Bot(token=API_TOKEN, loop=loop, parse_mode=types.ParseMode.HTML)
|
||||
dp = Dispatcher(bot, loop=loop)
|
||||
bot = Bot(token=API_TOKEN, parse_mode=types.ParseMode.HTML)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
def get_users():
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import random
|
||||
import uuid
|
||||
|
|
@ -11,27 +10,26 @@ from aiogram.utils.exceptions import MessageNotModified, Throttled
|
|||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
API_TOKEN = "BOT TOKEN HERE"
|
||||
API_TOKEN = 'BOT TOKEN HERE'
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
bot = Bot(token=API_TOKEN, loop=loop, parse_mode=types.ParseMode.HTML)
|
||||
|
||||
bot = Bot(token=API_TOKEN, parse_mode=types.ParseMode.HTML)
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot, storage=storage)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
POSTS = {
|
||||
str(uuid.uuid4()): {
|
||||
"title": f"Post {index}",
|
||||
"body": "Lorem ipsum dolor sit amet, "
|
||||
"consectetur adipiscing elit, "
|
||||
"sed do eiusmod tempor incididunt ut "
|
||||
"labore et dolore magna aliqua",
|
||||
"votes": random.randint(-2, 5),
|
||||
}
|
||||
for index in range(1, 6)
|
||||
'title': f'Post {index}',
|
||||
'body': 'Lorem ipsum dolor sit amet, '
|
||||
'consectetur adipiscing elit, '
|
||||
'sed do eiusmod tempor incididunt ut '
|
||||
'labore et dolore magna aliqua',
|
||||
'votes': random.randint(-2, 5),
|
||||
} 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:
|
||||
|
|
@ -42,73 +40,72 @@ def get_keyboard() -> types.InlineKeyboardMarkup:
|
|||
for post_id, post in POSTS.items():
|
||||
markup.add(
|
||||
types.InlineKeyboardButton(
|
||||
post["title"], callback_data=posts_cb.new(id=post_id, action="view")
|
||||
)
|
||||
post['title'],
|
||||
callback_data=posts_cb.new(id=post_id, action='view')),
|
||||
)
|
||||
return markup
|
||||
|
||||
|
||||
def format_post(post_id: str, post: dict) -> (str, types.InlineKeyboardMarkup):
|
||||
text = (
|
||||
f"{md.hbold(post['title'])}\n"
|
||||
f"{md.quote_html(post['body'])}\n"
|
||||
f"\n"
|
||||
f"Votes: {post['votes']}"
|
||||
text = md.text(
|
||||
md.hbold(post['title']),
|
||||
md.quote_html(post['body']),
|
||||
'', # just new empty line
|
||||
f"Votes: {post['votes']}",
|
||||
sep = '\n',
|
||||
)
|
||||
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
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="unlike")),
|
||||
)
|
||||
markup.add(
|
||||
types.InlineKeyboardButton("<< Back", callback_data=posts_cb.new(id="-", action="list"))
|
||||
types.InlineKeyboardButton('👍', callback_data=posts_cb.new(id=post_id, action='like')),
|
||||
types.InlineKeyboardButton('👎', callback_data=posts_cb.new(id=post_id, action='dislike')),
|
||||
)
|
||||
markup.add(types.InlineKeyboardButton('<< Back', callback_data=posts_cb.new(id='-', action='list')))
|
||||
return text, markup
|
||||
|
||||
|
||||
@dp.message_handler(commands="start")
|
||||
@dp.message_handler(commands='start')
|
||||
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):
|
||||
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):
|
||||
post_id = callback_data["id"]
|
||||
post_id = callback_data['id']
|
||||
|
||||
post = POSTS.get(post_id, None)
|
||||
if not post:
|
||||
return await query.answer("Unknown post!")
|
||||
return await query.answer('Unknown post!')
|
||||
|
||||
text, markup = format_post(post_id, post)
|
||||
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', 'dislike']))
|
||||
async def query_post_vote(query: types.CallbackQuery, callback_data: dict):
|
||||
try:
|
||||
await dp.throttle("vote", rate=1)
|
||||
await dp.throttle('vote', rate=1)
|
||||
except Throttled:
|
||||
return await query.answer("Too many requests.")
|
||||
return await query.answer('Too many requests.')
|
||||
|
||||
post_id = callback_data["id"]
|
||||
action = callback_data["action"]
|
||||
post_id = callback_data['id']
|
||||
action = callback_data['action']
|
||||
|
||||
post = POSTS.get(post_id, None)
|
||||
if not post:
|
||||
return await query.answer("Unknown post!")
|
||||
return await query.answer('Unknown post!')
|
||||
|
||||
if action == "like":
|
||||
post["votes"] += 1
|
||||
elif action == "unlike":
|
||||
post["votes"] -= 1
|
||||
if action == 'like':
|
||||
post['votes'] += 1
|
||||
elif action == 'dislike':
|
||||
post['votes'] -= 1
|
||||
|
||||
await query.answer("Voted.")
|
||||
await query.answer('Vote accepted')
|
||||
text, markup = format_post(post_id, post)
|
||||
await query.message.edit_text(text, reply_markup=markup)
|
||||
|
||||
|
|
@ -118,5 +115,5 @@ async def message_not_modified_handler(update, error):
|
|||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
|
|||
68
examples/callback_data_factory_simple.py
Normal file
68
examples/callback_data_factory_simple.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
This is a simple example of usage of CallbackData factory
|
||||
For more comprehensive example see callback_data_factory.py
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, Dispatcher, executor, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
from aiogram.utils.exceptions import MessageNotModified
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
vote_cb = CallbackData('vote', 'action') # vote:<action>
|
||||
likes = {} # user_id: amount_of_likes
|
||||
|
||||
|
||||
def get_keyboard():
|
||||
return types.InlineKeyboardMarkup().row(
|
||||
types.InlineKeyboardButton('👍', callback_data=vote_cb.new(action='up')),
|
||||
types.InlineKeyboardButton('👎', callback_data=vote_cb.new(action='down')),
|
||||
)
|
||||
|
||||
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def cmd_start(message: types.Message):
|
||||
amount_of_likes = likes.get(message.from_user.id, 0) # get value if key exists else set to 0
|
||||
await message.reply(f'Vote! You have {amount_of_likes} votes now.', reply_markup=get_keyboard())
|
||||
|
||||
|
||||
@dp.callback_query_handler(vote_cb.filter(action=['up', 'down']))
|
||||
async def callback_vote_action(query: types.CallbackQuery, callback_data: dict):
|
||||
logging.info('Got this callback data: %r', callback_data) # callback_data contains all info from callback data
|
||||
await query.answer() # don't forget to answer callback query as soon as possible
|
||||
callback_data_action = callback_data['action']
|
||||
likes_count = likes.get(query.from_user.id, 0)
|
||||
|
||||
if callback_data_action == 'up':
|
||||
likes_count += 1
|
||||
else:
|
||||
likes_count -= 1
|
||||
|
||||
likes[query.from_user.id] = likes_count # update amount of likes in storage
|
||||
|
||||
await bot.edit_message_text(
|
||||
f'You voted {callback_data_action}! Now you have {likes_count} vote[s].',
|
||||
query.from_user.id,
|
||||
query.message.message_id,
|
||||
reply_markup=get_keyboard(),
|
||||
)
|
||||
|
||||
|
||||
@dp.errors_handler(exception=MessageNotModified) # handle the cases when this exception raises
|
||||
async def message_not_modified_handler(update, error):
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
@ -2,17 +2,16 @@
|
|||
Babel is required.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, Dispatcher, executor, md, types
|
||||
|
||||
API_TOKEN = "BOT TOKEN HERE"
|
||||
API_TOKEN = 'BOT TOKEN HERE'
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
bot = Bot(token=API_TOKEN, loop=loop, parse_mode=types.ParseMode.MARKDOWN)
|
||||
|
||||
bot = Bot(token=API_TOKEN, parse_mode=types.ParseMode.MARKDOWN)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
|
|
@ -20,17 +19,15 @@ dp = Dispatcher(bot)
|
|||
async def check_language(message: types.Message):
|
||||
locale = message.from_user.locale
|
||||
|
||||
await message.reply(
|
||||
md.text(
|
||||
md.bold("Info about your language:"),
|
||||
md.text(" 🔸", md.bold("Code:"), md.italic(locale.locale)),
|
||||
md.text(" 🔸", md.bold("Territory:"), md.italic(locale.territory or "Unknown")),
|
||||
md.text(" 🔸", md.bold("Language name:"), md.italic(locale.language_name)),
|
||||
md.text(" 🔸", md.bold("English language name:"), md.italic(locale.english_name)),
|
||||
sep="\n",
|
||||
)
|
||||
)
|
||||
await message.reply(md.text(
|
||||
md.bold('Info about your language:'),
|
||||
md.text('🔸', md.bold('Code:'), md.code(locale.language)),
|
||||
md.text('🔸', md.bold('Territory:'), md.code(locale.territory or 'Unknown')),
|
||||
md.text('🔸', md.bold('Language name:'), md.code(locale.language_name)),
|
||||
md.text('🔸', md.bold('English language name:'), md.code(locale.english_name)),
|
||||
sep='\n',
|
||||
))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import logging
|
|||
|
||||
from aiogram import Bot, Dispatcher, executor, types
|
||||
|
||||
API_TOKEN = "BOT TOKEN HERE"
|
||||
API_TOKEN = 'BOT TOKEN HERE'
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
|
@ -17,29 +17,37 @@ bot = Bot(token=API_TOKEN)
|
|||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
@dp.message_handler(commands=["start", "help"])
|
||||
@dp.message_handler(commands=['start', 'help'])
|
||||
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 user sends `/start` or `/help` command
|
||||
"""
|
||||
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):
|
||||
with open("data/cats.jpg", "rb") as photo:
|
||||
with open('data/cats.jpg', 'rb') as photo:
|
||||
'''
|
||||
# Old fashioned way:
|
||||
await bot.send_photo(
|
||||
message.chat.id,
|
||||
photo,
|
||||
caption="Cats is here 😺",
|
||||
caption='Cats are here 😺',
|
||||
reply_to_message_id=message.message_id,
|
||||
)
|
||||
'''
|
||||
|
||||
await message.reply_photo(photo, caption='Cats are here 😺')
|
||||
|
||||
|
||||
@dp.message_handler()
|
||||
async def echo(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text)
|
||||
# old style:
|
||||
# await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
await message.reply(message.text, reply=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
import asyncio
|
||||
from typing import Optional
|
||||
import logging
|
||||
|
||||
import aiogram.utils.markdown as md
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters import Text
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.types import ParseMode
|
||||
from aiogram.utils import executor
|
||||
|
||||
API_TOKEN = "BOT TOKEN HERE"
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
API_TOKEN = 'BOT TOKEN HERE'
|
||||
|
||||
bot = Bot(token=API_TOKEN, loop=loop)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
|
||||
# For example use simple MemoryStorage for Dispatcher.
|
||||
storage = MemoryStorage()
|
||||
|
|
@ -27,7 +28,7 @@ class Form(StatesGroup):
|
|||
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):
|
||||
"""
|
||||
Conversation's entry point
|
||||
|
|
@ -39,21 +40,21 @@ async def cmd_start(message: types.Message):
|
|||
|
||||
|
||||
# You can use state '*' if you need to handle all states
|
||||
@dp.message_handler(state="*", commands=["cancel"])
|
||||
@dp.message_handler(lambda message: message.text.lower() == "cancel", state="*")
|
||||
async def cancel_handler(
|
||||
message: types.Message, state: FSMContext, raw_state: Optional[str] = None
|
||||
):
|
||||
@dp.message_handler(state='*', commands='cancel')
|
||||
@dp.message_handler(Text(equals='cancel', ignore_case=True), state='*')
|
||||
async def cancel_handler(message: types.Message, state: FSMContext):
|
||||
"""
|
||||
Allow user to cancel any action
|
||||
"""
|
||||
if raw_state is None:
|
||||
current_state = await state.get_state()
|
||||
if current_state is None:
|
||||
return
|
||||
|
||||
logging.info('Cancelling state %r', current_state)
|
||||
# Cancel state and inform user about it
|
||||
await state.finish()
|
||||
# And remove keyboard (just in case)
|
||||
await message.reply("Canceled.", reply_markup=types.ReplyKeyboardRemove())
|
||||
await message.reply('Cancelled.', reply_markup=types.ReplyKeyboardRemove())
|
||||
|
||||
|
||||
@dp.message_handler(state=Form.name)
|
||||
|
|
@ -62,7 +63,7 @@ async def process_name(message: types.Message, state: FSMContext):
|
|||
Process user name
|
||||
"""
|
||||
async with state.proxy() as data:
|
||||
data["name"] = message.text
|
||||
data['name'] = message.text
|
||||
|
||||
await Form.next()
|
||||
await message.reply("How old are you?")
|
||||
|
|
@ -70,7 +71,7 @@ async def process_name(message: types.Message, state: FSMContext):
|
|||
|
||||
# Check age. Age gotta be digit
|
||||
@dp.message_handler(lambda message: not message.text.isdigit(), state=Form.age)
|
||||
async def failed_process_age(message: types.Message):
|
||||
async def process_age_invalid(message: types.Message):
|
||||
"""
|
||||
If age is invalid
|
||||
"""
|
||||
|
|
@ -91,20 +92,18 @@ async def process_age(message: types.Message, state: FSMContext):
|
|||
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
|
||||
)
|
||||
async def failed_process_gender(message: types.Message):
|
||||
@dp.message_handler(lambda message: message.text not in ["Male", "Female", "Other"], state=Form.gender)
|
||||
async def process_gender_invalid(message: types.Message):
|
||||
"""
|
||||
In this example gender has to be one of: Male, Female, Other.
|
||||
"""
|
||||
return await message.reply("Bad gender name. Choose you gender from keyboard.")
|
||||
return await message.reply("Bad gender name. Choose your gender from the keyboard.")
|
||||
|
||||
|
||||
@dp.message_handler(state=Form.gender)
|
||||
async def process_gender(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data["gender"] = message.text
|
||||
data['gender'] = message.text
|
||||
|
||||
# Remove keyboard
|
||||
markup = types.ReplyKeyboardRemove()
|
||||
|
|
@ -113,18 +112,18 @@ async def process_gender(message: types.Message, state: FSMContext):
|
|||
await bot.send_message(
|
||||
message.chat.id,
|
||||
md.text(
|
||||
md.text("Hi! Nice to meet you,", md.bold(data["name"])),
|
||||
md.text("Age:", data["age"]),
|
||||
md.text("Gender:", data["gender"]),
|
||||
sep="\n",
|
||||
md.text('Hi! Nice to meet you,', md.bold(data['name'])),
|
||||
md.text('Age:', md.code(data['age'])),
|
||||
md.text('Gender:', data['gender']),
|
||||
sep='\n',
|
||||
),
|
||||
reply_markup=markup,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
)
|
||||
|
||||
# Finish conversation
|
||||
data.state = None
|
||||
await state.finish()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Internalize your bot
|
|||
|
||||
Step 1: extract texts
|
||||
# pybabel extract i18n_example.py -o locales/mybot.pot
|
||||
|
||||
Some useful options:
|
||||
- Extract texts with pluralization support
|
||||
# -k __:1,2
|
||||
- Add comments for translators, you can use another tag if you want (TR)
|
||||
# --add-comments=NOTE
|
||||
- Disable comments with string location in code
|
||||
# --no-location
|
||||
- Set project name
|
||||
# --project=MySuperBot
|
||||
- Set version
|
||||
# --version=2.2
|
||||
|
||||
Step 2: create *.po files. For e.g. create en, ru, uk locales.
|
||||
# echo {en,ru,uk} | xargs -n1 pybabel init -i locales/mybot.pot -d locales -D mybot -l
|
||||
Step 3: translate texts
|
||||
|
|
@ -24,11 +37,11 @@ from pathlib import Path
|
|||
from aiogram import Bot, Dispatcher, executor, types
|
||||
from aiogram.contrib.middlewares.i18n import I18nMiddleware
|
||||
|
||||
TOKEN = "BOT TOKEN HERE"
|
||||
I18N_DOMAIN = "mybot"
|
||||
TOKEN = 'BOT_TOKEN_HERE'
|
||||
I18N_DOMAIN = 'mybot'
|
||||
|
||||
BASE_DIR = Path(__file__).parent
|
||||
LOCALES_DIR = BASE_DIR / "locales"
|
||||
LOCALES_DIR = BASE_DIR / 'locales'
|
||||
|
||||
bot = Bot(TOKEN, parse_mode=types.ParseMode.HTML)
|
||||
dp = Dispatcher(bot)
|
||||
|
|
@ -41,16 +54,45 @@ dp.middleware.setup(i18n)
|
|||
_ = i18n.gettext
|
||||
|
||||
|
||||
@dp.message_handler(commands=["start"])
|
||||
@dp.message_handler(commands='start')
|
||||
async def cmd_start(message: types.Message):
|
||||
# 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):
|
||||
await message.reply(_("Your current language: <i>{language}</i>").format(language=locale))
|
||||
# For setting custom lang you have to modify i18n middleware, like this:
|
||||
# https://github.com/aiogram/EventsTrackerBot/blob/master/modules/base/middlewares.py
|
||||
await message.reply(_('Your current language: <i>{language}</i>').format(language=locale))
|
||||
|
||||
# If you care about pluralization, here's small handler
|
||||
# And also, there's and example of comments for translators. Most translation tools support them.
|
||||
|
||||
# Alias for gettext method, parser will understand double underscore as plural (aka ngettext)
|
||||
__ = i18n.gettext
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# some likes manager
|
||||
LIKES_STORAGE = {'count': 0}
|
||||
|
||||
|
||||
def get_likes() -> int:
|
||||
return LIKES_STORAGE['count']
|
||||
|
||||
|
||||
def increase_likes() -> int:
|
||||
LIKES_STORAGE['count'] += 1
|
||||
return get_likes()
|
||||
#
|
||||
|
||||
|
||||
@dp.message_handler(commands='like')
|
||||
async def cmd_like(message: types.Message, locale):
|
||||
likes = increase_likes()
|
||||
|
||||
# NOTE: This is comment for a translator
|
||||
await message.reply(__('Aiogram has {number} like!', 'Aiogram has {number} likes!', likes).format(number=likes))
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
|
|||
36
examples/id_filter_example.py
Normal file
36
examples/id_filter_example.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
from aiogram import Bot, Dispatcher, executor, types
|
||||
from aiogram.dispatcher.handler import SkipHandler
|
||||
|
||||
|
||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
user_id_required = None # TODO: Set id here
|
||||
chat_id_required = user_id_required # Change for use in groups (user_id == chat_id in pm)
|
||||
|
||||
|
||||
@dp.message_handler(user_id=user_id_required)
|
||||
async def handler1(msg: types.Message):
|
||||
await bot.send_message(msg.chat.id, "Hello, checking with user_id=")
|
||||
raise SkipHandler # just for demo
|
||||
|
||||
|
||||
@dp.message_handler(chat_id=chat_id_required)
|
||||
async def handler2(msg: types.Message):
|
||||
await bot.send_message(msg.chat.id, "Hello, checking with chat_id=")
|
||||
raise SkipHandler # just for demo
|
||||
|
||||
|
||||
@dp.message_handler(user_id=user_id_required, chat_id=chat_id_required)
|
||||
async def handler3(msg: types.Message):
|
||||
await msg.reply("Hello from user= & chat_id=", reply=False)
|
||||
|
||||
|
||||
@dp.message_handler(user_id=[user_id_required, 42]) # TODO: You can add any number of ids here
|
||||
async def handler4(msg: types.Message):
|
||||
await msg.reply("Checked user_id with list!", reply=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp)
|
||||
|
|
@ -1,25 +1,37 @@
|
|||
import asyncio
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, types, Dispatcher, executor
|
||||
from aiogram import Bot, Dispatcher, executor
|
||||
from aiogram.types import InlineQuery, \
|
||||
InputTextMessageContent, InlineQueryResultArticle
|
||||
|
||||
API_TOKEN = "BOT TOKEN HERE"
|
||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
bot = Bot(token=API_TOKEN, loop=loop)
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
@dp.inline_handler()
|
||||
async def inline_echo(inline_query: types.InlineQuery):
|
||||
input_content = types.InputTextMessageContent(inline_query.query or "echo")
|
||||
item = types.InlineQueryResultArticle(
|
||||
id="1", title="echo", input_message_content=input_content
|
||||
async def inline_echo(inline_query: InlineQuery):
|
||||
# id affects both preview and content,
|
||||
# so it has to be unique for each result
|
||||
# (Unique identifier for this result, 1-64 Bytes)
|
||||
# you can set your unique id's
|
||||
# but for example i'll generate it based on text because I know, that
|
||||
# only text will be passed in this example
|
||||
text = inline_query.query or 'echo'
|
||||
input_content = InputTextMessageContent(text)
|
||||
result_id: str = hashlib.md5(text.encode()).hexdigest()
|
||||
item = InlineQueryResultArticle(
|
||||
id=result_id,
|
||||
title=f'Result {text!r}',
|
||||
input_message_content=input_content,
|
||||
)
|
||||
# don't forget to set cache_time=1 for testing (default is 300s or 5m)
|
||||
await bot.answer_inline_query(inline_query.id, results=[item], cache_time=1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
|
|||
62
examples/inline_keyboard_example.py
Normal file
62
examples/inline_keyboard_example.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
"""
|
||||
This bot is created for the demonstration of a usage of inline keyboards.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, Dispatcher, executor, types
|
||||
|
||||
|
||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Initialize bot and dispatcher
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
@dp.message_handler(commands='start')
|
||||
async def start_cmd_handler(message: types.Message):
|
||||
keyboard_markup = types.InlineKeyboardMarkup(row_width=3)
|
||||
# default row_width is 3, so here we can omit it actually
|
||||
# kept for clearness
|
||||
|
||||
text_and_data = (
|
||||
('Yes!', 'yes'),
|
||||
('No!', 'no'),
|
||||
)
|
||||
# in real life for the callback_data the callback data factory should be used
|
||||
# here the raw string is used for the simplicity
|
||||
row_btns = (types.InlineKeyboardButton(text, callback_data=data) for text, data in text_and_data)
|
||||
|
||||
keyboard_markup.row(*row_btns)
|
||||
keyboard_markup.add(
|
||||
# url buttons have no callback data
|
||||
types.InlineKeyboardButton('aiogram source', url='https://github.com/aiogram/aiogram'),
|
||||
)
|
||||
|
||||
await message.reply("Hi!\nDo you love aiogram?", reply_markup=keyboard_markup)
|
||||
|
||||
|
||||
# Use multiple registrators. Handler will execute when one of the filters is OK
|
||||
@dp.callback_query_handler(text='no') # if cb.data == 'no'
|
||||
@dp.callback_query_handler(text='yes') # if cb.data == 'yes'
|
||||
async def inline_kb_answer_callback_handler(query: types.CallbackQuery):
|
||||
answer_data = query.data
|
||||
# always answer callback queries, even if you have nothing to say
|
||||
await query.answer(f'You answered with {answer_data!r}')
|
||||
|
||||
if answer_data == 'yes':
|
||||
text = 'Great, me too!'
|
||||
elif answer_data == 'no':
|
||||
text = 'Oh no...Why so?'
|
||||
else:
|
||||
text = f'Unexpected callback data {answer_data!r}!'
|
||||
|
||||
await bot.send_message(query.from_user.id, text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
@ -1,27 +1,26 @@
|
|||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2018 ORGANIZATION
|
||||
# Copyright (C) 2019 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2018-06-30 03:50+0300\n"
|
||||
"POT-Creation-Date: 2019-08-10 17:51+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.6.0\n"
|
||||
"Generated-By: Babel 2.7.0\n"
|
||||
|
||||
#: i18n_example.py:48
|
||||
#: i18n_example.py:60
|
||||
msgid "Hello, <b>{user}</b>!"
|
||||
msgstr ""
|
||||
|
||||
#: i18n_example.py:53
|
||||
#: i18n_example.py:67
|
||||
msgid "Your current language: <i>{language}</i>"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
# Russian translations for PROJECT.
|
||||
# Copyright (C) 2018 ORGANIZATION
|
||||
# Copyright (C) 2019 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2018-06-30 03:50+0300\n"
|
||||
"PO-Revision-Date: 2018-06-30 03:43+0300\n"
|
||||
"POT-Creation-Date: 2019-08-10 17:51+0300\n"
|
||||
"PO-Revision-Date: 2019-08-10 17:52+0300\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: ru\n"
|
||||
"Language-Team: ru <LL@li.org>\n"
|
||||
|
|
@ -17,13 +17,19 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.6.0\n"
|
||||
"Generated-By: Babel 2.7.0\n"
|
||||
|
||||
#: i18n_example.py:48
|
||||
#: i18n_example.py:60
|
||||
msgid "Hello, <b>{user}</b>!"
|
||||
msgstr "Привет, <b>{user}</b>!"
|
||||
|
||||
#: i18n_example.py:53
|
||||
#: i18n_example.py:67
|
||||
msgid "Your current language: <i>{language}</i>"
|
||||
msgstr "Твой язык: <i>{language}</i>"
|
||||
|
||||
#: i18n_example.py:95
|
||||
msgid "Aiogram has {number} like!"
|
||||
msgid_plural "Aiogram has {number} likes!"
|
||||
msgstr[0] "Aiogram имеет {number} лайк!"
|
||||
msgstr[1] "Aiogram имеет {number} лайка!"
|
||||
msgstr[2] "Aiogram имеет {number} лайков!"
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import asyncio
|
|||
|
||||
from aiogram import Bot, Dispatcher, executor, filters, types
|
||||
|
||||
API_TOKEN = "BOT TOKEN HERE"
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
bot = Bot(token=API_TOKEN, loop=loop)
|
||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
|
|
@ -14,23 +14,23 @@ async def send_welcome(message: types.Message):
|
|||
# So... At first I want to send something like this:
|
||||
await message.reply("Do you want to see many pussies? Are you ready?")
|
||||
|
||||
# And wait few seconds...
|
||||
# Wait a little...
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Good bots should send chat actions. Or not.
|
||||
# Good bots should send chat actions...
|
||||
await types.ChatActions.upload_photo()
|
||||
|
||||
# Create media group
|
||||
media = types.MediaGroup()
|
||||
|
||||
# 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!
|
||||
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
|
||||
# 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:
|
||||
# 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)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
|
|||
|
|
@ -7,14 +7,12 @@ from aiogram.dispatcher.handler import CancelHandler, current_handler
|
|||
from aiogram.dispatcher.middlewares import BaseMiddleware
|
||||
from aiogram.utils.exceptions import Throttled
|
||||
|
||||
TOKEN = "BOT TOKEN HERE"
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
TOKEN = 'BOT_TOKEN_HERE'
|
||||
|
||||
# In this example Redis storage is used
|
||||
storage = RedisStorage2(db=5)
|
||||
|
||||
bot = Bot(token=TOKEN, loop=loop)
|
||||
bot = Bot(token=TOKEN)
|
||||
dp = Dispatcher(bot, storage=storage)
|
||||
|
||||
|
||||
|
|
@ -28,9 +26,9 @@ def rate_limit(limit: int, key=None):
|
|||
"""
|
||||
|
||||
def decorator(func):
|
||||
setattr(func, "throttling_rate_limit", limit)
|
||||
setattr(func, 'throttling_rate_limit', limit)
|
||||
if key:
|
||||
setattr(func, "throttling_key", key)
|
||||
setattr(func, 'throttling_key', key)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
|
@ -41,7 +39,7 @@ class ThrottlingMiddleware(BaseMiddleware):
|
|||
Simple middleware
|
||||
"""
|
||||
|
||||
def __init__(self, limit=DEFAULT_RATE_LIMIT, key_prefix="antiflood_"):
|
||||
def __init__(self, limit=DEFAULT_RATE_LIMIT, key_prefix='antiflood_'):
|
||||
self.rate_limit = limit
|
||||
self.prefix = key_prefix
|
||||
super(ThrottlingMiddleware, self).__init__()
|
||||
|
|
@ -59,8 +57,8 @@ class ThrottlingMiddleware(BaseMiddleware):
|
|||
dispatcher = Dispatcher.get_current()
|
||||
# If handler was configured, get rate limit and key from handler
|
||||
if handler:
|
||||
limit = getattr(handler, "throttling_rate_limit", self.rate_limit)
|
||||
key = getattr(handler, "throttling_key", f"{self.prefix}_{handler.__name__}")
|
||||
limit = getattr(handler, 'throttling_rate_limit', self.rate_limit)
|
||||
key = getattr(handler, 'throttling_key', f"{self.prefix}_{handler.__name__}")
|
||||
else:
|
||||
limit = self.rate_limit
|
||||
key = f"{self.prefix}_message"
|
||||
|
|
@ -85,7 +83,7 @@ class ThrottlingMiddleware(BaseMiddleware):
|
|||
handler = current_handler.get()
|
||||
dispatcher = Dispatcher.get_current()
|
||||
if handler:
|
||||
key = getattr(handler, "throttling_key", f"{self.prefix}_{handler.__name__}")
|
||||
key = getattr(handler, 'throttling_key', f"{self.prefix}_{handler.__name__}")
|
||||
else:
|
||||
key = f"{self.prefix}_message"
|
||||
|
||||
|
|
@ -94,7 +92,7 @@ class ThrottlingMiddleware(BaseMiddleware):
|
|||
|
||||
# Prevent flooding
|
||||
if throttled.exceeded_count <= 2:
|
||||
await message.reply("Too many requests! ")
|
||||
await message.reply('Too many requests! ')
|
||||
|
||||
# Sleep.
|
||||
await asyncio.sleep(delta)
|
||||
|
|
@ -104,21 +102,19 @@ class ThrottlingMiddleware(BaseMiddleware):
|
|||
|
||||
# If current message is not last with current key - do not send message
|
||||
if thr.exceeded_count == throttled.exceeded_count:
|
||||
await message.reply("Unlocked.")
|
||||
await message.reply('Unlocked.')
|
||||
|
||||
|
||||
@dp.message_handler(commands=["start"])
|
||||
@rate_limit(
|
||||
5, "start"
|
||||
) # this is not required but you can configure throttling manager for current handler using it
|
||||
@dp.message_handler(commands=['start'])
|
||||
@rate_limit(5, 'start') # this is not required but you can configure throttling manager for current handler using it
|
||||
async def cmd_test(message: types.Message):
|
||||
# You can use this command every 5 seconds
|
||||
await message.reply("Test passed! You can use this command every 5 seconds.")
|
||||
await message.reply('Test passed! You can use this command every 5 seconds.')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
# Setup middleware
|
||||
dp.middleware.setup(ThrottlingMiddleware())
|
||||
|
||||
# Start long-polling
|
||||
executor.start_polling(dp, loop=loop)
|
||||
executor.start_polling(dp)
|
||||
|
|
|
|||
|
|
@ -1,121 +1,97 @@
|
|||
import asyncio
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram import types
|
||||
from aiogram.dispatcher import Dispatcher
|
||||
from aiogram.types.message import ContentTypes
|
||||
from aiogram.utils import executor
|
||||
|
||||
BOT_TOKEN = "BOT TOKEN HERE"
|
||||
PAYMENTS_PROVIDER_TOKEN = "123456789:TEST:1234567890abcdef1234567890abcdef"
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
BOT_TOKEN = 'BOT_TOKEN_HERE'
|
||||
PAYMENTS_PROVIDER_TOKEN = '123456789:TEST:1422'
|
||||
|
||||
bot = Bot(BOT_TOKEN)
|
||||
dp = Dispatcher(bot, loop=loop)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
# Setup prices
|
||||
prices = [
|
||||
types.LabeledPrice(label="Working Time Machine", amount=5750),
|
||||
types.LabeledPrice(label="Gift wrapping", amount=500),
|
||||
types.LabeledPrice(label='Working Time Machine', amount=5750),
|
||||
types.LabeledPrice(label='Gift wrapping', amount=500),
|
||||
]
|
||||
|
||||
# Setup shipping options
|
||||
shipping_options = [
|
||||
types.ShippingOption(id="instant", title="WorldWide Teleporter").add(
|
||||
types.LabeledPrice("Teleporter", 1000)
|
||||
),
|
||||
types.ShippingOption(id="pickup", title="Local pickup").add(types.LabeledPrice("Pickup", 300)),
|
||||
types.ShippingOption(id='instant', title='WorldWide Teleporter').add(types.LabeledPrice('Teleporter', 1000)),
|
||||
types.ShippingOption(id='pickup', title='Local pickup').add(types.LabeledPrice('Pickup', 300)),
|
||||
]
|
||||
|
||||
|
||||
@dp.message_handler(commands=["start"])
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def cmd_start(message: types.Message):
|
||||
await bot.send_message(
|
||||
message.chat.id,
|
||||
await bot.send_message(message.chat.id,
|
||||
"Hello, I'm the demo merchant bot."
|
||||
" I can sell you a Time Machine."
|
||||
" Use /buy to order one, /terms for Terms and Conditions",
|
||||
)
|
||||
" Use /buy to order one, /terms for Terms and Conditions")
|
||||
|
||||
|
||||
@dp.message_handler(commands=["terms"])
|
||||
@dp.message_handler(commands=['terms'])
|
||||
async def cmd_terms(message: types.Message):
|
||||
await bot.send_message(
|
||||
message.chat.id,
|
||||
"Thank you for shopping with our demo bot. We hope you like your new time machine!\n"
|
||||
"1. If your time machine was not delivered on time, please rethink your concept of time"
|
||||
" and try again.\n"
|
||||
"2. If you find that your time machine is not working, kindly contact our future service"
|
||||
" workshops on Trappist-1e. They will be accessible anywhere between"
|
||||
" May 2075 and November 4000 C.E.\n"
|
||||
"3. If you would like a refund, kindly apply for one yesterday and we will have sent it"
|
||||
" to you immediately.",
|
||||
)
|
||||
await bot.send_message(message.chat.id,
|
||||
'Thank you for shopping with our demo bot. We hope you like your new time machine!\n'
|
||||
'1. If your time machine was not delivered on time, please rethink your concept of time'
|
||||
' and try again.\n'
|
||||
'2. If you find that your time machine is not working, kindly contact our future service'
|
||||
' workshops on Trappist-1e. They will be accessible anywhere between'
|
||||
' May 2075 and November 4000 C.E.\n'
|
||||
'3. If you would like a refund, kindly apply for one yesterday and we will have sent it'
|
||||
' to you immediately.')
|
||||
|
||||
|
||||
@dp.message_handler(commands=["buy"])
|
||||
@dp.message_handler(commands=['buy'])
|
||||
async def cmd_buy(message: types.Message):
|
||||
await bot.send_message(
|
||||
message.chat.id,
|
||||
await bot.send_message(message.chat.id,
|
||||
"Real cards won't work with me, no money will be debited from your account."
|
||||
" Use this test card number to pay for your Time Machine: `4242 4242 4242 4242`"
|
||||
"\n\nThis is your demo invoice:",
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
await bot.send_invoice(
|
||||
message.chat.id,
|
||||
title="Working Time Machine",
|
||||
description="Want to visit your great-great-great-grandparents?"
|
||||
" Make a fortune at the races?"
|
||||
" Shake hands with Hammurabi and take a stroll in the Hanging Gardens?"
|
||||
" Order our Working Time Machine today!",
|
||||
"\n\nThis is your demo invoice:", parse_mode='Markdown')
|
||||
await bot.send_invoice(message.chat.id, title='Working Time Machine',
|
||||
description='Want to visit your great-great-great-grandparents?'
|
||||
' Make a fortune at the races?'
|
||||
' Shake hands with Hammurabi and take a stroll in the Hanging Gardens?'
|
||||
' Order our Working Time Machine today!',
|
||||
provider_token=PAYMENTS_PROVIDER_TOKEN,
|
||||
currency="usd",
|
||||
photo_url="https://images.fineartamerica.com/images-medium-large/2-the-time-machine-dmitriy-khristenko.jpg",
|
||||
currency='usd',
|
||||
photo_url='https://telegra.ph/file/d08ff863531f10bf2ea4b.jpg',
|
||||
photo_height=512, # !=0/None or picture won't be shown
|
||||
photo_width=512,
|
||||
photo_size=512,
|
||||
is_flexible=True, # True If you need to set up Shipping Fee
|
||||
prices=prices,
|
||||
start_parameter="time-machine-example",
|
||||
payload="HAPPY FRIDAYS COUPON",
|
||||
)
|
||||
start_parameter='time-machine-example',
|
||||
payload='HAPPY FRIDAYS COUPON')
|
||||
|
||||
|
||||
@dp.shipping_query_handler(func=lambda query: True)
|
||||
@dp.shipping_query_handler(lambda query: True)
|
||||
async def shipping(shipping_query: types.ShippingQuery):
|
||||
await bot.answer_shipping_query(
|
||||
shipping_query.id,
|
||||
ok=True,
|
||||
shipping_options=shipping_options,
|
||||
error_message="Oh, seems like our Dog couriers are having a lunch right now."
|
||||
" Try again later!",
|
||||
)
|
||||
await bot.answer_shipping_query(shipping_query.id, ok=True, shipping_options=shipping_options,
|
||||
error_message='Oh, seems like our Dog couriers are having a lunch right now.'
|
||||
' Try again later!')
|
||||
|
||||
|
||||
@dp.pre_checkout_query_handler(func=lambda query: True)
|
||||
@dp.pre_checkout_query_handler(lambda query: True)
|
||||
async def checkout(pre_checkout_query: types.PreCheckoutQuery):
|
||||
await bot.answer_pre_checkout_query(
|
||||
pre_checkout_query.id,
|
||||
ok=True,
|
||||
await bot.answer_pre_checkout_query(pre_checkout_query.id, ok=True,
|
||||
error_message="Aliens tried to steal your card's CVV,"
|
||||
" but we successfully protected your credentials,"
|
||||
" try to pay again in a few minutes, we need a small rest.",
|
||||
)
|
||||
" try to pay again in a few minutes, we need a small rest.")
|
||||
|
||||
|
||||
@dp.message_handler(content_types=ContentTypes.SUCCESSFUL_PAYMENT)
|
||||
async def got_payment(message: types.Message):
|
||||
await bot.send_message(
|
||||
message.chat.id,
|
||||
"Hoooooray! Thanks for payment! We will proceed your order for `{} {}`"
|
||||
" as fast as possible! Stay in touch."
|
||||
"\n\nUse /buy again to get a Time Machine for your friend!".format(
|
||||
message.successful_payment.total_amount / 100, message.successful_payment.currency
|
||||
),
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
await bot.send_message(message.chat.id,
|
||||
'Hoooooray! Thanks for payment! We will proceed your order for `{} {}`'
|
||||
' as fast as possible! Stay in touch.'
|
||||
'\n\nUse /buy again to get a Time Machine for your friend!'.format(
|
||||
message.successful_payment.total_amount / 100, message.successful_payment.currency),
|
||||
parse_mode='Markdown')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
executor.start_polling(dp, loop=loop)
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import asyncio
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
|
|
@ -11,49 +10,52 @@ from aiogram.utils.executor import start_polling
|
|||
from aiogram.utils.markdown import bold, code, italic, text
|
||||
|
||||
# Configure bot here
|
||||
API_TOKEN = "BOT TOKEN HERE"
|
||||
PROXY_URL = "http://PROXY_URL" # Or 'socks5://...'
|
||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||
PROXY_URL = 'http://PROXY_URL' # Or 'socks5://host:port'
|
||||
|
||||
# If authentication is required in your proxy then uncomment next line and change login/password for it
|
||||
# NOTE: If authentication is required in your proxy then uncomment next line and change login/password for it
|
||||
# PROXY_AUTH = aiohttp.BasicAuth(login='login', password='password')
|
||||
# And add `proxy_auth=PROXY_AUTH` argument in line 25, like this:
|
||||
# >>> bot = Bot(token=API_TOKEN, loop=loop, proxy=PROXY_URL, proxy_auth=PROXY_AUTH)
|
||||
# And add `proxy_auth=PROXY_AUTH` argument in line 30, like this:
|
||||
# >>> bot = Bot(token=API_TOKEN, proxy=PROXY_URL, proxy_auth=PROXY_AUTH)
|
||||
# Also you can use Socks5 proxy but you need manually install aiohttp_socks package.
|
||||
|
||||
# Get my ip URL
|
||||
GET_IP_URL = "http://bot.whatismyipaddress.com/"
|
||||
GET_IP_URL = 'http://bot.whatismyipaddress.com/'
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
bot = Bot(token=API_TOKEN, loop=loop, proxy=PROXY_URL)
|
||||
bot = Bot(token=API_TOKEN, proxy=PROXY_URL)
|
||||
|
||||
# If auth is required:
|
||||
# bot = Bot(token=API_TOKEN, proxy=PROXY_URL, proxy_auth=PROXY_AUTH)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
async def fetch(url, proxy=None, proxy_auth=None):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, proxy=proxy, proxy_auth=proxy_auth) as response:
|
||||
async def fetch(url, session):
|
||||
async with session.get(url) as response:
|
||||
return await response.text()
|
||||
|
||||
|
||||
@dp.message_handler(commands=["start"])
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def cmd_start(message: types.Message):
|
||||
# fetching urls will take some time, so notify user that everything is OK
|
||||
await types.ChatActions.typing()
|
||||
|
||||
content = []
|
||||
|
||||
# Make request (without proxy)
|
||||
ip = await fetch(GET_IP_URL)
|
||||
content.append(text(":globe_showing_Americas:", bold("IP:"), code(ip)))
|
||||
async with aiohttp.ClientSession() as session:
|
||||
ip = await fetch(GET_IP_URL, session)
|
||||
content.append(text(':globe_showing_Americas:', bold('IP:'), code(ip)))
|
||||
# This line is formatted to '🌎 *IP:* `YOUR IP`'
|
||||
|
||||
# Make request through proxy
|
||||
ip = await fetch(GET_IP_URL, bot.proxy, bot.proxy_auth)
|
||||
content.append(text(":locked_with_key:", bold("IP:"), code(ip), italic("via proxy")))
|
||||
# Make request through bot's proxy
|
||||
ip = await fetch(GET_IP_URL, bot.session)
|
||||
content.append(text(':locked_with_key:', bold('IP:'), code(ip), italic('via proxy')))
|
||||
# This line is formatted to '🔐 *IP:* `YOUR IP` _via proxy_'
|
||||
|
||||
# Send content
|
||||
await bot.send_message(
|
||||
message.chat.id, emojize(text(*content, sep="\n")), parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
await bot.send_message(message.chat.id, emojize(text(*content, sep='\n')), parse_mode=ParseMode.MARKDOWN)
|
||||
|
||||
# In this example you can see emoji codes: ":globe_showing_Americas:" and ":locked_with_key:"
|
||||
# You can find full emoji cheat sheet at https://www.webpagefx.com/tools/emoji-cheat-sheet/
|
||||
|
|
@ -63,5 +65,5 @@ async def cmd_start(message: types.Message):
|
|||
# For example emojize('Moon face :new_moon_face:') is transformed to 'Moon face 🌚'
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_polling(dp, loop=loop, skip_updates=True)
|
||||
if __name__ == '__main__':
|
||||
start_polling(dp, skip_updates=True)
|
||||
|
|
|
|||
|
|
@ -2,16 +2,28 @@ from aiogram import Bot, types
|
|||
from aiogram.dispatcher import Dispatcher, filters
|
||||
from aiogram.utils import executor
|
||||
|
||||
bot = Bot(token="TOKEN")
|
||||
|
||||
bot = Bot(token='BOT_TOKEN_HERE', parse_mode=types.ParseMode.HTML)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
@dp.message_handler(filters.RegexpCommandsFilter(regexp_commands=["item_([0-9]*)"]))
|
||||
@dp.message_handler(filters.RegexpCommandsFilter(regexp_commands=['item_([0-9]*)']))
|
||||
async def send_welcome(message: types.Message, regexp_command):
|
||||
await message.reply(
|
||||
"You have requested an item with number: {}".format(regexp_command.group(1))
|
||||
await message.reply(f"You have requested an item with id <code>{regexp_command.group(1)}</code>")
|
||||
|
||||
|
||||
@dp.message_handler(commands='start')
|
||||
async def create_deeplink(message: types.Message):
|
||||
bot_user = await bot.me
|
||||
bot_username = bot_user.username
|
||||
deeplink = f'https://t.me/{bot_username}?start=item_12345'
|
||||
text = (
|
||||
f'Either send a command /item_1234 or follow this link {deeplink} and then click start\n'
|
||||
'It also can be hidden in a inline button\n\n'
|
||||
'Or just send <code>/start item_123</code>'
|
||||
)
|
||||
await message.reply(text, disable_web_page_preview=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
executor.start_polling(dp)
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
|
|||
67
examples/regular_keyboard_example.py
Normal file
67
examples/regular_keyboard_example.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
"""
|
||||
This bot is created for the demonstration of a usage of regular keyboards.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, Dispatcher, executor, types
|
||||
|
||||
|
||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# Initialize bot and dispatcher
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
@dp.message_handler(commands='start')
|
||||
async def start_cmd_handler(message: types.Message):
|
||||
keyboard_markup = types.ReplyKeyboardMarkup(row_width=3)
|
||||
# default row_width is 3, so here we can omit it actually
|
||||
# kept for clearness
|
||||
|
||||
btns_text = ('Yes!', 'No!')
|
||||
keyboard_markup.row(*(types.KeyboardButton(text) for text in btns_text))
|
||||
# adds buttons as a new row to the existing keyboard
|
||||
# the behaviour doesn't depend on row_width attribute
|
||||
|
||||
more_btns_text = (
|
||||
"I don't know",
|
||||
"Who am i?",
|
||||
"Where am i?",
|
||||
"Who is there?",
|
||||
)
|
||||
keyboard_markup.add(*(types.KeyboardButton(text) for text in more_btns_text))
|
||||
# adds buttons. New rows are formed according to row_width parameter
|
||||
|
||||
await message.reply("Hi!\nDo you like aiogram?", reply_markup=keyboard_markup)
|
||||
|
||||
|
||||
@dp.message_handler()
|
||||
async def all_msg_handler(message: types.Message):
|
||||
# pressing of a KeyboardButton is the same as sending the regular message with the same text
|
||||
# so, to handle the responses from the keyboard, we need to use a message_handler
|
||||
# in real bot, it's better to define message_handler(text="...") for each button
|
||||
# but here for the simplicity only one handler is defined
|
||||
|
||||
button_text = message.text
|
||||
logger.debug('The answer is %r', button_text) # print the text we've got
|
||||
|
||||
if button_text == 'Yes!':
|
||||
reply_text = "That's great"
|
||||
elif button_text == 'No!':
|
||||
reply_text = "Oh no! Why?"
|
||||
else:
|
||||
reply_text = "Keep calm...Everything is fine"
|
||||
|
||||
await message.reply(reply_text, reply_markup=types.ReplyKeyboardRemove())
|
||||
# with message, we send types.ReplyKeyboardRemove() to hide the keyboard
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
53
examples/text_filter_example.py
Normal file
53
examples/text_filter_example.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
"""
|
||||
This is a bot to show the usage of the builtin Text filter
|
||||
Instead of a list, a single element can be passed to any filter, it will be treated as list with an element
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, Dispatcher, executor, types
|
||||
|
||||
|
||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Initialize bot and dispatcher
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
# if the text from user in the list
|
||||
@dp.message_handler(text=['text1', 'text2'])
|
||||
async def text_in_handler(message: types.Message):
|
||||
await message.answer("The message text equals to one of in the list!")
|
||||
|
||||
|
||||
# if the text contains any string
|
||||
@dp.message_handler(text_contains='example1')
|
||||
@dp.message_handler(text_contains='example2')
|
||||
async def text_contains_any_handler(message: types.Message):
|
||||
await message.answer("The message text contains any of strings")
|
||||
|
||||
|
||||
# if the text contains all the strings from the list
|
||||
@dp.message_handler(text_contains=['str1', 'str2'])
|
||||
async def text_contains_all_handler(message: types.Message):
|
||||
await message.answer("The message text contains all strings from the list")
|
||||
|
||||
|
||||
# if the text starts with any string from the list
|
||||
@dp.message_handler(text_startswith=['prefix1', 'prefix2'])
|
||||
async def text_startswith_handler(message: types.Message):
|
||||
await message.answer("The message text starts with any of prefixes")
|
||||
|
||||
|
||||
# if the text ends with any string from the list
|
||||
@dp.message_handler(text_endswith=['postfix1', 'postfix2'])
|
||||
async def text_endswith_handler(message: types.Message):
|
||||
await message.answer("The message text ends with any of postfixes")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
"""
|
||||
Example for throttling manager.
|
||||
|
||||
You can use that for flood controlling.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, types
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
from aiogram.dispatcher import Dispatcher
|
||||
from aiogram.utils.exceptions import Throttled
|
||||
from aiogram.utils.executor import start_polling
|
||||
|
||||
API_TOKEN = "BOT TOKEN HERE"
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
bot = Bot(token=API_TOKEN, loop=loop)
|
||||
|
||||
# Throttling manager does not work without Leaky Bucket.
|
||||
# Then need to use storages. For example use simple in-memory storage.
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot, storage=storage)
|
||||
|
||||
|
||||
@dp.message_handler(commands=["start", "help"])
|
||||
async def send_welcome(message: types.Message):
|
||||
try:
|
||||
# Execute throttling manager with rate-limit equal to 2 seconds for key "start"
|
||||
await dp.throttle("start", rate=2)
|
||||
except Throttled:
|
||||
# If request is throttled, the `Throttled` exception will be raised
|
||||
await message.reply("Too many requests!")
|
||||
else:
|
||||
# Otherwise do something
|
||||
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_polling(dp, loop=loop, skip_updates=True)
|
||||
71
examples/throttling_example.py
Normal file
71
examples/throttling_example.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
"""
|
||||
Example for throttling manager.
|
||||
|
||||
You can use that for flood controlling.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, types
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
from aiogram.dispatcher import Dispatcher
|
||||
from aiogram.utils.exceptions import Throttled
|
||||
from aiogram.utils.executor import start_polling
|
||||
|
||||
|
||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
|
||||
# Throttling manager does not work without Leaky Bucket.
|
||||
# You need to use a storage. For example use simple in-memory storage.
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot, storage=storage)
|
||||
|
||||
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(message: types.Message):
|
||||
try:
|
||||
# Execute throttling manager with rate-limit equal to 2 seconds for key "start"
|
||||
await dp.throttle('start', rate=2)
|
||||
except Throttled:
|
||||
# If request is throttled, the `Throttled` exception will be raised
|
||||
await message.reply('Too many requests!')
|
||||
else:
|
||||
# Otherwise do something
|
||||
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
|
||||
|
||||
|
||||
@dp.message_handler(commands=['hi'])
|
||||
@dp.throttled(lambda msg, loop, *args, **kwargs: loop.create_task(bot.send_message(msg.from_user.id, "Throttled")),
|
||||
rate=5)
|
||||
# loop is added to the function to run coroutines from it
|
||||
async def say_hi(message: types.Message):
|
||||
await message.answer("Hi")
|
||||
|
||||
|
||||
# the on_throttled object can be either a regular function or coroutine
|
||||
async def hello_throttled(*args, **kwargs):
|
||||
# args will be the same as in the original handler
|
||||
# kwargs will be updated with parameters given to .throttled (rate, key, user_id, chat_id)
|
||||
print(f"hello_throttled was called with args={args} and kwargs={kwargs}")
|
||||
message = args[0] # as message was the first argument in the original handler
|
||||
await message.answer("Throttled")
|
||||
|
||||
|
||||
@dp.message_handler(commands=['hello'])
|
||||
@dp.throttled(hello_throttled, rate=4)
|
||||
async def say_hello(message: types.Message):
|
||||
await message.answer("Hello!")
|
||||
|
||||
|
||||
@dp.message_handler(commands=['help'])
|
||||
@dp.throttled(rate=5)
|
||||
# nothing will happen if the handler will be throttled
|
||||
async def help_handler(message: types.Message):
|
||||
await message.answer('Help!')
|
||||
|
||||
if __name__ == '__main__':
|
||||
start_polling(dp, skip_updates=True)
|
||||
|
|
@ -1,199 +1,66 @@
|
|||
"""
|
||||
Example outdated
|
||||
"""
|
||||
import logging
|
||||
|
||||
import asyncio
|
||||
import ssl
|
||||
import sys
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
import aiogram
|
||||
from aiogram import Bot, types
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.dispatcher import Dispatcher
|
||||
from aiogram.dispatcher.webhook import get_new_configured_app, SendMessage
|
||||
from aiogram.types import ChatType, ParseMode, ContentTypes
|
||||
from aiogram.utils.markdown import hbold, bold, text, link
|
||||
from aiogram.dispatcher.webhook import SendMessage
|
||||
from aiogram.utils.executor import start_webhook
|
||||
|
||||
TOKEN = "BOT TOKEN HERE"
|
||||
|
||||
WEBHOOK_HOST = "example.com" # Domain name or IP addres which your bot is located.
|
||||
WEBHOOK_PORT = 443 # Telegram Bot API allows only for usage next ports: 443, 80, 88 or 8443
|
||||
WEBHOOK_URL_PATH = "/webhook" # Part of URL
|
||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||
|
||||
# This options needed if you use self-signed SSL certificate
|
||||
# Instructions: https://core.telegram.org/bots/self-signed
|
||||
WEBHOOK_SSL_CERT = "./webhook_cert.pem" # Path to the ssl certificate
|
||||
WEBHOOK_SSL_PRIV = "./webhook_pkey.pem" # Path to the ssl private key
|
||||
# webhook settings
|
||||
WEBHOOK_HOST = 'https://your.domain'
|
||||
WEBHOOK_PATH = '/path/to/api'
|
||||
WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
|
||||
|
||||
WEBHOOK_URL = f"https://{WEBHOOK_HOST}:{WEBHOOK_PORT}{WEBHOOK_URL_PATH}"
|
||||
|
||||
# Web app settings:
|
||||
# Use LAN address to listen webhooks
|
||||
# User any available port in range from 1024 to 49151 if you're using proxy, or WEBHOOK_PORT if you're using direct webhook handling
|
||||
WEBAPP_HOST = "localhost"
|
||||
# webserver settings
|
||||
WEBAPP_HOST = 'localhost' # or ip
|
||||
WEBAPP_PORT = 3001
|
||||
|
||||
BAD_CONTENT = (
|
||||
ContentTypes.PHOTO & ContentTypes.DOCUMENT & ContentTypes.STICKER & ContentTypes.AUDIO
|
||||
)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
bot = Bot(TOKEN, loop=loop)
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot, storage=storage)
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
|
||||
async def cmd_start(message: types.Message):
|
||||
# Yep. aiogram allows to respond into webhook.
|
||||
# https://core.telegram.org/bots/api#making-requests-when-getting-updates
|
||||
return SendMessage(
|
||||
chat_id=message.chat.id, text="Hi from webhook!", reply_to_message_id=message.message_id
|
||||
)
|
||||
@dp.message_handler()
|
||||
async def echo(message: types.Message):
|
||||
# Regular request
|
||||
# await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
# or reply INTO webhook
|
||||
return SendMessage(message.chat.id, message.text)
|
||||
|
||||
|
||||
async def cmd_about(message: types.Message):
|
||||
# In this function markdown utils are userd for formatting message text
|
||||
return SendMessage(
|
||||
message.chat.id,
|
||||
text(
|
||||
bold("Hi! I'm just a simple telegram bot."),
|
||||
"",
|
||||
text("I'm powered by", bold("Python", Version(*sys.version_info[:]))),
|
||||
text(
|
||||
"With",
|
||||
link(text("aiogram", aiogram.VERSION), "https://github.com/aiogram/aiogram"),
|
||||
),
|
||||
sep="\n",
|
||||
),
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
)
|
||||
async def on_startup(dp):
|
||||
await bot.set_webhook(WEBHOOK_URL)
|
||||
# insert code here to run it after start
|
||||
|
||||
|
||||
async def cancel(message: types.Message):
|
||||
# Get current state context
|
||||
state = dp.current_state(chat=message.chat.id, user=message.from_user.id)
|
||||
async def on_shutdown(dp):
|
||||
logging.warning('Shutting down..')
|
||||
|
||||
# If current user in any state - cancel it.
|
||||
if await state.get_state() is not None:
|
||||
await state.set_state(state=None)
|
||||
return SendMessage(message.chat.id, "Current action is canceled.")
|
||||
# Otherwise do nothing
|
||||
# insert code here to run it before shutdown
|
||||
|
||||
|
||||
async def unknown(message: types.Message):
|
||||
"""
|
||||
Handler for unknown messages.
|
||||
"""
|
||||
return SendMessage(
|
||||
message.chat.id,
|
||||
f"I don't know what to do with content type `{message.content_type()}`. Sorry :c",
|
||||
)
|
||||
|
||||
|
||||
async def cmd_id(message: types.Message):
|
||||
"""
|
||||
Return info about user.
|
||||
"""
|
||||
if message.reply_to_message:
|
||||
target = message.reply_to_message.from_user
|
||||
chat = message.chat
|
||||
elif message.forward_from and message.chat.type == ChatType.PRIVATE:
|
||||
target = message.forward_from
|
||||
chat = message.forward_from or message.chat
|
||||
else:
|
||||
target = message.from_user
|
||||
chat = message.chat
|
||||
|
||||
result_msg = [hbold("Info about user:"), f"First name: {target.first_name}"]
|
||||
if target.last_name:
|
||||
result_msg.append(f"Last name: {target.last_name}")
|
||||
if target.username:
|
||||
result_msg.append(f"Username: {target.mention}")
|
||||
result_msg.append(f"User ID: {target.id}")
|
||||
|
||||
result_msg.extend([hbold("Chat:"), f"Type: {chat.type}", f"Chat ID: {chat.id}"])
|
||||
if chat.type != ChatType.PRIVATE:
|
||||
result_msg.append(f"Title: {chat.title}")
|
||||
else:
|
||||
result_msg.append(f"Title: {chat.full_name}")
|
||||
return SendMessage(
|
||||
message.chat.id,
|
||||
"\n".join(result_msg),
|
||||
reply_to_message_id=message.message_id,
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
|
||||
|
||||
async def on_startup(app):
|
||||
# Demonstrate one of the available methods for registering handlers
|
||||
# This command available only in main state (state=None)
|
||||
dp.register_message_handler(cmd_start, commands=["start"])
|
||||
|
||||
# This handler is available in all states at any time.
|
||||
dp.register_message_handler(cmd_about, commands=["help", "about"], state="*")
|
||||
dp.register_message_handler(
|
||||
unknown,
|
||||
content_types=BAD_CONTENT,
|
||||
func=lambda message: message.chat.type == ChatType.PRIVATE,
|
||||
)
|
||||
|
||||
# You are able to register one function handler for multiple conditions
|
||||
dp.register_message_handler(cancel, commands=["cancel"], state="*")
|
||||
dp.register_message_handler(
|
||||
cancel, func=lambda message: message.text.lower().strip() in ["cancel"], state="*"
|
||||
)
|
||||
|
||||
dp.register_message_handler(cmd_id, commands=["id"], state="*")
|
||||
dp.register_message_handler(
|
||||
cmd_id,
|
||||
func=lambda message: message.forward_from
|
||||
or message.reply_to_message
|
||||
and message.chat.type == ChatType.PRIVATE,
|
||||
state="*",
|
||||
)
|
||||
|
||||
# Get current webhook status
|
||||
webhook = await bot.get_webhook_info()
|
||||
|
||||
# If URL is bad
|
||||
if webhook.url != WEBHOOK_URL:
|
||||
# If URL doesnt match current - remove webhook
|
||||
if not webhook.url:
|
||||
# Remove webhook (not acceptable in some cases)
|
||||
await bot.delete_webhook()
|
||||
|
||||
# Set new URL for webhook
|
||||
await bot.set_webhook(WEBHOOK_URL, certificate=open(WEBHOOK_SSL_CERT, "rb"))
|
||||
# If you want to use free certificate signed by LetsEncrypt you need to set only URL without sending certificate.
|
||||
|
||||
|
||||
async def on_shutdown(app):
|
||||
"""
|
||||
Graceful shutdown. This method is recommended by aiohttp docs.
|
||||
"""
|
||||
# Remove webhook.
|
||||
await bot.delete_webhook()
|
||||
|
||||
# Close Redis connection.
|
||||
# Close DB connection (if used)
|
||||
await dp.storage.close()
|
||||
await dp.storage.wait_closed()
|
||||
|
||||
logging.warning('Bye!')
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Get instance of :class:`aiohttp.web.Application` with configured router.
|
||||
app = get_new_configured_app(dispatcher=dp, path=WEBHOOK_URL_PATH)
|
||||
|
||||
# Setup event handlers.
|
||||
app.on_startup.append(on_startup)
|
||||
app.on_shutdown.append(on_shutdown)
|
||||
|
||||
# Generate SSL context
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||
context.load_cert_chain(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV)
|
||||
|
||||
# Start web-application.
|
||||
web.run_app(app, host=WEBAPP_HOST, port=WEBAPP_PORT, ssl_context=context)
|
||||
# Note:
|
||||
# If you start your bot using nginx or Apache web server, SSL context is not required.
|
||||
# Otherwise you need to set `ssl_context` parameter.
|
||||
if __name__ == '__main__':
|
||||
start_webhook(
|
||||
dispatcher=dp,
|
||||
webhook_path=WEBHOOK_PATH,
|
||||
on_startup=on_startup,
|
||||
on_shutdown=on_shutdown,
|
||||
skip_updates=True,
|
||||
host=WEBAPP_HOST,
|
||||
port=WEBAPP_PORT,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, types
|
||||
from aiogram.dispatcher import Dispatcher
|
||||
from aiogram.utils.executor import start_webhook
|
||||
|
||||
API_TOKEN = "BOT TOKEN HERE"
|
||||
|
||||
# webhook settings
|
||||
WEBHOOK_HOST = "https://your.domain"
|
||||
WEBHOOK_PATH = "/path/to/api"
|
||||
WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
|
||||
|
||||
# webserver settings
|
||||
WEBAPP_HOST = "localhost" # or ip
|
||||
WEBAPP_PORT = 3001
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
bot = Bot(token=API_TOKEN, loop=loop)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
@dp.message_handler()
|
||||
async def echo(message: types.Message):
|
||||
await bot.send_message(message.chat.id, message.text)
|
||||
|
||||
|
||||
async def on_startup(dp):
|
||||
await bot.set_webhook(WEBHOOK_URL)
|
||||
# insert code here to run it after start
|
||||
|
||||
|
||||
async def on_shutdown(dp):
|
||||
# insert code here to run it before shutdown
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_webhook(
|
||||
dispatcher=dp,
|
||||
webhook_path=WEBHOOK_PATH,
|
||||
on_startup=on_startup,
|
||||
on_shutdown=on_shutdown,
|
||||
skip_updates=True,
|
||||
host=WEBAPP_HOST,
|
||||
port=WEBAPP_PORT,
|
||||
)
|
||||
176
examples/webhook_example_old.py
Normal file
176
examples/webhook_example_old.py
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
"""
|
||||
Example outdated
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import ssl
|
||||
import sys
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
import aiogram
|
||||
from aiogram import Bot, types
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
from aiogram.dispatcher import Dispatcher
|
||||
from aiogram.dispatcher.webhook import get_new_configured_app, SendMessage
|
||||
from aiogram.types import ChatType, ParseMode, ContentTypes
|
||||
from aiogram.utils.markdown import hbold, bold, text, link
|
||||
|
||||
TOKEN = 'BOT TOKEN HERE'
|
||||
|
||||
WEBHOOK_HOST = 'example.com' # Domain name or IP addres which your bot is located.
|
||||
WEBHOOK_PORT = 443 # Telegram Bot API allows only for usage next ports: 443, 80, 88 or 8443
|
||||
WEBHOOK_URL_PATH = '/webhook' # Part of URL
|
||||
|
||||
# This options needed if you use self-signed SSL certificate
|
||||
# Instructions: https://core.telegram.org/bots/self-signed
|
||||
WEBHOOK_SSL_CERT = './webhook_cert.pem' # Path to the ssl certificate
|
||||
WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
|
||||
|
||||
WEBHOOK_URL = f"https://{WEBHOOK_HOST}:{WEBHOOK_PORT}{WEBHOOK_URL_PATH}"
|
||||
|
||||
# Web app settings:
|
||||
# Use LAN address to listen webhooks
|
||||
# User any available port in range from 1024 to 49151 if you're using proxy, or WEBHOOK_PORT if you're using direct webhook handling
|
||||
WEBAPP_HOST = 'localhost'
|
||||
WEBAPP_PORT = 3001
|
||||
|
||||
BAD_CONTENT = ContentTypes.PHOTO & ContentTypes.DOCUMENT & ContentTypes.STICKER & ContentTypes.AUDIO
|
||||
|
||||
bot = Bot(TOKEN)
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot, storage=storage)
|
||||
|
||||
|
||||
async def cmd_start(message: types.Message):
|
||||
# Yep. aiogram allows to respond into webhook.
|
||||
# https://core.telegram.org/bots/api#making-requests-when-getting-updates
|
||||
return SendMessage(chat_id=message.chat.id, text='Hi from webhook!',
|
||||
reply_to_message_id=message.message_id)
|
||||
|
||||
|
||||
async def cmd_about(message: types.Message):
|
||||
# In this function markdown utils are userd for formatting message text
|
||||
return SendMessage(message.chat.id, text(
|
||||
bold('Hi! I\'m just a simple telegram bot.'),
|
||||
'',
|
||||
text('I\'m powered by', bold('Python', Version(*sys.version_info[:]))),
|
||||
text('With', link(text('aiogram', aiogram.VERSION), 'https://github.com/aiogram/aiogram')),
|
||||
sep='\n'
|
||||
), parse_mode=ParseMode.MARKDOWN)
|
||||
|
||||
|
||||
async def cancel(message: types.Message):
|
||||
# Get current state context
|
||||
state = dp.current_state(chat=message.chat.id, user=message.from_user.id)
|
||||
|
||||
# If current user in any state - cancel it.
|
||||
if await state.get_state() is not None:
|
||||
await state.set_state(state=None)
|
||||
return SendMessage(message.chat.id, 'Current action is canceled.')
|
||||
# Otherwise do nothing
|
||||
|
||||
|
||||
async def unknown(message: types.Message):
|
||||
"""
|
||||
Handler for unknown messages.
|
||||
"""
|
||||
return SendMessage(message.chat.id,
|
||||
f"I don\'t know what to do with content type `{message.content_type()}`. Sorry :c")
|
||||
|
||||
|
||||
async def cmd_id(message: types.Message):
|
||||
"""
|
||||
Return info about user.
|
||||
"""
|
||||
if message.reply_to_message:
|
||||
target = message.reply_to_message.from_user
|
||||
chat = message.chat
|
||||
elif message.forward_from and message.chat.type == ChatType.PRIVATE:
|
||||
target = message.forward_from
|
||||
chat = message.forward_from or message.chat
|
||||
else:
|
||||
target = message.from_user
|
||||
chat = message.chat
|
||||
|
||||
result_msg = [hbold('Info about user:'),
|
||||
f"First name: {target.first_name}"]
|
||||
if target.last_name:
|
||||
result_msg.append(f"Last name: {target.last_name}")
|
||||
if target.username:
|
||||
result_msg.append(f"Username: {target.mention}")
|
||||
result_msg.append(f"User ID: {target.id}")
|
||||
|
||||
result_msg.extend([hbold('Chat:'),
|
||||
f"Type: {chat.type}",
|
||||
f"Chat ID: {chat.id}"])
|
||||
if chat.type != ChatType.PRIVATE:
|
||||
result_msg.append(f"Title: {chat.title}")
|
||||
else:
|
||||
result_msg.append(f"Title: {chat.full_name}")
|
||||
return SendMessage(message.chat.id, '\n'.join(result_msg), reply_to_message_id=message.message_id,
|
||||
parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
async def on_startup(app):
|
||||
# Demonstrate one of the available methods for registering handlers
|
||||
# This command available only in main state (state=None)
|
||||
dp.register_message_handler(cmd_start, commands=['start'])
|
||||
|
||||
# This handler is available in all states at any time.
|
||||
dp.register_message_handler(cmd_about, commands=['help', 'about'], state='*')
|
||||
dp.register_message_handler(unknown, content_types=BAD_CONTENT,
|
||||
func=lambda message: message.chat.type == ChatType.PRIVATE)
|
||||
|
||||
# You are able to register one function handler for multiple conditions
|
||||
dp.register_message_handler(cancel, commands=['cancel'], state='*')
|
||||
dp.register_message_handler(cancel, func=lambda message: message.text.lower().strip() in ['cancel'], state='*')
|
||||
|
||||
dp.register_message_handler(cmd_id, commands=['id'], state='*')
|
||||
dp.register_message_handler(cmd_id, func=lambda message: message.forward_from or
|
||||
message.reply_to_message and
|
||||
message.chat.type == ChatType.PRIVATE, state='*')
|
||||
|
||||
# Get current webhook status
|
||||
webhook = await bot.get_webhook_info()
|
||||
|
||||
# If URL is bad
|
||||
if webhook.url != WEBHOOK_URL:
|
||||
# If URL doesnt match current - remove webhook
|
||||
if not webhook.url:
|
||||
await bot.delete_webhook()
|
||||
|
||||
# Set new URL for webhook
|
||||
await bot.set_webhook(WEBHOOK_URL, certificate=open(WEBHOOK_SSL_CERT, 'rb'))
|
||||
# If you want to use free certificate signed by LetsEncrypt you need to set only URL without sending certificate.
|
||||
|
||||
|
||||
async def on_shutdown(app):
|
||||
"""
|
||||
Graceful shutdown. This method is recommended by aiohttp docs.
|
||||
"""
|
||||
# Remove webhook.
|
||||
await bot.delete_webhook()
|
||||
|
||||
# Close Redis connection.
|
||||
await dp.storage.close()
|
||||
await dp.storage.wait_closed()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Get instance of :class:`aiohttp.web.Application` with configured router.
|
||||
app = get_new_configured_app(dispatcher=dp, path=WEBHOOK_URL_PATH)
|
||||
|
||||
# Setup event handlers.
|
||||
app.on_startup.append(on_startup)
|
||||
app.on_shutdown.append(on_shutdown)
|
||||
|
||||
# Generate SSL context
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||
context.load_cert_chain(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV)
|
||||
|
||||
# Start web-application.
|
||||
web.run_app(app, host=WEBAPP_HOST, port=WEBAPP_PORT, ssl_context=context)
|
||||
# Note:
|
||||
# If you start your bot using nginx or Apache web server, SSL context is not required.
|
||||
# Otherwise you need to set `ssl_context` parameter.
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
aiohttp>=3.5.4
|
||||
aiohttp>=3.5.4,<4.0.0
|
||||
Babel>=2.6.0
|
||||
certifi>=2019.3.9
|
||||
|
|
|
|||
48
setup.py
48
setup.py
|
|
@ -15,9 +15,7 @@ WORK_DIR = pathlib.Path(__file__).parent
|
|||
# Check python version
|
||||
MINIMAL_PY_VERSION = (3, 7)
|
||||
if sys.version_info < MINIMAL_PY_VERSION:
|
||||
raise RuntimeError(
|
||||
"aiogram works only with Python {}+".format(".".join(map(str, MINIMAL_PY_VERSION)))
|
||||
)
|
||||
raise RuntimeError('aiogram works only with Python {}+'.format('.'.join(map(str, MINIMAL_PY_VERSION))))
|
||||
|
||||
|
||||
def get_version():
|
||||
|
|
@ -26,11 +24,11 @@ def get_version():
|
|||
|
||||
:return: str
|
||||
"""
|
||||
txt = (WORK_DIR / "aiogram" / "__init__.py").read_text("utf-8")
|
||||
txt = (WORK_DIR / 'aiogram' / '__init__.py').read_text('utf-8')
|
||||
try:
|
||||
return re.findall(r"^__version__ = '([^']+)'\r?$", txt, re.M)[0]
|
||||
except IndexError:
|
||||
raise RuntimeError("Unable to determine version.")
|
||||
raise RuntimeError('Unable to determine version.')
|
||||
|
||||
|
||||
def get_description():
|
||||
|
|
@ -40,7 +38,7 @@ def get_description():
|
|||
:return: description
|
||||
:rtype: str
|
||||
"""
|
||||
with open("README.rst", "r", encoding="utf-8") as f:
|
||||
with open('README.rst', 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
|
|
@ -52,36 +50,36 @@ def get_requirements(filename=None):
|
|||
:rtype: list
|
||||
"""
|
||||
if filename is None:
|
||||
filename = "requirements.txt"
|
||||
filename = 'requirements.txt'
|
||||
|
||||
file = WORK_DIR / filename
|
||||
|
||||
install_reqs = parse_requirements(str(file), session="hack")
|
||||
install_reqs = parse_requirements(str(file), session='hack')
|
||||
return [str(ir.req) for ir in install_reqs]
|
||||
|
||||
|
||||
setup(
|
||||
name="aiogram",
|
||||
name='aiogram',
|
||||
version=get_version(),
|
||||
packages=find_packages(exclude=("tests", "tests.*", "examples.*", "docs", "generator")),
|
||||
url="https://github.com/aiogram/aiogram",
|
||||
license="MIT",
|
||||
author="Alex Root Junior",
|
||||
requires_python=">=3.7",
|
||||
author_email="jroot.junior@gmail.com",
|
||||
description="Is a pretty simple and fully asynchronous library for Telegram Bot API",
|
||||
packages=find_packages(exclude=('tests', 'tests.*', 'examples.*', 'docs',)),
|
||||
url='https://github.com/aiogram/aiogram',
|
||||
license='MIT',
|
||||
author='Alex Root Junior',
|
||||
requires_python='>=3.7',
|
||||
author_email='jroot.junior@gmail.com',
|
||||
description='Is a pretty simple and fully asynchronous framework for Telegram Bot API',
|
||||
long_description=get_description(),
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Console",
|
||||
"Framework :: AsyncIO",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: System Administrators",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Console',
|
||||
'Framework :: AsyncIO',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: System Administrators',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Topic :: Software Development :: Libraries :: Application Frameworks',
|
||||
],
|
||||
install_requires=get_requirements(),
|
||||
package_data={"": ["requirements.txt"]},
|
||||
package_data={'': ['requirements.txt']},
|
||||
include_package_data=False,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import pytest
|
|||
|
||||
from aiogram import Bot, types
|
||||
|
||||
TOKEN = "123456789:AABBCCDDEEFFaabbccddeeff-1234567890"
|
||||
TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff-1234567890'
|
||||
|
||||
|
||||
class FakeTelegram(aresponses.ResponsesMockServer):
|
||||
|
|
@ -13,25 +13,23 @@ class FakeTelegram(aresponses.ResponsesMockServer):
|
|||
|
||||
async def __aenter__(self):
|
||||
await super().__aenter__()
|
||||
_response = self.Response(text=self._body, headers=self._headers, status=200, reason="OK")
|
||||
_response = self.Response(text=self._body, headers=self._headers, status=200, reason='OK')
|
||||
self.add(self.ANY, response=_response)
|
||||
|
||||
@staticmethod
|
||||
def parse_data(message_dict):
|
||||
import json
|
||||
|
||||
_body = '{"ok":true,"result":' + json.dumps(message_dict) + "}"
|
||||
_headers = {
|
||||
"Server": "nginx/1.12.2",
|
||||
"Date": "Tue, 03 Apr 2018 16:59:54 GMT",
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": str(len(_body)),
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||||
"Access-Control-Expose-Headers": "Content-Length,Content-Type,Date,Server,Connection",
|
||||
"Strict-Transport-Security": "max-age=31536000; includeSubdomains",
|
||||
}
|
||||
_body = '{"ok":true,"result":' + json.dumps(message_dict) + '}'
|
||||
_headers = {'Server': 'nginx/1.12.2',
|
||||
'Date': 'Tue, 03 Apr 2018 16:59:54 GMT',
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': str(len(_body)),
|
||||
'Connection': 'keep-alive',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Expose-Headers': 'Content-Length,Content-Type,Date,Server,Connection',
|
||||
'Strict-Transport-Security': 'max-age=31536000; includeSubdomains'}
|
||||
return _body, _headers
|
||||
|
||||
|
||||
|
|
@ -48,7 +46,6 @@ async def bot(event_loop):
|
|||
async def test_get_me(bot: Bot, event_loop):
|
||||
""" getMe method test """
|
||||
from .types.dataset import USER
|
||||
|
||||
user = types.User(**USER)
|
||||
|
||||
async with FakeTelegram(message_dict=USER, loop=event_loop):
|
||||
|
|
@ -60,7 +57,6 @@ async def test_get_me(bot: Bot, event_loop):
|
|||
async def test_send_message(bot: Bot, event_loop):
|
||||
""" sendMessage method test """
|
||||
from .types.dataset import MESSAGE
|
||||
|
||||
msg = types.Message(**MESSAGE)
|
||||
|
||||
async with FakeTelegram(message_dict=MESSAGE, loop=event_loop):
|
||||
|
|
@ -72,15 +68,11 @@ async def test_send_message(bot: Bot, event_loop):
|
|||
async def test_forward_message(bot: Bot, event_loop):
|
||||
""" forwardMessage method test """
|
||||
from .types.dataset import FORWARDED_MESSAGE
|
||||
|
||||
msg = types.Message(**FORWARDED_MESSAGE)
|
||||
|
||||
async with FakeTelegram(message_dict=FORWARDED_MESSAGE, loop=event_loop):
|
||||
result = await bot.forward_message(
|
||||
chat_id=msg.chat.id,
|
||||
from_chat_id=msg.forward_from_chat.id,
|
||||
message_id=msg.forward_from_message_id,
|
||||
)
|
||||
result = await bot.forward_message(chat_id=msg.chat.id, from_chat_id=msg.forward_from_chat.id,
|
||||
message_id=msg.forward_from_message_id)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -88,18 +80,12 @@ async def test_forward_message(bot: Bot, event_loop):
|
|||
async def test_send_photo(bot: Bot, event_loop):
|
||||
""" sendPhoto method test with file_id """
|
||||
from .types.dataset import MESSAGE_WITH_PHOTO, PHOTO
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_PHOTO)
|
||||
photo = types.PhotoSize(**PHOTO)
|
||||
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_PHOTO, loop=event_loop):
|
||||
result = await bot.send_photo(
|
||||
msg.chat.id,
|
||||
photo=photo.file_id,
|
||||
caption=msg.caption,
|
||||
parse_mode=types.ParseMode.HTML,
|
||||
disable_notification=False,
|
||||
)
|
||||
result = await bot.send_photo(msg.chat.id, photo=photo.file_id, caption=msg.caption,
|
||||
parse_mode=types.ParseMode.HTML, disable_notification=False)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -107,20 +93,12 @@ async def test_send_photo(bot: Bot, event_loop):
|
|||
async def test_send_audio(bot: Bot, event_loop):
|
||||
""" sendAudio method test with file_id """
|
||||
from .types.dataset import MESSAGE_WITH_AUDIO
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_AUDIO)
|
||||
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_AUDIO, loop=event_loop):
|
||||
result = await bot.send_audio(
|
||||
chat_id=msg.chat.id,
|
||||
audio=msg.audio.file_id,
|
||||
caption=msg.caption,
|
||||
parse_mode=types.ParseMode.HTML,
|
||||
duration=msg.audio.duration,
|
||||
performer=msg.audio.performer,
|
||||
title=msg.audio.title,
|
||||
disable_notification=False,
|
||||
)
|
||||
result = await bot.send_audio(chat_id=msg.chat.id, audio=msg.audio.file_id, caption=msg.caption,
|
||||
parse_mode=types.ParseMode.HTML, duration=msg.audio.duration,
|
||||
performer=msg.audio.performer, title=msg.audio.title, disable_notification=False)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -128,17 +106,11 @@ async def test_send_audio(bot: Bot, event_loop):
|
|||
async def test_send_document(bot: Bot, event_loop):
|
||||
""" sendDocument method test with file_id """
|
||||
from .types.dataset import MESSAGE_WITH_DOCUMENT
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_DOCUMENT)
|
||||
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_DOCUMENT, loop=event_loop):
|
||||
result = await bot.send_document(
|
||||
chat_id=msg.chat.id,
|
||||
document=msg.document.file_id,
|
||||
caption=msg.caption,
|
||||
parse_mode=types.ParseMode.HTML,
|
||||
disable_notification=False,
|
||||
)
|
||||
result = await bot.send_document(chat_id=msg.chat.id, document=msg.document.file_id, caption=msg.caption,
|
||||
parse_mode=types.ParseMode.HTML, disable_notification=False)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -146,22 +118,14 @@ async def test_send_document(bot: Bot, event_loop):
|
|||
async def test_send_video(bot: Bot, event_loop):
|
||||
""" sendVideo method test with file_id """
|
||||
from .types.dataset import MESSAGE_WITH_VIDEO, VIDEO
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_VIDEO)
|
||||
video = types.Video(**VIDEO)
|
||||
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_VIDEO, loop=event_loop):
|
||||
result = await bot.send_video(
|
||||
chat_id=msg.chat.id,
|
||||
video=video.file_id,
|
||||
duration=video.duration,
|
||||
width=video.width,
|
||||
height=video.height,
|
||||
caption=msg.caption,
|
||||
parse_mode=types.ParseMode.HTML,
|
||||
supports_streaming=True,
|
||||
disable_notification=False,
|
||||
)
|
||||
result = await bot.send_video(chat_id=msg.chat.id, video=video.file_id, duration=video.duration,
|
||||
width=video.width, height=video.height, caption=msg.caption,
|
||||
parse_mode=types.ParseMode.HTML, supports_streaming=True,
|
||||
disable_notification=False)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -169,19 +133,13 @@ async def test_send_video(bot: Bot, event_loop):
|
|||
async def test_send_voice(bot: Bot, event_loop):
|
||||
""" sendVoice method test with file_id """
|
||||
from .types.dataset import MESSAGE_WITH_VOICE, VOICE
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_VOICE)
|
||||
voice = types.Voice(**VOICE)
|
||||
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_VOICE, loop=event_loop):
|
||||
result = await bot.send_voice(
|
||||
chat_id=msg.chat.id,
|
||||
voice=voice.file_id,
|
||||
caption=msg.caption,
|
||||
parse_mode=types.ParseMode.HTML,
|
||||
duration=voice.duration,
|
||||
disable_notification=False,
|
||||
)
|
||||
result = await bot.send_voice(chat_id=msg.chat.id, voice=voice.file_id, caption=msg.caption,
|
||||
parse_mode=types.ParseMode.HTML, duration=voice.duration,
|
||||
disable_notification=False)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -189,18 +147,13 @@ async def test_send_voice(bot: Bot, event_loop):
|
|||
async def test_send_video_note(bot: Bot, event_loop):
|
||||
""" sendVideoNote method test with file_id """
|
||||
from .types.dataset import MESSAGE_WITH_VIDEO_NOTE, VIDEO_NOTE
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_VIDEO_NOTE)
|
||||
video_note = types.VideoNote(**VIDEO_NOTE)
|
||||
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_VIDEO_NOTE, loop=event_loop):
|
||||
result = await bot.send_video_note(
|
||||
chat_id=msg.chat.id,
|
||||
video_note=video_note.file_id,
|
||||
duration=video_note.duration,
|
||||
length=video_note.length,
|
||||
disable_notification=False,
|
||||
)
|
||||
result = await bot.send_video_note(chat_id=msg.chat.id, video_note=video_note.file_id,
|
||||
duration=video_note.duration, length=video_note.length,
|
||||
disable_notification=False)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -208,17 +161,11 @@ async def test_send_video_note(bot: Bot, event_loop):
|
|||
async def test_send_media_group(bot: Bot, event_loop):
|
||||
""" sendMediaGroup method test with file_id """
|
||||
from .types.dataset import MESSAGE_WITH_MEDIA_GROUP, PHOTO
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_MEDIA_GROUP)
|
||||
photo = types.PhotoSize(**PHOTO)
|
||||
media = [
|
||||
types.InputMediaPhoto(media=photo.file_id),
|
||||
types.InputMediaPhoto(media=photo.file_id),
|
||||
]
|
||||
media = [types.InputMediaPhoto(media=photo.file_id), types.InputMediaPhoto(media=photo.file_id)]
|
||||
|
||||
async with FakeTelegram(
|
||||
message_dict=[MESSAGE_WITH_MEDIA_GROUP, MESSAGE_WITH_MEDIA_GROUP], loop=event_loop
|
||||
):
|
||||
async with FakeTelegram(message_dict=[MESSAGE_WITH_MEDIA_GROUP, MESSAGE_WITH_MEDIA_GROUP], loop=event_loop):
|
||||
result = await bot.send_media_group(msg.chat.id, media=media, disable_notification=False)
|
||||
assert len(result) == len(media)
|
||||
assert result.pop().media_group_id
|
||||
|
|
@ -228,18 +175,12 @@ async def test_send_media_group(bot: Bot, event_loop):
|
|||
async def test_send_location(bot: Bot, event_loop):
|
||||
""" sendLocation method test """
|
||||
from .types.dataset import MESSAGE_WITH_LOCATION, LOCATION
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_LOCATION)
|
||||
location = types.Location(**LOCATION)
|
||||
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_LOCATION, loop=event_loop):
|
||||
result = await bot.send_location(
|
||||
msg.chat.id,
|
||||
latitude=location.latitude,
|
||||
longitude=location.longitude,
|
||||
live_period=10,
|
||||
disable_notification=False,
|
||||
)
|
||||
result = await bot.send_location(msg.chat.id, latitude=location.latitude, longitude=location.longitude,
|
||||
live_period=10, disable_notification=False)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -247,18 +188,13 @@ async def test_send_location(bot: Bot, event_loop):
|
|||
async def test_edit_message_live_location_by_bot(bot: Bot, event_loop):
|
||||
""" editMessageLiveLocation method test """
|
||||
from .types.dataset import MESSAGE_WITH_LOCATION, LOCATION
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_LOCATION)
|
||||
location = types.Location(**LOCATION)
|
||||
|
||||
# editing bot message
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_LOCATION, loop=event_loop):
|
||||
result = await bot.edit_message_live_location(
|
||||
chat_id=msg.chat.id,
|
||||
message_id=msg.message_id,
|
||||
latitude=location.latitude,
|
||||
longitude=location.longitude,
|
||||
)
|
||||
result = await bot.edit_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id,
|
||||
latitude=location.latitude, longitude=location.longitude)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -266,18 +202,13 @@ async def test_edit_message_live_location_by_bot(bot: Bot, event_loop):
|
|||
async def test_edit_message_live_location_by_user(bot: Bot, event_loop):
|
||||
""" editMessageLiveLocation method test """
|
||||
from .types.dataset import MESSAGE_WITH_LOCATION, LOCATION
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_LOCATION)
|
||||
location = types.Location(**LOCATION)
|
||||
|
||||
# editing user's message
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
result = await bot.edit_message_live_location(
|
||||
chat_id=msg.chat.id,
|
||||
message_id=msg.message_id,
|
||||
latitude=location.latitude,
|
||||
longitude=location.longitude,
|
||||
)
|
||||
result = await bot.edit_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id,
|
||||
latitude=location.latitude, longitude=location.longitude)
|
||||
assert isinstance(result, bool) and result is True
|
||||
|
||||
|
||||
|
|
@ -285,14 +216,11 @@ async def test_edit_message_live_location_by_user(bot: Bot, event_loop):
|
|||
async def test_stop_message_live_location_by_bot(bot: Bot, event_loop):
|
||||
""" stopMessageLiveLocation method test """
|
||||
from .types.dataset import MESSAGE_WITH_LOCATION
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_LOCATION)
|
||||
|
||||
# stopping bot message
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_LOCATION, loop=event_loop):
|
||||
result = await bot.stop_message_live_location(
|
||||
chat_id=msg.chat.id, message_id=msg.message_id
|
||||
)
|
||||
result = await bot.stop_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -300,14 +228,11 @@ async def test_stop_message_live_location_by_bot(bot: Bot, event_loop):
|
|||
async def test_stop_message_live_location_by_user(bot: Bot, event_loop):
|
||||
""" stopMessageLiveLocation method test """
|
||||
from .types.dataset import MESSAGE_WITH_LOCATION
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_LOCATION)
|
||||
|
||||
# stopping user's message
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
result = await bot.stop_message_live_location(
|
||||
chat_id=msg.chat.id, message_id=msg.message_id
|
||||
)
|
||||
result = await bot.stop_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id)
|
||||
assert isinstance(result, bool)
|
||||
assert result is True
|
||||
|
||||
|
|
@ -316,21 +241,14 @@ async def test_stop_message_live_location_by_user(bot: Bot, event_loop):
|
|||
async def test_send_venue(bot: Bot, event_loop):
|
||||
""" sendVenue method test """
|
||||
from .types.dataset import MESSAGE_WITH_VENUE, VENUE, LOCATION
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_VENUE)
|
||||
location = types.Location(**LOCATION)
|
||||
venue = types.Venue(**VENUE)
|
||||
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_VENUE, loop=event_loop):
|
||||
result = await bot.send_venue(
|
||||
msg.chat.id,
|
||||
latitude=location.latitude,
|
||||
longitude=location.longitude,
|
||||
title=venue.title,
|
||||
address=venue.address,
|
||||
foursquare_id=venue.foursquare_id,
|
||||
disable_notification=False,
|
||||
)
|
||||
result = await bot.send_venue(msg.chat.id, latitude=location.latitude, longitude=location.longitude,
|
||||
title=venue.title, address=venue.address, foursquare_id=venue.foursquare_id,
|
||||
disable_notification=False)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -338,18 +256,12 @@ async def test_send_venue(bot: Bot, event_loop):
|
|||
async def test_send_contact(bot: Bot, event_loop):
|
||||
""" sendContact method test """
|
||||
from .types.dataset import MESSAGE_WITH_CONTACT, CONTACT
|
||||
|
||||
msg = types.Message(**MESSAGE_WITH_CONTACT)
|
||||
contact = types.Contact(**CONTACT)
|
||||
|
||||
async with FakeTelegram(message_dict=MESSAGE_WITH_CONTACT, loop=event_loop):
|
||||
result = await bot.send_contact(
|
||||
msg.chat.id,
|
||||
phone_number=contact.phone_number,
|
||||
first_name=contact.first_name,
|
||||
last_name=contact.last_name,
|
||||
disable_notification=False,
|
||||
)
|
||||
result = await bot.send_contact(msg.chat.id, phone_number=contact.phone_number, first_name=contact.first_name,
|
||||
last_name=contact.last_name, disable_notification=False)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -357,7 +269,6 @@ async def test_send_contact(bot: Bot, event_loop):
|
|||
async def test_send_chat_action(bot: Bot, event_loop):
|
||||
""" sendChatAction method test """
|
||||
from .types.dataset import CHAT
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
|
|
@ -370,7 +281,6 @@ async def test_send_chat_action(bot: Bot, event_loop):
|
|||
async def test_get_user_profile_photo(bot: Bot, event_loop):
|
||||
""" getUserProfilePhotos method test """
|
||||
from .types.dataset import USER_PROFILE_PHOTOS, USER
|
||||
|
||||
user = types.User(**USER)
|
||||
|
||||
async with FakeTelegram(message_dict=USER_PROFILE_PHOTOS, loop=event_loop):
|
||||
|
|
@ -382,7 +292,6 @@ async def test_get_user_profile_photo(bot: Bot, event_loop):
|
|||
async def test_get_file(bot: Bot, event_loop):
|
||||
""" getFile method test """
|
||||
from .types.dataset import FILE
|
||||
|
||||
file = types.File(**FILE)
|
||||
|
||||
async with FakeTelegram(message_dict=FILE, loop=event_loop):
|
||||
|
|
@ -394,7 +303,6 @@ async def test_get_file(bot: Bot, event_loop):
|
|||
async def test_kick_chat_member(bot: Bot, event_loop):
|
||||
""" kickChatMember method test """
|
||||
from .types.dataset import USER, CHAT
|
||||
|
||||
user = types.User(**USER)
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
|
|
@ -408,7 +316,6 @@ async def test_kick_chat_member(bot: Bot, event_loop):
|
|||
async def test_unban_chat_member(bot: Bot, event_loop):
|
||||
""" unbanChatMember method test """
|
||||
from .types.dataset import USER, CHAT
|
||||
|
||||
user = types.User(**USER)
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
|
|
@ -422,7 +329,6 @@ async def test_unban_chat_member(bot: Bot, event_loop):
|
|||
async def test_restrict_chat_member(bot: Bot, event_loop):
|
||||
""" restrictChatMember method test """
|
||||
from .types.dataset import USER, CHAT
|
||||
|
||||
user = types.User(**USER)
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
|
|
@ -430,12 +336,12 @@ async def test_restrict_chat_member(bot: Bot, event_loop):
|
|||
result = await bot.restrict_chat_member(
|
||||
chat_id=chat.id,
|
||||
user_id=user.id,
|
||||
permissions=types.ChatPermissions(
|
||||
can_add_web_page_previews=False,
|
||||
can_send_media_messages=False,
|
||||
can_send_messages=False,
|
||||
can_send_other_messages=False,
|
||||
until_date=123,
|
||||
)
|
||||
can_send_other_messages=False
|
||||
), until_date=123)
|
||||
assert isinstance(result, bool)
|
||||
assert result is True
|
||||
|
||||
|
|
@ -444,23 +350,14 @@ async def test_restrict_chat_member(bot: Bot, event_loop):
|
|||
async def test_promote_chat_member(bot: Bot, event_loop):
|
||||
""" promoteChatMember method test """
|
||||
from .types.dataset import USER, CHAT
|
||||
|
||||
user = types.User(**USER)
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
result = await bot.promote_chat_member(
|
||||
chat_id=chat.id,
|
||||
user_id=user.id,
|
||||
can_change_info=True,
|
||||
can_delete_messages=True,
|
||||
can_edit_messages=True,
|
||||
can_invite_users=True,
|
||||
can_pin_messages=True,
|
||||
can_post_messages=True,
|
||||
can_promote_members=True,
|
||||
can_restrict_members=True,
|
||||
)
|
||||
result = await bot.promote_chat_member(chat_id=chat.id, user_id=user.id, can_change_info=True,
|
||||
can_delete_messages=True, can_edit_messages=True,
|
||||
can_invite_users=True, can_pin_messages=True, can_post_messages=True,
|
||||
can_promote_members=True, can_restrict_members=True)
|
||||
assert isinstance(result, bool)
|
||||
assert result is True
|
||||
|
||||
|
|
@ -469,7 +366,6 @@ async def test_promote_chat_member(bot: Bot, event_loop):
|
|||
async def test_export_chat_invite_link(bot: Bot, event_loop):
|
||||
""" exportChatInviteLink method test """
|
||||
from .types.dataset import CHAT, INVITE_LINK
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=INVITE_LINK, loop=event_loop):
|
||||
|
|
@ -481,7 +377,6 @@ async def test_export_chat_invite_link(bot: Bot, event_loop):
|
|||
async def test_delete_chat_photo(bot: Bot, event_loop):
|
||||
""" deleteChatPhoto method test """
|
||||
from .types.dataset import CHAT
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
|
|
@ -494,11 +389,10 @@ async def test_delete_chat_photo(bot: Bot, event_loop):
|
|||
async def test_set_chat_title(bot: Bot, event_loop):
|
||||
""" setChatTitle method test """
|
||||
from .types.dataset import CHAT
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
result = await bot.set_chat_title(chat_id=chat.id, title="Test title")
|
||||
result = await bot.set_chat_title(chat_id=chat.id, title='Test title')
|
||||
assert isinstance(result, bool)
|
||||
assert result is True
|
||||
|
||||
|
|
@ -507,11 +401,10 @@ async def test_set_chat_title(bot: Bot, event_loop):
|
|||
async def test_set_chat_description(bot: Bot, event_loop):
|
||||
""" setChatDescription method test """
|
||||
from .types.dataset import CHAT
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
result = await bot.set_chat_description(chat_id=chat.id, description="Test description")
|
||||
result = await bot.set_chat_description(chat_id=chat.id, description='Test description')
|
||||
assert isinstance(result, bool)
|
||||
assert result is True
|
||||
|
||||
|
|
@ -520,13 +413,11 @@ async def test_set_chat_description(bot: Bot, event_loop):
|
|||
async def test_pin_chat_message(bot: Bot, event_loop):
|
||||
""" pinChatMessage method test """
|
||||
from .types.dataset import MESSAGE
|
||||
|
||||
message = types.Message(**MESSAGE)
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
result = await bot.pin_chat_message(
|
||||
chat_id=message.chat.id, message_id=message.message_id, disable_notification=False
|
||||
)
|
||||
result = await bot.pin_chat_message(chat_id=message.chat.id, message_id=message.message_id,
|
||||
disable_notification=False)
|
||||
assert isinstance(result, bool)
|
||||
assert result is True
|
||||
|
||||
|
|
@ -535,7 +426,6 @@ async def test_pin_chat_message(bot: Bot, event_loop):
|
|||
async def test_unpin_chat_message(bot: Bot, event_loop):
|
||||
""" unpinChatMessage method test """
|
||||
from .types.dataset import CHAT
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
|
|
@ -548,7 +438,6 @@ async def test_unpin_chat_message(bot: Bot, event_loop):
|
|||
async def test_leave_chat(bot: Bot, event_loop):
|
||||
""" leaveChat method test """
|
||||
from .types.dataset import CHAT
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
|
|
@ -561,7 +450,6 @@ async def test_leave_chat(bot: Bot, event_loop):
|
|||
async def test_get_chat(bot: Bot, event_loop):
|
||||
""" getChat method test """
|
||||
from .types.dataset import CHAT
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=CHAT, loop=event_loop):
|
||||
|
|
@ -573,7 +461,6 @@ async def test_get_chat(bot: Bot, event_loop):
|
|||
async def test_get_chat_administrators(bot: Bot, event_loop):
|
||||
""" getChatAdministrators method test """
|
||||
from .types.dataset import CHAT, CHAT_MEMBER
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
member = types.ChatMember(**CHAT_MEMBER)
|
||||
|
||||
|
|
@ -587,7 +474,6 @@ async def test_get_chat_administrators(bot: Bot, event_loop):
|
|||
async def test_get_chat_members_count(bot: Bot, event_loop):
|
||||
""" getChatMembersCount method test """
|
||||
from .types.dataset import CHAT
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
count = 5
|
||||
|
||||
|
|
@ -600,7 +486,6 @@ async def test_get_chat_members_count(bot: Bot, event_loop):
|
|||
async def test_get_chat_member(bot: Bot, event_loop):
|
||||
""" getChatMember method test """
|
||||
from .types.dataset import CHAT, CHAT_MEMBER
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
member = types.ChatMember(**CHAT_MEMBER)
|
||||
|
||||
|
|
@ -614,13 +499,10 @@ async def test_get_chat_member(bot: Bot, event_loop):
|
|||
async def test_set_chat_sticker_set(bot: Bot, event_loop):
|
||||
""" setChatStickerSet method test """
|
||||
from .types.dataset import CHAT
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
result = await bot.set_chat_sticker_set(
|
||||
chat_id=chat.id, sticker_set_name="aiogram_stickers"
|
||||
)
|
||||
result = await bot.set_chat_sticker_set(chat_id=chat.id, sticker_set_name='aiogram_stickers')
|
||||
assert isinstance(result, bool)
|
||||
assert result is True
|
||||
|
||||
|
|
@ -629,7 +511,6 @@ async def test_set_chat_sticker_set(bot: Bot, event_loop):
|
|||
async def test_delete_chat_sticker_set(bot: Bot, event_loop):
|
||||
""" setChatStickerSet method test """
|
||||
from .types.dataset import CHAT
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
|
|
@ -643,7 +524,7 @@ async def test_answer_callback_query(bot: Bot, event_loop):
|
|||
""" answerCallbackQuery method test """
|
||||
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
result = await bot.answer_callback_query(callback_query_id="QuERyId", text="Test Answer")
|
||||
result = await bot.answer_callback_query(callback_query_id='QuERyId', text='Test Answer')
|
||||
assert isinstance(result, bool)
|
||||
assert result is True
|
||||
|
||||
|
|
@ -652,14 +533,11 @@ async def test_answer_callback_query(bot: Bot, event_loop):
|
|||
async def test_edit_message_text_by_bot(bot: Bot, event_loop):
|
||||
""" editMessageText method test """
|
||||
from .types.dataset import EDITED_MESSAGE
|
||||
|
||||
msg = types.Message(**EDITED_MESSAGE)
|
||||
|
||||
# message by bot
|
||||
async with FakeTelegram(message_dict=EDITED_MESSAGE, loop=event_loop):
|
||||
result = await bot.edit_message_text(
|
||||
text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id
|
||||
)
|
||||
result = await bot.edit_message_text(text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id)
|
||||
assert result == msg
|
||||
|
||||
|
||||
|
|
@ -667,13 +545,10 @@ async def test_edit_message_text_by_bot(bot: Bot, event_loop):
|
|||
async def test_edit_message_text_by_user(bot: Bot, event_loop):
|
||||
""" editMessageText method test """
|
||||
from .types.dataset import EDITED_MESSAGE
|
||||
|
||||
msg = types.Message(**EDITED_MESSAGE)
|
||||
|
||||
# message by user
|
||||
async with FakeTelegram(message_dict=True, loop=event_loop):
|
||||
result = await bot.edit_message_text(
|
||||
text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id
|
||||
)
|
||||
result = await bot.edit_message_text(text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id)
|
||||
assert isinstance(result, bool)
|
||||
assert result is True
|
||||
|
|
|
|||
18
tests/test_bot/test_api.py
Normal file
18
tests/test_bot/test_api.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import pytest
|
||||
from aiogram.bot.api import check_token
|
||||
|
||||
from aiogram.utils.exceptions import ValidationError
|
||||
|
||||
|
||||
VALID_TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff-1234567890'
|
||||
INVALID_TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff 123456789' # Space in token and wrong length
|
||||
|
||||
|
||||
class Test_check_token:
|
||||
|
||||
def test_valid(self):
|
||||
assert check_token(VALID_TOKEN) is True
|
||||
|
||||
def test_invalid_token(self):
|
||||
with pytest.raises(ValidationError):
|
||||
check_token(INVALID_TOKEN)
|
||||
18
tests/test_dispatcher/test_filters/test_builtin.py
Normal file
18
tests/test_dispatcher/test_filters/test_builtin.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.dispatcher.filters.builtin import Text
|
||||
|
||||
|
||||
class TestText:
|
||||
|
||||
@pytest.mark.parametrize('param, key', [
|
||||
('text', 'equals'),
|
||||
('text_contains', 'contains'),
|
||||
('text_startswith', 'startswith'),
|
||||
('text_endswith', 'endswith'),
|
||||
])
|
||||
def test_validate(self, param, key):
|
||||
value = 'spam and eggs'
|
||||
config = {param: value}
|
||||
res = Text.validate(config)
|
||||
assert res == {key: value}
|
||||
18
tests/test_dispatcher/test_filters/test_state.py
Normal file
18
tests/test_dispatcher/test_filters/test_state.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from aiogram.dispatcher.filters.state import StatesGroup
|
||||
|
||||
class TestStatesGroup:
|
||||
|
||||
def test_all_childs(self):
|
||||
|
||||
class InnerState1(StatesGroup):
|
||||
pass
|
||||
|
||||
class InnerState2(InnerState1):
|
||||
pass
|
||||
|
||||
class Form(StatesGroup):
|
||||
inner1 = InnerState1
|
||||
inner2 = InnerState2
|
||||
|
||||
form_childs = Form.all_childs
|
||||
assert form_childs == (InnerState1, InnerState2)
|
||||
263
tests/test_filters.py
Normal file
263
tests/test_filters.py
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.dispatcher.filters import Text
|
||||
from aiogram.types import Message, CallbackQuery, InlineQuery, Poll
|
||||
|
||||
|
||||
def data_sample_1():
|
||||
return [
|
||||
('', ''),
|
||||
('', 'exAmple_string'),
|
||||
|
||||
('example_string', 'example_string'),
|
||||
('example_string', 'exAmple_string'),
|
||||
('exAmple_string', 'example_string'),
|
||||
|
||||
('example_string', 'example_string_dsf'),
|
||||
('example_string', 'example_striNG_dsf'),
|
||||
('example_striNG', 'example_string_dsf'),
|
||||
|
||||
('example_string', 'not_example_string'),
|
||||
('example_string', 'not_eXample_string'),
|
||||
('EXample_string', 'not_example_string'),
|
||||
]
|
||||
|
||||
class TestTextFilter:
|
||||
|
||||
async def _run_check(self, check, test_text):
|
||||
assert await check(Message(text=test_text))
|
||||
assert await check(CallbackQuery(data=test_text))
|
||||
assert await check(InlineQuery(query=test_text))
|
||||
assert await check(Poll(question=test_text))
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||
@pytest.mark.parametrize("test_prefix, test_text", data_sample_1())
|
||||
async def test_startswith(self, test_prefix, test_text, ignore_case):
|
||||
test_filter = Text(startswith=test_prefix, ignore_case=ignore_case)
|
||||
|
||||
async def check(obj):
|
||||
result = await test_filter.check(obj)
|
||||
if ignore_case:
|
||||
_test_prefix = test_prefix.lower()
|
||||
_test_text = test_text.lower()
|
||||
else:
|
||||
_test_prefix = test_prefix
|
||||
_test_text = test_text
|
||||
|
||||
return result is _test_text.startswith(_test_prefix)
|
||||
|
||||
await self._run_check(check, test_text)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||
@pytest.mark.parametrize("test_prefix_list, test_text", [
|
||||
(['not_example', ''], ''),
|
||||
(['', 'not_example'], 'exAmple_string'),
|
||||
|
||||
(['not_example', 'example_string'], 'example_string'),
|
||||
(['example_string', 'not_example'], 'exAmple_string'),
|
||||
(['not_example', 'exAmple_string'], 'example_string'),
|
||||
|
||||
(['not_example', 'example_string'], 'example_string_dsf'),
|
||||
(['example_string', 'not_example'], 'example_striNG_dsf'),
|
||||
(['not_example', 'example_striNG'], 'example_string_dsf'),
|
||||
|
||||
(['not_example', 'example_string'], 'not_example_string'),
|
||||
(['example_string', 'not_example'], 'not_eXample_string'),
|
||||
(['not_example', 'EXample_string'], 'not_example_string'),
|
||||
])
|
||||
async def test_startswith_list(self, test_prefix_list, test_text, ignore_case):
|
||||
test_filter = Text(startswith=test_prefix_list, ignore_case=ignore_case)
|
||||
|
||||
async def check(obj):
|
||||
result = await test_filter.check(obj)
|
||||
if ignore_case:
|
||||
_test_prefix_list = map(str.lower, test_prefix_list)
|
||||
_test_text = test_text.lower()
|
||||
else:
|
||||
_test_prefix_list = test_prefix_list
|
||||
_test_text = test_text
|
||||
|
||||
return result is any(map(_test_text.startswith, _test_prefix_list))
|
||||
|
||||
await self._run_check(check, test_text)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||
@pytest.mark.parametrize("test_postfix, test_text", data_sample_1())
|
||||
async def test_endswith(self, test_postfix, test_text, ignore_case):
|
||||
test_filter = Text(endswith=test_postfix, ignore_case=ignore_case)
|
||||
|
||||
async def check(obj):
|
||||
result = await test_filter.check(obj)
|
||||
if ignore_case:
|
||||
_test_postfix = test_postfix.lower()
|
||||
_test_text = test_text.lower()
|
||||
else:
|
||||
_test_postfix = test_postfix
|
||||
_test_text = test_text
|
||||
|
||||
return result is _test_text.endswith(_test_postfix)
|
||||
|
||||
await self._run_check(check, test_text)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||
@pytest.mark.parametrize("test_postfix_list, test_text", [
|
||||
(['', 'not_example'], ''),
|
||||
(['not_example', ''], 'exAmple_string'),
|
||||
|
||||
(['example_string', 'not_example'], 'example_string'),
|
||||
(['not_example', 'example_string'], 'exAmple_string'),
|
||||
(['exAmple_string', 'not_example'], 'example_string'),
|
||||
|
||||
(['not_example', 'example_string'], 'example_string_dsf'),
|
||||
(['example_string', 'not_example'], 'example_striNG_dsf'),
|
||||
(['not_example', 'example_striNG'], 'example_string_dsf'),
|
||||
|
||||
(['not_example', 'example_string'], 'not_example_string'),
|
||||
(['example_string', 'not_example'], 'not_eXample_string'),
|
||||
(['not_example', 'EXample_string'], 'not_example_string'),
|
||||
])
|
||||
async def test_endswith_list(self, test_postfix_list, test_text, ignore_case):
|
||||
test_filter = Text(endswith=test_postfix_list, ignore_case=ignore_case)
|
||||
|
||||
async def check(obj):
|
||||
result = await test_filter.check(obj)
|
||||
if ignore_case:
|
||||
_test_postfix_list = map(str.lower, test_postfix_list)
|
||||
_test_text = test_text.lower()
|
||||
else:
|
||||
_test_postfix_list = test_postfix_list
|
||||
_test_text = test_text
|
||||
|
||||
return result is any(map(_test_text.endswith, _test_postfix_list))
|
||||
await self._run_check(check, test_text)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||
@pytest.mark.parametrize("test_string, test_text", [
|
||||
('', ''),
|
||||
('', 'exAmple_string'),
|
||||
|
||||
('example_string', 'example_string'),
|
||||
('example_string', 'exAmple_string'),
|
||||
('exAmple_string', 'example_string'),
|
||||
|
||||
('example_string', 'example_string_dsf'),
|
||||
('example_string', 'example_striNG_dsf'),
|
||||
('example_striNG', 'example_string_dsf'),
|
||||
|
||||
('example_string', 'not_example_strin'),
|
||||
('example_string', 'not_eXample_strin'),
|
||||
('EXample_string', 'not_example_strin'),
|
||||
])
|
||||
async def test_contains(self, test_string, test_text, ignore_case):
|
||||
test_filter = Text(contains=test_string, ignore_case=ignore_case)
|
||||
|
||||
async def check(obj):
|
||||
result = await test_filter.check(obj)
|
||||
if ignore_case:
|
||||
_test_string = test_string.lower()
|
||||
_test_text = test_text.lower()
|
||||
else:
|
||||
_test_string = test_string
|
||||
_test_text = test_text
|
||||
|
||||
return result is (_test_string in _test_text)
|
||||
|
||||
await self._run_check(check, test_text)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||
@pytest.mark.parametrize("test_filter_list, test_text", [
|
||||
(['a', 'ab', 'abc'], 'A'),
|
||||
(['a', 'ab', 'abc'], 'ab'),
|
||||
(['a', 'ab', 'abc'], 'aBc'),
|
||||
(['a', 'ab', 'abc'], 'd'),
|
||||
])
|
||||
async def test_contains_list(self, test_filter_list, test_text, ignore_case):
|
||||
test_filter = Text(contains=test_filter_list, ignore_case=ignore_case)
|
||||
|
||||
async def check(obj):
|
||||
result = await test_filter.check(obj)
|
||||
if ignore_case:
|
||||
_test_filter_list = list(map(str.lower, test_filter_list))
|
||||
_test_text = test_text.lower()
|
||||
else:
|
||||
_test_filter_list = test_filter_list
|
||||
_test_text = test_text
|
||||
|
||||
return result is all(map(_test_text.__contains__, _test_filter_list))
|
||||
|
||||
await self._run_check(check, test_text)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||
@pytest.mark.parametrize("test_filter_text, test_text", [
|
||||
('', ''),
|
||||
('', 'exAmple_string'),
|
||||
|
||||
('example_string', 'example_string'),
|
||||
('example_string', 'exAmple_string'),
|
||||
('exAmple_string', 'example_string'),
|
||||
|
||||
('example_string', 'not_example_string'),
|
||||
('example_string', 'not_eXample_string'),
|
||||
('EXample_string', 'not_example_string'),
|
||||
])
|
||||
async def test_equals_string(self, test_filter_text, test_text, ignore_case):
|
||||
test_filter = Text(equals=test_filter_text, ignore_case=ignore_case)
|
||||
|
||||
async def check(obj):
|
||||
result = await test_filter.check(obj)
|
||||
if ignore_case:
|
||||
_test_filter_text = test_filter_text.lower()
|
||||
_test_text = test_text.lower()
|
||||
else:
|
||||
_test_filter_text = test_filter_text
|
||||
_test_text = test_text
|
||||
return result is (_test_text == _test_filter_text)
|
||||
|
||||
await self._run_check(check, test_text)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||
@pytest.mark.parametrize("test_filter_list, test_text", [
|
||||
(['new_string', ''], ''),
|
||||
(['', 'new_string'], 'exAmple_string'),
|
||||
|
||||
(['example_string'], 'example_string'),
|
||||
(['example_string'], 'exAmple_string'),
|
||||
(['exAmple_string'], 'example_string'),
|
||||
|
||||
(['example_string'], 'not_example_string'),
|
||||
(['example_string'], 'not_eXample_string'),
|
||||
(['EXample_string'], 'not_example_string'),
|
||||
|
||||
(['example_string', 'new_string'], 'example_string'),
|
||||
(['new_string', 'example_string'], 'exAmple_string'),
|
||||
(['exAmple_string', 'new_string'], 'example_string'),
|
||||
|
||||
(['example_string', 'new_string'], 'not_example_string'),
|
||||
(['new_string', 'example_string'], 'not_eXample_string'),
|
||||
(['EXample_string', 'new_string'], 'not_example_string'),
|
||||
])
|
||||
async def test_equals_list(self, test_filter_list, test_text, ignore_case):
|
||||
test_filter = Text(equals=test_filter_list, ignore_case=ignore_case)
|
||||
|
||||
async def check(obj):
|
||||
result = await test_filter.check(obj)
|
||||
if ignore_case:
|
||||
_test_filter_list = list(map(str.lower, test_filter_list))
|
||||
_test_text = test_text.lower()
|
||||
else:
|
||||
_test_filter_list = test_filter_list
|
||||
_test_text = test_text
|
||||
assert result is (_test_text in _test_filter_list)
|
||||
|
||||
await check(Message(text=test_text))
|
||||
await check(CallbackQuery(data=test_text))
|
||||
await check(InlineQuery(query=test_text))
|
||||
await check(Poll(question=test_text))
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.bot import api
|
||||
from aiogram.utils import auth_widget, exceptions
|
||||
|
||||
VALID_TOKEN = "123456789:AABBCCDDEEFFaabbccddeeff-1234567890"
|
||||
INVALID_TOKEN = "123456789:AABBCCDDEEFFaabbccddeeff 123456789" # Space in token and wrong length
|
||||
|
||||
VALID_DATA = {
|
||||
"date": 1525385236,
|
||||
"first_name": "Test",
|
||||
"last_name": "User",
|
||||
"id": 123456789,
|
||||
"username": "username",
|
||||
"hash": "69a9871558fbbe4cd0dbaba52fa1cc4f38315d3245b7504381a64139fb024b5b",
|
||||
}
|
||||
INVALID_DATA = {
|
||||
"date": 1525385237,
|
||||
"first_name": "Test",
|
||||
"last_name": "User",
|
||||
"id": 123456789,
|
||||
"username": "username",
|
||||
"hash": "69a9871558fbbe4cd0dbaba52fa1cc4f38315d3245b7504381a64139fb024b5b",
|
||||
}
|
||||
|
||||
|
||||
def test_valid_token():
|
||||
assert api.check_token(VALID_TOKEN)
|
||||
|
||||
|
||||
def test_invalid_token():
|
||||
with pytest.raises(exceptions.ValidationError):
|
||||
api.check_token(INVALID_TOKEN)
|
||||
|
||||
|
||||
def test_widget():
|
||||
assert auth_widget.check_token(VALID_DATA, VALID_TOKEN)
|
||||
|
||||
|
||||
def test_invalid_widget_data():
|
||||
assert not auth_widget.check_token(INVALID_DATA, VALID_TOKEN)
|
||||
46
tests/test_utils/test_auth_widget.py
Normal file
46
tests/test_utils/test_auth_widget.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.utils.auth_widget import check_integrity, \
|
||||
generate_hash, check_token
|
||||
|
||||
TOKEN = '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def data():
|
||||
return {
|
||||
'id': '42',
|
||||
'first_name': 'John',
|
||||
'last_name': 'Smith',
|
||||
'username': 'username',
|
||||
'photo_url': 'https://t.me/i/userpic/320/picname.jpg',
|
||||
'auth_date': '1565810688',
|
||||
'hash': 'c303db2b5a06fe41d23a9b14f7c545cfc11dcc7473c07c9c5034ae60062461ce',
|
||||
}
|
||||
|
||||
|
||||
def test_generate_hash(data):
|
||||
res = generate_hash(data, TOKEN)
|
||||
assert res == data['hash']
|
||||
|
||||
|
||||
class Test_check_token:
|
||||
"""
|
||||
This case gonna be deleted
|
||||
"""
|
||||
def test_ok(self, data):
|
||||
assert check_token(data, TOKEN) is True
|
||||
|
||||
def test_fail(self, data):
|
||||
data.pop('username')
|
||||
assert check_token(data, TOKEN) is False
|
||||
|
||||
|
||||
class Test_check_integrity:
|
||||
|
||||
def test_ok(self, data):
|
||||
assert check_integrity(TOKEN, data) is True
|
||||
|
||||
def test_fail(self, data):
|
||||
data.pop('username')
|
||||
assert check_integrity(TOKEN, data) is False
|
||||
22
tests/test_utils/test_helper.py
Normal file
22
tests/test_utils/test_helper.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from aiogram.utils.helper import OrderedHelper, Item, ListItem
|
||||
|
||||
|
||||
class TestOrderedHelper:
|
||||
|
||||
def test_items_are_ordered(self):
|
||||
class Helper(OrderedHelper):
|
||||
A = Item()
|
||||
D = Item()
|
||||
C = Item()
|
||||
B = Item()
|
||||
|
||||
assert Helper.all() == ['A', 'D', 'C', 'B']
|
||||
|
||||
def test_list_items_are_ordered(self):
|
||||
class Helper(OrderedHelper):
|
||||
A = ListItem()
|
||||
D = ListItem()
|
||||
C = ListItem()
|
||||
B = ListItem()
|
||||
|
||||
assert Helper.all() == ['A', 'D', 'C', 'B']
|
||||
|
|
@ -47,7 +47,11 @@ CHAT_MEMBER = {
|
|||
"can_promote_members": False,
|
||||
}
|
||||
|
||||
CONTACT = {"phone_number": "88005553535", "first_name": "John", "last_name": "Smith"}
|
||||
CONTACT = {
|
||||
"phone_number": "88005553535",
|
||||
"first_name": "John",
|
||||
"last_name": "Smith",
|
||||
}
|
||||
|
||||
DOCUMENT = {
|
||||
"file_name": "test.docx",
|
||||
|
|
@ -64,17 +68,42 @@ ANIMATION = {
|
|||
"file_size": 65837,
|
||||
}
|
||||
|
||||
ENTITY_BOLD = {"offset": 5, "length": 2, "type": "bold"}
|
||||
ENTITY_BOLD = {
|
||||
"offset": 5,
|
||||
"length": 2,
|
||||
"type": "bold",
|
||||
}
|
||||
|
||||
ENTITY_ITALIC = {"offset": 8, "length": 1, "type": "italic"}
|
||||
ENTITY_ITALIC = {
|
||||
"offset": 8,
|
||||
"length": 1,
|
||||
"type": "italic",
|
||||
}
|
||||
|
||||
ENTITY_LINK = {"offset": 10, "length": 6, "type": "text_link", "url": "http://google.com/"}
|
||||
ENTITY_LINK = {
|
||||
"offset": 10,
|
||||
"length": 6,
|
||||
"type": "text_link",
|
||||
"url": "http://google.com/",
|
||||
}
|
||||
|
||||
ENTITY_CODE = {"offset": 17, "length": 7, "type": "code"}
|
||||
ENTITY_CODE = {
|
||||
"offset": 17,
|
||||
"length": 7,
|
||||
"type": "code",
|
||||
}
|
||||
|
||||
ENTITY_PRE = {"offset": 30, "length": 4, "type": "pre"}
|
||||
ENTITY_PRE = {
|
||||
"offset": 30,
|
||||
"length": 4,
|
||||
"type": "pre",
|
||||
}
|
||||
|
||||
ENTITY_MENTION = {"offset": 47, "length": 9, "type": "mention"}
|
||||
ENTITY_MENTION = {
|
||||
"offset": 47,
|
||||
"length": 9,
|
||||
"type": "mention",
|
||||
}
|
||||
|
||||
GAME = {
|
||||
"title": "Karate Kido",
|
||||
|
|
@ -94,7 +123,10 @@ INVOICE = {
|
|||
"total_amount": 6250,
|
||||
}
|
||||
|
||||
LOCATION = {"latitude": 50.693416, "longitude": 30.624605}
|
||||
LOCATION = {
|
||||
"latitude": 50.693416,
|
||||
"longitude": 30.624605,
|
||||
}
|
||||
|
||||
VENUE = {
|
||||
"location": LOCATION,
|
||||
|
|
@ -121,7 +153,7 @@ STICKER = {
|
|||
"file_id": "AAbbCCddEEffGGhh1234567890",
|
||||
"file_size": 1234,
|
||||
"width": 128,
|
||||
"height": 128,
|
||||
"height": 128
|
||||
},
|
||||
"file_id": "AAbbCCddEEffGGhh1234567890",
|
||||
"file_size": 12345,
|
||||
|
|
@ -186,15 +218,8 @@ FORWARDED_MESSAGE = {
|
|||
"forward_from_message_id": 123,
|
||||
"forward_date": 1522749037,
|
||||
"text": "Forwarded text with entities from public channel ",
|
||||
"entities": [
|
||||
ENTITY_BOLD,
|
||||
ENTITY_CODE,
|
||||
ENTITY_ITALIC,
|
||||
ENTITY_LINK,
|
||||
ENTITY_LINK,
|
||||
ENTITY_MENTION,
|
||||
ENTITY_PRE,
|
||||
],
|
||||
"entities": [ENTITY_BOLD, ENTITY_CODE, ENTITY_ITALIC, ENTITY_LINK,
|
||||
ENTITY_LINK, ENTITY_MENTION, ENTITY_PRE],
|
||||
}
|
||||
|
||||
INLINE_QUERY = {}
|
||||
|
|
@ -391,12 +416,27 @@ SHIPPING_QUERY = {
|
|||
"shipping_address": SHIPPING_ADDRESS,
|
||||
}
|
||||
|
||||
USER_PROFILE_PHOTOS = {"total_count": 1, "photos": [[PHOTO, PHOTO, PHOTO]]}
|
||||
USER_PROFILE_PHOTOS = {
|
||||
"total_count": 1, "photos": [
|
||||
[PHOTO, PHOTO, PHOTO],
|
||||
],
|
||||
}
|
||||
|
||||
FILE = {"file_id": "XXXYYYZZZ", "file_size": 5254, "file_path": "voice/file_8"}
|
||||
FILE = {
|
||||
"file_id": "XXXYYYZZZ",
|
||||
"file_size": 5254,
|
||||
"file_path": "voice/file_8",
|
||||
}
|
||||
|
||||
INVITE_LINK = "https://t.me/joinchat/AbCdEfjKILDADwdd123"
|
||||
INVITE_LINK = 'https://t.me/joinchat/AbCdEfjKILDADwdd123'
|
||||
|
||||
UPDATE = {"update_id": 123456789, "message": MESSAGE}
|
||||
UPDATE = {
|
||||
"update_id": 123456789,
|
||||
"message": MESSAGE,
|
||||
}
|
||||
|
||||
WEBHOOK_INFO = {"url": "", "has_custom_certificate": False, "pending_update_count": 0}
|
||||
WEBHOOK_INFO = {
|
||||
"url": "",
|
||||
"has_custom_certificate": False,
|
||||
"pending_update_count": 0,
|
||||
}
|
||||
|
|
|
|||
77
tests/types/test_chat_member.py
Normal file
77
tests/types/test_chat_member.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
from aiogram import types
|
||||
from .dataset import CHAT_MEMBER
|
||||
|
||||
chat_member = types.ChatMember(**CHAT_MEMBER)
|
||||
|
||||
|
||||
def test_export():
|
||||
exported = chat_member.to_python()
|
||||
assert isinstance(exported, dict)
|
||||
assert exported == CHAT_MEMBER
|
||||
|
||||
|
||||
def test_user():
|
||||
assert isinstance(chat_member.user, types.User)
|
||||
|
||||
|
||||
def test_status():
|
||||
assert isinstance(chat_member.status, str)
|
||||
assert chat_member.status == CHAT_MEMBER['status']
|
||||
|
||||
|
||||
def test_privileges():
|
||||
assert isinstance(chat_member.can_be_edited, bool)
|
||||
assert chat_member.can_be_edited == CHAT_MEMBER['can_be_edited']
|
||||
|
||||
assert isinstance(chat_member.can_change_info, bool)
|
||||
assert chat_member.can_change_info == CHAT_MEMBER['can_change_info']
|
||||
|
||||
assert isinstance(chat_member.can_delete_messages, bool)
|
||||
assert chat_member.can_delete_messages == CHAT_MEMBER['can_delete_messages']
|
||||
|
||||
assert isinstance(chat_member.can_invite_users, bool)
|
||||
assert chat_member.can_invite_users == CHAT_MEMBER['can_invite_users']
|
||||
|
||||
assert isinstance(chat_member.can_restrict_members, bool)
|
||||
assert chat_member.can_restrict_members == CHAT_MEMBER['can_restrict_members']
|
||||
|
||||
assert isinstance(chat_member.can_pin_messages, bool)
|
||||
assert chat_member.can_pin_messages == CHAT_MEMBER['can_pin_messages']
|
||||
|
||||
assert isinstance(chat_member.can_promote_members, bool)
|
||||
assert chat_member.can_promote_members == CHAT_MEMBER['can_promote_members']
|
||||
|
||||
|
||||
def test_int():
|
||||
assert int(chat_member) == chat_member.user.id
|
||||
assert isinstance(int(chat_member), int)
|
||||
|
||||
|
||||
def test_chat_member_status():
|
||||
assert types.ChatMemberStatus.CREATOR == 'creator'
|
||||
assert types.ChatMemberStatus.ADMINISTRATOR == 'administrator'
|
||||
assert types.ChatMemberStatus.MEMBER == 'member'
|
||||
assert types.ChatMemberStatus.RESTRICTED == 'restricted'
|
||||
assert types.ChatMemberStatus.LEFT == 'left'
|
||||
assert types.ChatMemberStatus.KICKED == 'kicked'
|
||||
|
||||
|
||||
def test_chat_member_status_filters():
|
||||
assert types.ChatMemberStatus.is_chat_admin(chat_member.status)
|
||||
assert types.ChatMemberStatus.is_chat_member(chat_member.status)
|
||||
|
||||
assert types.ChatMemberStatus.is_chat_admin(types.ChatMemberStatus.CREATOR)
|
||||
assert types.ChatMemberStatus.is_chat_admin(types.ChatMemberStatus.ADMINISTRATOR)
|
||||
|
||||
assert types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.CREATOR)
|
||||
assert types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.ADMINISTRATOR)
|
||||
assert types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.MEMBER)
|
||||
assert types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.RESTRICTED)
|
||||
|
||||
assert not types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.LEFT)
|
||||
assert not types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.KICKED)
|
||||
|
||||
|
||||
def test_chat_member_filters():
|
||||
assert chat_member.is_chat_admin()
|
||||
assert chat_member.is_chat_member()
|
||||
Loading…
Add table
Add a link
Reference in a new issue