mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Merge branch 'aiogram:dev-2.x' into dev-2.x
This commit is contained in:
commit
371f589298
114 changed files with 5362 additions and 1990 deletions
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
|
@ -1 +1,2 @@
|
|||
open_collective: aiogram
|
||||
patreon: aiogram
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -43,7 +43,7 @@ test:
|
|||
tox
|
||||
|
||||
summary:
|
||||
cloc aiogram/ tests/ examples/ setup.py
|
||||
cloc aiogram/ tests/ examples/ docs/ setup.py
|
||||
|
||||
docs: docs/source/*
|
||||
cd docs && $(MAKE) html
|
||||
|
|
|
|||
68
README.md
68
README.md
|
|
@ -6,22 +6,82 @@
|
|||
[](https://pypi.python.org/pypi/aiogram)
|
||||
[](https://pypi.python.org/pypi/aiogram)
|
||||
[](https://pypi.python.org/pypi/aiogram)
|
||||
[](https://core.telegram.org/bots/api)
|
||||
[](https://core.telegram.org/bots/api)
|
||||
[](http://docs.aiogram.dev/en/latest/?badge=latest)
|
||||
[](https://github.com/aiogram/aiogram/issues)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
**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://docs.aiogram.dev/en/latest/).
|
||||
|
||||
## Examples
|
||||
<details>
|
||||
<summary>📚 Click to see some basic examples</summary>
|
||||
|
||||
|
||||
**Few steps before getting started...**
|
||||
- First, you should obtain token for your bot from [BotFather](https://t.me/BotFather).
|
||||
- Install latest stable version of aiogram, simply running `pip install aiogram`
|
||||
|
||||
### Simple [`getMe`](https://core.telegram.org/bots/api#getme) request
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from aiogram import Bot
|
||||
|
||||
BOT_TOKEN = ""
|
||||
|
||||
async def main():
|
||||
bot = Bot(token=BOT_TOKEN)
|
||||
|
||||
try:
|
||||
me = await bot.get_me()
|
||||
print(f"🤖 Hello, I'm {me.first_name}.\nHave a nice Day!")
|
||||
finally:
|
||||
await bot.close()
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Poll BotAPI for updates and process updates
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
|
||||
BOT_TOKEN = ""
|
||||
|
||||
async def start_handler(event: types.Message):
|
||||
await event.answer(
|
||||
f"Hello, {event.from_user.get_mention(as_html=True)} 👋!",
|
||||
parse_mode=types.ParseMode.HTML,
|
||||
)
|
||||
|
||||
async def main():
|
||||
bot = Bot(token=BOT_TOKEN)
|
||||
try:
|
||||
disp = Dispatcher(bot=bot)
|
||||
disp.register_message_handler(start_handler, commands={"start", "restart"})
|
||||
await disp.start_polling()
|
||||
finally:
|
||||
await bot.close()
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Moar!
|
||||
|
||||
You can find more examples in [`examples/`](https://github.com/aiogram/aiogram/tree/dev-2.x/examples) directory
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## Official aiogram resources:
|
||||
- News: [@aiogram_live](https://t.me/aiogram_live)
|
||||
- Community: [@aiogram](https://t.me/aiogram)
|
||||
- Russian community: [@aiogram_ru](https://t.me/aiogram_ru)
|
||||
- Pip: [aiogram](https://pypi.python.org/pypi/aiogram)
|
||||
- Docs: [aiogram site](https://docs.aiogram.dev/)
|
||||
- PyPI: [aiogram](https://pypi.python.org/pypi/aiogram)
|
||||
- Documentation: [aiogram site](https://docs.aiogram.dev/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)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ AIOGramBot
|
|||
:target: https://pypi.python.org/pypi/aiogram
|
||||
:alt: Supported python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.9-blue.svg?style=flat-square&logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.3-blue.svg?style=flat-square&logo=telegram
|
||||
:target: https://core.telegram.org/bots/api
|
||||
:alt: Telegram Bot API
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ else:
|
|||
if 'DISABLE_UVLOOP' not in os.environ:
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
|
||||
__all__ = [
|
||||
__all__ = (
|
||||
'Bot',
|
||||
'Dispatcher',
|
||||
'__api_version__',
|
||||
|
|
@ -40,8 +40,8 @@ __all__ = [
|
|||
'md',
|
||||
'middlewares',
|
||||
'types',
|
||||
'utils'
|
||||
]
|
||||
'utils',
|
||||
)
|
||||
|
||||
__version__ = '2.9.2'
|
||||
__api_version__ = '4.9'
|
||||
__version__ = '2.14.3'
|
||||
__api_version__ = '5.3'
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from . import api
|
|||
from .base import BaseBot
|
||||
from .bot import Bot
|
||||
|
||||
__all__ = [
|
||||
__all__ = (
|
||||
'BaseBot',
|
||||
'Bot',
|
||||
'api'
|
||||
]
|
||||
'api',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,57 @@
|
|||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from http import HTTPStatus
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .. import types
|
||||
from ..utils import exceptions
|
||||
from ..utils import json
|
||||
from ..utils import exceptions, json
|
||||
from ..utils.helper import Helper, HelperMode, Item
|
||||
|
||||
# Main aiogram logger
|
||||
log = logging.getLogger('aiogram')
|
||||
|
||||
# API Url's
|
||||
API_URL = "https://api.telegram.org/bot{token}/{method}"
|
||||
FILE_URL = "https://api.telegram.org/file/bot{token}/{path}"
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TelegramAPIServer:
|
||||
"""
|
||||
Base config for API Endpoints
|
||||
"""
|
||||
|
||||
base: str
|
||||
file: str
|
||||
|
||||
def api_url(self, token: str, method: str) -> str:
|
||||
"""
|
||||
Generate URL for API methods
|
||||
|
||||
:param token: Bot token
|
||||
:param method: API method name (case insensitive)
|
||||
:return: URL
|
||||
"""
|
||||
return self.base.format(token=token, method=method)
|
||||
|
||||
def file_url(self, token: str, path: str) -> str:
|
||||
"""
|
||||
Generate URL for downloading files
|
||||
|
||||
:param token: Bot token
|
||||
:param path: file path
|
||||
:return: URL
|
||||
"""
|
||||
return self.file.format(token=token, path=path)
|
||||
|
||||
@classmethod
|
||||
def from_base(cls, base: str) -> 'TelegramAPIServer':
|
||||
base = base.rstrip("/")
|
||||
return cls(
|
||||
base=f"{base}/bot{{token}}/{{method}}",
|
||||
file=f"{base}/file/bot{{token}}/{{path}}",
|
||||
)
|
||||
|
||||
|
||||
TELEGRAM_PRODUCTION = TelegramAPIServer.from_base("https://api.telegram.org")
|
||||
|
||||
|
||||
def check_token(token: str) -> bool:
|
||||
|
|
@ -80,7 +117,7 @@ def check_result(method_name: str, content_type: str, status_code: int, body: st
|
|||
exceptions.NotFound.detect(description)
|
||||
elif status_code == HTTPStatus.CONFLICT:
|
||||
exceptions.ConflictError.detect(description)
|
||||
elif status_code in [HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN]:
|
||||
elif status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
|
||||
exceptions.Unauthorized.detect(description)
|
||||
elif status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
|
||||
raise exceptions.NetworkError('File too large for uploading. '
|
||||
|
|
@ -92,11 +129,10 @@ def check_result(method_name: str, content_type: str, status_code: int, body: st
|
|||
raise exceptions.TelegramAPIError(f"{description} [{status_code}]")
|
||||
|
||||
|
||||
async def make_request(session, token, method, data=None, files=None, **kwargs):
|
||||
# log.debug(f"Make request: '{method}' with data: {data} and files {files}")
|
||||
async def make_request(session, server, token, method, data=None, files=None, **kwargs):
|
||||
log.debug('Make request: "%s" with data: "%r" and files "%r"', method, data, files)
|
||||
|
||||
url = Methods.api_url(token=token, method=method)
|
||||
url = server.api_url(token=token, method=method)
|
||||
|
||||
req = compose_data(data, files)
|
||||
try:
|
||||
|
|
@ -153,7 +189,7 @@ class Methods(Helper):
|
|||
"""
|
||||
Helper for Telegram API Methods listed on https://core.telegram.org/bots/api
|
||||
|
||||
List is updated to Bot API 4.9
|
||||
List is updated to Bot API 5.3
|
||||
"""
|
||||
mode = HelperMode.lowerCamelCase
|
||||
|
||||
|
|
@ -165,8 +201,11 @@ class Methods(Helper):
|
|||
|
||||
# Available methods
|
||||
GET_ME = Item() # getMe
|
||||
LOG_OUT = Item() # logOut
|
||||
CLOSE = Item() # close
|
||||
SEND_MESSAGE = Item() # sendMessage
|
||||
FORWARD_MESSAGE = Item() # forwardMessage
|
||||
COPY_MESSAGE = Item() # copyMessage
|
||||
SEND_PHOTO = Item() # sendPhoto
|
||||
SEND_AUDIO = Item() # sendAudio
|
||||
SEND_DOCUMENT = Item() # sendDocument
|
||||
|
|
@ -186,27 +225,34 @@ class Methods(Helper):
|
|||
GET_USER_PROFILE_PHOTOS = Item() # getUserProfilePhotos
|
||||
GET_FILE = Item() # getFile
|
||||
KICK_CHAT_MEMBER = Item() # kickChatMember
|
||||
BAN_CHAT_MEMBER = Item() # banChatMember
|
||||
UNBAN_CHAT_MEMBER = Item() # unbanChatMember
|
||||
RESTRICT_CHAT_MEMBER = Item() # restrictChatMember
|
||||
PROMOTE_CHAT_MEMBER = Item() # promoteChatMember
|
||||
SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE = Item() # setChatAdministratorCustomTitle
|
||||
SET_CHAT_PERMISSIONS = Item() # setChatPermissions
|
||||
EXPORT_CHAT_INVITE_LINK = Item() # exportChatInviteLink
|
||||
CREATE_CHAT_INVITE_LINK = Item() # createChatInviteLink
|
||||
EDIT_CHAT_INVITE_LINK = Item() # editChatInviteLink
|
||||
REVOKE_CHAT_INVITE_LINK = Item() # revokeChatInviteLink
|
||||
SET_CHAT_PHOTO = Item() # setChatPhoto
|
||||
DELETE_CHAT_PHOTO = Item() # deleteChatPhoto
|
||||
SET_CHAT_TITLE = Item() # setChatTitle
|
||||
SET_CHAT_DESCRIPTION = Item() # setChatDescription
|
||||
PIN_CHAT_MESSAGE = Item() # pinChatMessage
|
||||
UNPIN_CHAT_MESSAGE = Item() # unpinChatMessage
|
||||
UNPIN_ALL_CHAT_MESSAGES = Item() # unpinAllChatMessages
|
||||
LEAVE_CHAT = Item() # leaveChat
|
||||
GET_CHAT = Item() # getChat
|
||||
GET_CHAT_ADMINISTRATORS = Item() # getChatAdministrators
|
||||
GET_CHAT_MEMBERS_COUNT = Item() # getChatMembersCount
|
||||
GET_CHAT_MEMBER_COUNT = Item() # getChatMemberCount
|
||||
GET_CHAT_MEMBERS_COUNT = Item() # getChatMembersCount (renamed to getChatMemberCount)
|
||||
GET_CHAT_MEMBER = Item() # getChatMember
|
||||
SET_CHAT_STICKER_SET = Item() # setChatStickerSet
|
||||
DELETE_CHAT_STICKER_SET = Item() # deleteChatStickerSet
|
||||
ANSWER_CALLBACK_QUERY = Item() # answerCallbackQuery
|
||||
SET_MY_COMMANDS = Item() # setMyCommands
|
||||
DELETE_MY_COMMANDS = Item() # deleteMyCommands
|
||||
GET_MY_COMMANDS = Item() # getMyCommands
|
||||
|
||||
# Updating messages
|
||||
|
|
@ -242,25 +288,3 @@ class Methods(Helper):
|
|||
SEND_GAME = Item() # sendGame
|
||||
SET_GAME_SCORE = Item() # setGameScore
|
||||
GET_GAME_HIGH_SCORES = Item() # getGameHighScores
|
||||
|
||||
@staticmethod
|
||||
def api_url(token, method):
|
||||
"""
|
||||
Generate API URL with included token and method name
|
||||
|
||||
:param token:
|
||||
:param method:
|
||||
:return:
|
||||
"""
|
||||
return API_URL.format(token=token, method=method)
|
||||
|
||||
@staticmethod
|
||||
def file_url(token, path):
|
||||
"""
|
||||
Generate File URL with included token and file path
|
||||
|
||||
:param token:
|
||||
:param path:
|
||||
:return:
|
||||
"""
|
||||
return FILE_URL.format(token=token, path=path)
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@ import certifi
|
|||
from aiohttp.helpers import sentinel
|
||||
|
||||
from . import api
|
||||
from .api import TelegramAPIServer, TELEGRAM_PRODUCTION
|
||||
from ..types import ParseMode, base
|
||||
from ..utils import json
|
||||
from ..utils.auth_widget import check_integrity
|
||||
from ..utils.deprecated import deprecated
|
||||
|
||||
|
||||
class BaseBot:
|
||||
|
|
@ -33,7 +35,8 @@ class BaseBot:
|
|||
proxy_auth: Optional[aiohttp.BasicAuth] = None,
|
||||
validate_token: Optional[base.Boolean] = True,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
timeout: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]] = None
|
||||
timeout: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]] = None,
|
||||
server: TelegramAPIServer = TELEGRAM_PRODUCTION
|
||||
):
|
||||
"""
|
||||
Instructions how to get Bot token is found here: https://core.telegram.org/bots#3-how-do-i-create-a-bot
|
||||
|
|
@ -54,31 +57,29 @@ class BaseBot:
|
|||
:type parse_mode: :obj:`str`
|
||||
:param timeout: Request timeout
|
||||
:type timeout: :obj:`typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]]`
|
||||
:param server: Telegram Bot API Server endpoint.
|
||||
:type server: :obj:`TelegramAPIServer`
|
||||
:raise: when token is invalid throw an :obj:`aiogram.utils.exceptions.ValidationError`
|
||||
"""
|
||||
self._main_loop = loop
|
||||
|
||||
# Authentication
|
||||
if validate_token:
|
||||
api.check_token(token)
|
||||
self._token = None
|
||||
self.__token = token
|
||||
self.id = int(token.split(sep=':')[0])
|
||||
self.server = server
|
||||
|
||||
self.proxy = proxy
|
||||
self.proxy_auth = proxy_auth
|
||||
|
||||
# Asyncio loop instance
|
||||
if loop is None:
|
||||
loop = asyncio.get_event_loop()
|
||||
self.loop = loop
|
||||
|
||||
# aiohttp main session
|
||||
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
||||
|
||||
self._session: Optional[aiohttp.ClientSession] = None
|
||||
self._connector_class: Type[aiohttp.TCPConnector] = aiohttp.TCPConnector
|
||||
self._connector_init = dict(
|
||||
limit=connections_limit, ssl=ssl_context, loop=self.loop
|
||||
)
|
||||
self._connector_init = dict(limit=connections_limit, ssl=ssl_context)
|
||||
|
||||
if isinstance(proxy, str) and (proxy.startswith('socks5://') or proxy.startswith('socks4://')):
|
||||
from aiohttp_socks import SocksConnector
|
||||
|
|
@ -106,11 +107,15 @@ class BaseBot:
|
|||
|
||||
def get_new_session(self) -> aiohttp.ClientSession:
|
||||
return aiohttp.ClientSession(
|
||||
connector=self._connector_class(**self._connector_init),
|
||||
loop=self.loop,
|
||||
connector=self._connector_class(**self._connector_init, loop=self._main_loop),
|
||||
loop=self._main_loop,
|
||||
json_serialize=json.dumps
|
||||
)
|
||||
|
||||
@property
|
||||
def loop(self) -> Optional[asyncio.AbstractEventLoop]:
|
||||
return self._main_loop
|
||||
|
||||
@property
|
||||
def session(self) -> Optional[aiohttp.ClientSession]:
|
||||
if self._session is None or self._session.closed:
|
||||
|
|
@ -174,6 +179,8 @@ class BaseBot:
|
|||
finally:
|
||||
self._ctx_token.reset(token)
|
||||
|
||||
@deprecated("This method's behavior will be changed in aiogram v3.0. "
|
||||
"More info: https://core.telegram.org/bots/api#close", stacklevel=3)
|
||||
async def close(self):
|
||||
"""
|
||||
Close all client sessions
|
||||
|
|
@ -198,7 +205,7 @@ class BaseBot:
|
|||
:rtype: Union[List, Dict]
|
||||
:raise: :obj:`aiogram.exceptions.TelegramApiError`
|
||||
"""
|
||||
return await api.make_request(self.session, self.__token, method, data, files,
|
||||
return await api.make_request(self.session, self.server, self.__token, method, data, files,
|
||||
proxy=self.proxy, proxy_auth=self.proxy_auth, timeout=self.timeout, **kwargs)
|
||||
|
||||
async def download_file(self, file_path: base.String,
|
||||
|
|
@ -238,7 +245,7 @@ class BaseBot:
|
|||
return dest
|
||||
|
||||
def get_file_url(self, file_path):
|
||||
return api.Methods.file_url(token=self.__token, path=file_path)
|
||||
return self.server.file_url(token=self.__token, path=file_path)
|
||||
|
||||
async def send_file(self, file_type, method, file, payload) -> Union[Dict, base.Boolean]:
|
||||
"""
|
||||
|
|
|
|||
1920
aiogram/bot/bot.py
1920
aiogram/bot/bot.py
File diff suppressed because it is too large
Load diff
|
|
@ -35,7 +35,7 @@ class MemoryStorage(BaseStorage):
|
|||
user: typing.Union[str, int, None] = None,
|
||||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
||||
chat, user = self.resolve_address(chat=chat, user=user)
|
||||
return self.data[chat][user]['state']
|
||||
return self.data[chat][user].get("state", self.resolve_state(default))
|
||||
|
||||
async def get_data(self, *,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
|
|
@ -58,7 +58,7 @@ class MemoryStorage(BaseStorage):
|
|||
user: typing.Union[str, int, None] = None,
|
||||
state: typing.AnyStr = None):
|
||||
chat, user = self.resolve_address(chat=chat, user=user)
|
||||
self.data[chat][user]['state'] = state
|
||||
self.data[chat][user]['state'] = self.resolve_state(state)
|
||||
|
||||
async def set_data(self, *,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
|
|
@ -66,6 +66,7 @@ class MemoryStorage(BaseStorage):
|
|||
data: typing.Dict = None):
|
||||
chat, user = self.resolve_address(chat=chat, user=user)
|
||||
self.data[chat][user]['data'] = copy.deepcopy(data)
|
||||
self._cleanup(chat, user)
|
||||
|
||||
async def reset_state(self, *,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
|
|
@ -74,6 +75,7 @@ class MemoryStorage(BaseStorage):
|
|||
await self.set_state(chat=chat, user=user, state=None)
|
||||
if with_data:
|
||||
await self.set_data(chat=chat, user=user, data={})
|
||||
self._cleanup(chat, user)
|
||||
|
||||
def has_bucket(self):
|
||||
return True
|
||||
|
|
@ -91,6 +93,7 @@ class MemoryStorage(BaseStorage):
|
|||
bucket: typing.Dict = None):
|
||||
chat, user = self.resolve_address(chat=chat, user=user)
|
||||
self.data[chat][user]['bucket'] = copy.deepcopy(bucket)
|
||||
self._cleanup(chat, user)
|
||||
|
||||
async def update_bucket(self, *,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
|
|
@ -100,3 +103,10 @@ class MemoryStorage(BaseStorage):
|
|||
bucket = {}
|
||||
chat, user = self.resolve_address(chat=chat, user=user)
|
||||
self.data[chat][user]['bucket'].update(bucket, **kwargs)
|
||||
|
||||
def _cleanup(self, chat, user):
|
||||
chat, user = self.resolve_address(chat=chat, user=user)
|
||||
if self.data[chat][user] == {'state': None, 'data': {}, 'bucket': {}}:
|
||||
del self.data[chat][user]
|
||||
if not self.data[chat]:
|
||||
del self.data[chat]
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ This module has mongo storage for finite-state machine
|
|||
|
||||
from typing import Union, Dict, Optional, List, Tuple, AnyStr
|
||||
|
||||
import pymongo
|
||||
|
||||
try:
|
||||
import pymongo
|
||||
import motor
|
||||
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
|
||||
except ModuleNotFoundError as e:
|
||||
|
|
@ -26,6 +26,7 @@ COLLECTIONS = (STATE, DATA, BUCKET)
|
|||
class MongoStorage(BaseStorage):
|
||||
"""
|
||||
Mongo-based storage for FSM.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: python3
|
||||
|
|
@ -39,7 +40,6 @@ class MongoStorage(BaseStorage):
|
|||
|
||||
await dp.storage.close()
|
||||
await dp.storage.wait_closed()
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, host='localhost', port=27017, db_name='aiogram_fsm', uri=None,
|
||||
|
|
@ -65,7 +65,7 @@ class MongoStorage(BaseStorage):
|
|||
try:
|
||||
self._mongo = AsyncIOMotorClient(self._uri)
|
||||
except pymongo.errors.ConfigurationError as e:
|
||||
if "query() got an unexpected keyword argument 'lifetime'" in e.args[0]:
|
||||
if "query() got an unexpected keyword argument 'lifetime'" in e.args[0]:
|
||||
import logging
|
||||
logger = logging.getLogger("aiogram")
|
||||
logger.warning("Run `pip install dnspython==1.16.0` in order to fix ConfigurationError. More information: https://github.com/mongodb/mongo-python-driver/pull/423#issuecomment-528998245")
|
||||
|
|
@ -114,7 +114,9 @@ class MongoStorage(BaseStorage):
|
|||
async def wait_closed(self):
|
||||
return True
|
||||
|
||||
async def set_state(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
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()
|
||||
|
|
@ -122,8 +124,11 @@ class MongoStorage(BaseStorage):
|
|||
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)
|
||||
await db[STATE].update_one(
|
||||
filter={'chat': chat, 'user': user},
|
||||
update={'$set': {'state': self.resolve_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]:
|
||||
|
|
@ -131,15 +136,17 @@ class MongoStorage(BaseStorage):
|
|||
db = await self.get_db()
|
||||
result = await db[STATE].find_one(filter={'chat': chat, 'user': user})
|
||||
|
||||
return result.get('state') if result else default
|
||||
return result.get('state') if result else self.resolve_state(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)
|
||||
if not data:
|
||||
await db[DATA].delete_one(filter={'chat': chat, 'user': user})
|
||||
else:
|
||||
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:
|
||||
|
|
@ -206,12 +213,5 @@ class MongoStorage(BaseStorage):
|
|||
: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
|
||||
return [(int(item['chat']), int(item['user'])) for item in items]
|
||||
|
|
|
|||
|
|
@ -110,24 +110,29 @@ class RedisStorage(BaseStorage):
|
|||
chat, user = self.check_address(chat=chat, user=user)
|
||||
addr = f"fsm:{chat}:{user}"
|
||||
|
||||
record = {'state': state, 'data': data, 'bucket': bucket}
|
||||
|
||||
conn = await self.redis()
|
||||
await conn.execute('SET', addr, json.dumps(record))
|
||||
if state is None and data == bucket == {}:
|
||||
await conn.execute('DEL', addr)
|
||||
else:
|
||||
record = {'state': state, 'data': data, 'bucket': bucket}
|
||||
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]:
|
||||
record = await self.get_record(chat=chat, user=user)
|
||||
return record['state']
|
||||
return record.get('state', self.resolve_state(default))
|
||||
|
||||
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']
|
||||
|
||||
async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = 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)
|
||||
state = self.resolve_state(state)
|
||||
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,
|
||||
|
|
@ -208,7 +213,7 @@ class RedisStorage2(BaseStorage):
|
|||
|
||||
.. code-block:: python3
|
||||
|
||||
storage = RedisStorage('localhost', 6379, db=5, pool_size=10, prefix='my_fsm_key')
|
||||
storage = RedisStorage2('localhost', 6379, db=5, pool_size=10, prefix='my_fsm_key')
|
||||
dp = Dispatcher(bot, storage=storage)
|
||||
|
||||
And need to close Redis connection when shutdown
|
||||
|
|
@ -219,7 +224,7 @@ class RedisStorage2(BaseStorage):
|
|||
await dp.storage.wait_closed()
|
||||
|
||||
"""
|
||||
def __init__(self, host: str = 'localhost', port=6379, db=None, password=None,
|
||||
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,
|
||||
|
|
@ -274,7 +279,7 @@ class RedisStorage2(BaseStorage):
|
|||
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 self.resolve_state(default)
|
||||
|
||||
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:
|
||||
|
|
@ -294,14 +299,17 @@ class RedisStorage2(BaseStorage):
|
|||
if state is None:
|
||||
await redis.delete(key)
|
||||
else:
|
||||
await redis.set(key, state, expire=self._state_ttl)
|
||||
await redis.set(key, self.resolve_state(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):
|
||||
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), expire=self._data_ttl)
|
||||
if data:
|
||||
await redis.set(key, json.dumps(data), expire=self._data_ttl)
|
||||
else:
|
||||
await redis.delete(key)
|
||||
|
||||
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
data: typing.Dict = None, **kwargs):
|
||||
|
|
@ -329,7 +337,10 @@ class RedisStorage2(BaseStorage):
|
|||
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), expire=self._bucket_ttl)
|
||||
if bucket:
|
||||
await redis.set(key, json.dumps(bucket), expire=self._bucket_ttl)
|
||||
else:
|
||||
await redis.delete(key)
|
||||
|
||||
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from rethinkdb.asyncio_net.net_asyncio import Connection
|
|||
|
||||
from ...dispatcher.storage import BaseStorage
|
||||
|
||||
__all__ = ['RethinkDBStorage']
|
||||
__all__ = ('RethinkDBStorage',)
|
||||
|
||||
r = rethinkdb.RethinkDB()
|
||||
r.set_loop_type('asyncio')
|
||||
|
|
@ -19,16 +19,17 @@ class RethinkDBStorage(BaseStorage):
|
|||
|
||||
Usage:
|
||||
|
||||
..code-block:: python3
|
||||
.. code-block:: python3
|
||||
|
||||
storage = RethinkDBStorage(db='aiogram', table='aiogram', user='aiogram', password='aiogram_secret')
|
||||
dispatcher = Dispatcher(bot, storage=storage)
|
||||
|
||||
And need to close connection when shutdown
|
||||
|
||||
..code-clock:: python3
|
||||
.. code-block:: python3
|
||||
|
||||
await storage.close()
|
||||
await storage.wait_closed()
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -54,7 +55,7 @@ class RethinkDBStorage(BaseStorage):
|
|||
self._ssl = ssl or {}
|
||||
self._loop = loop
|
||||
|
||||
self._conn: typing.Union[Connection, None] = None
|
||||
self._conn: typing.Optional[Connection] = None
|
||||
|
||||
async def connect(self) -> Connection:
|
||||
"""
|
||||
|
|
@ -94,7 +95,9 @@ class RethinkDBStorage(BaseStorage):
|
|||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||
async with self.connection() as conn:
|
||||
return await r.table(self._table).get(chat)[user]['state'].default(default or None).run(conn)
|
||||
return await r.table(self._table).get(chat)[user]['state'].default(
|
||||
self.resolve_state(default) or None
|
||||
).run(conn)
|
||||
|
||||
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:
|
||||
|
|
@ -102,11 +105,16 @@ class RethinkDBStorage(BaseStorage):
|
|||
async with self.connection() as conn:
|
||||
return await r.table(self._table).get(chat)[user]['data'].default(default or {}).run(conn)
|
||||
|
||||
async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
async def set_state(self, *,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
state: typing.Optional[typing.AnyStr] = None):
|
||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||
async with self.connection() as conn:
|
||||
await r.table(self._table).insert({'id': chat, user: {'state': state}}, conflict="update").run(conn)
|
||||
await r.table(self._table).insert(
|
||||
{'id': chat, user: {'state': self.resolve_state(state)}},
|
||||
conflict="update",
|
||||
).run(conn)
|
||||
|
||||
async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
data: typing.Dict = None):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import asyncio
|
||||
|
||||
from aiogram.dispatcher.middlewares import BaseMiddleware
|
||||
|
||||
|
||||
|
|
@ -14,7 +16,7 @@ class EnvironmentMiddleware(BaseMiddleware):
|
|||
data.update(
|
||||
bot=dp.bot,
|
||||
dispatcher=dp,
|
||||
loop=dp.loop
|
||||
loop=dp.loop or asyncio.get_event_loop()
|
||||
)
|
||||
if self.context:
|
||||
data.update(self.context)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import gettext
|
||||
import os
|
||||
from contextvars import ContextVar
|
||||
from typing import Any, Dict, Tuple
|
||||
from typing import Any, Dict, Tuple, Optional
|
||||
|
||||
from babel import Locale
|
||||
from babel.support import LazyProxy
|
||||
|
|
@ -59,13 +59,13 @@ class I18nMiddleware(BaseMiddleware):
|
|||
with open(mo_path, 'rb') as fp:
|
||||
translations[name] = gettext.GNUTranslations(fp)
|
||||
elif os.path.exists(mo_path[:-2] + 'po'):
|
||||
raise RuntimeError(f"Found locale '{name} but this language is not compiled!")
|
||||
raise RuntimeError(f"Found locale '{name}' but this language is not compiled!")
|
||||
|
||||
return translations
|
||||
|
||||
def reload(self):
|
||||
"""
|
||||
Hot reload locles
|
||||
Hot reload locales
|
||||
"""
|
||||
self.locales = self.find_locales()
|
||||
|
||||
|
|
@ -119,22 +119,24 @@ class I18nMiddleware(BaseMiddleware):
|
|||
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:
|
||||
async def get_user_locale(self, action: str, args: Tuple[Any]) -> Optional[str]:
|
||||
"""
|
||||
User locale getter
|
||||
You can override the method if you want to use different way of getting user language.
|
||||
You can override the method if you want to use different way of
|
||||
getting user language.
|
||||
|
||||
:param action: event name
|
||||
:param args: event arguments
|
||||
:return: locale name
|
||||
:return: locale name or None
|
||||
"""
|
||||
user: types.User = types.User.get_current()
|
||||
locale: Locale = user.locale
|
||||
user: Optional[types.User] = types.User.get_current()
|
||||
locale: Optional[Locale] = user.locale if user else None
|
||||
|
||||
if locale:
|
||||
if locale and locale.language in self.locales:
|
||||
*_, data = args
|
||||
language = data['locale'] = locale.language
|
||||
return language
|
||||
return self.default
|
||||
|
||||
async def trigger(self, action, args):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -160,6 +160,26 @@ class LoggingMiddleware(BaseMiddleware):
|
|||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} poll answer [ID:{poll_answer.poll_id}] "
|
||||
f"from user [ID:{poll_answer.user.id}]")
|
||||
|
||||
async def on_pre_process_my_chat_member(self, my_chat_member_update, data):
|
||||
self.logger.info(f"Received chat member update "
|
||||
f"for user [ID:{my_chat_member_update.from_user.id}]. "
|
||||
f"Old state: {my_chat_member_update.old_chat_member.to_python()} "
|
||||
f"New state: {my_chat_member_update.new_chat_member.to_python()} ")
|
||||
|
||||
async def on_post_process_my_chat_member(self, my_chat_member_update, results, data):
|
||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} my_chat_member "
|
||||
f"for user [ID:{my_chat_member_update.from_user.id}]")
|
||||
|
||||
async def on_pre_process_chat_member(self, chat_member_update, data):
|
||||
self.logger.info(f"Received chat member update "
|
||||
f"for user [ID:{chat_member_update.from_user.id}]. "
|
||||
f"Old state: {chat_member_update.old_chat_member.to_python()} "
|
||||
f"New state: {chat_member_update.new_chat_member.to_python()} ")
|
||||
|
||||
async def on_post_process_chat_member(self, chat_member_update, results, data):
|
||||
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} chat_member "
|
||||
f"for user [ID:{chat_member_update.from_user.id}]")
|
||||
|
||||
|
||||
class LoggingFilter(logging.Filter):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from . import storage
|
|||
from . import webhook
|
||||
from .dispatcher import Dispatcher, FSMContext, DEFAULT_RATE_LIMIT
|
||||
|
||||
__all__ = [
|
||||
__all__ = (
|
||||
'DEFAULT_RATE_LIMIT',
|
||||
'Dispatcher',
|
||||
'FSMContext',
|
||||
|
|
@ -14,4 +14,4 @@ __all__ = [
|
|||
'middlewares',
|
||||
'storage',
|
||||
'webhook'
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from aiohttp.helpers import sentinel
|
|||
from aiogram.utils.deprecated import renamed_argument
|
||||
from .filters import Command, ContentTypeFilter, ExceptionsFilter, FiltersFactory, HashTag, Regexp, \
|
||||
RegexpCommandsFilter, StateFilter, Text, IDFilter, AdminFilter, IsReplyFilter, ForwardedMessageFilter, \
|
||||
IsSenderContact, ChatTypeFilter, AbstractFilter
|
||||
IsSenderContact, ChatTypeFilter, MediaGroupFilter, AbstractFilter
|
||||
from .handler import Handler
|
||||
from .middlewares import MiddlewareManager
|
||||
from .storage import BaseStorage, DELTA, DisabledStorage, EXCEEDED_COUNT, FSMContext, \
|
||||
|
|
@ -27,6 +27,13 @@ log = logging.getLogger(__name__)
|
|||
DEFAULT_RATE_LIMIT = .1
|
||||
|
||||
|
||||
def _ensure_loop(x: "asyncio.AbstractEventLoop"):
|
||||
assert isinstance(
|
||||
x, asyncio.AbstractEventLoop
|
||||
), f"Loop must be the implementation of {asyncio.AbstractEventLoop!r}, " \
|
||||
f"not {type(x)!r}"
|
||||
|
||||
|
||||
class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||
"""
|
||||
Simple Updates dispatcher
|
||||
|
|
@ -43,15 +50,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
if not isinstance(bot, Bot):
|
||||
raise TypeError(f"Argument 'bot' must be an instance of Bot, not '{type(bot).__name__}'")
|
||||
|
||||
if loop is None:
|
||||
loop = bot.loop
|
||||
if storage is None:
|
||||
storage = DisabledStorage()
|
||||
if filters_factory is None:
|
||||
filters_factory = FiltersFactory(self)
|
||||
|
||||
self.bot: Bot = bot
|
||||
self.loop = loop
|
||||
if loop is not None:
|
||||
_ensure_loop(loop)
|
||||
self._main_loop = loop
|
||||
self.storage = storage
|
||||
self.run_tasks_by_default = run_tasks_by_default
|
||||
|
||||
|
|
@ -71,6 +78,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
self.pre_checkout_query_handlers = Handler(self, middleware_key='pre_checkout_query')
|
||||
self.poll_handlers = Handler(self, middleware_key='poll')
|
||||
self.poll_answer_handlers = Handler(self, middleware_key='poll_answer')
|
||||
self.my_chat_member_handlers = Handler(self, middleware_key='my_chat_member')
|
||||
self.chat_member_handlers = Handler(self, middleware_key='chat_member')
|
||||
self.errors_handlers = Handler(self, once=False, middleware_key='error')
|
||||
|
||||
self.middleware = MiddlewareManager(self)
|
||||
|
|
@ -79,10 +88,27 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
|
||||
self._polling = False
|
||||
self._closed = True
|
||||
self._close_waiter = loop.create_future()
|
||||
self._dispatcher_close_waiter = None
|
||||
|
||||
self._setup_filters()
|
||||
|
||||
@property
|
||||
def loop(self) -> typing.Optional[asyncio.AbstractEventLoop]:
|
||||
# for the sake of backward compatibility
|
||||
# lib internally must delegate tasks with respect to _main_loop attribute
|
||||
# however should never be used by the library itself
|
||||
# use more generic approaches from asyncio's namespace
|
||||
return self._main_loop
|
||||
|
||||
@property
|
||||
def _close_waiter(self) -> "asyncio.Future":
|
||||
if self._dispatcher_close_waiter is None:
|
||||
if self._main_loop is not None:
|
||||
self._dispatcher_close_waiter = self._main_loop.create_future()
|
||||
else:
|
||||
self._dispatcher_close_waiter = asyncio.get_event_loop().create_future()
|
||||
return self._dispatcher_close_waiter
|
||||
|
||||
def _setup_filters(self):
|
||||
filters_factory = self.filters_factory
|
||||
|
||||
|
|
@ -139,6 +165,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers,
|
||||
self.inline_query_handlers,
|
||||
self.chat_member_handlers,
|
||||
])
|
||||
filters_factory.bind(IDFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
|
|
@ -147,6 +174,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers,
|
||||
self.inline_query_handlers,
|
||||
self.chat_member_handlers,
|
||||
self.my_chat_member_handlers,
|
||||
])
|
||||
filters_factory.bind(IsReplyFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
|
|
@ -172,6 +201,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers,
|
||||
self.my_chat_member_handlers,
|
||||
self.chat_member_handlers
|
||||
])
|
||||
filters_factory.bind(MediaGroupFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers
|
||||
])
|
||||
|
||||
def __del__(self):
|
||||
|
|
@ -195,9 +232,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:return:
|
||||
"""
|
||||
if fast:
|
||||
tasks = []
|
||||
for update in updates:
|
||||
tasks.append(self.updates_handler.notify(update))
|
||||
tasks = [self.updates_handler.notify(update) for update in updates]
|
||||
return await asyncio.gather(*tasks)
|
||||
|
||||
results = []
|
||||
|
|
@ -262,6 +297,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
types.PollAnswer.set_current(update.poll_answer)
|
||||
types.User.set_current(update.poll_answer.user)
|
||||
return await self.poll_answer_handlers.notify(update.poll_answer)
|
||||
if update.my_chat_member:
|
||||
types.ChatMemberUpdated.set_current(update.my_chat_member)
|
||||
types.User.set_current(update.my_chat_member.from_user)
|
||||
return await self.my_chat_member_handlers.notify(update.my_chat_member)
|
||||
if update.chat_member:
|
||||
types.ChatMemberUpdated.set_current(update.chat_member)
|
||||
types.User.set_current(update.chat_member.from_user)
|
||||
return await self.chat_member_handlers.notify(update.chat_member)
|
||||
except Exception as e:
|
||||
err = await self.errors_handlers.notify(update, e)
|
||||
if err:
|
||||
|
|
@ -282,13 +325,20 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
|
||||
return await self.bot.delete_webhook()
|
||||
|
||||
def _loop_create_task(self, coro):
|
||||
if self._main_loop is None:
|
||||
return asyncio.create_task(coro)
|
||||
_ensure_loop(self._main_loop)
|
||||
return self._main_loop.create_task(coro)
|
||||
|
||||
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,
|
||||
allowed_updates: typing.Optional[typing.List[str]] = None):
|
||||
"""
|
||||
Start long-polling
|
||||
|
||||
|
|
@ -297,6 +347,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
:param limit:
|
||||
:param reset_webhook:
|
||||
:param fast:
|
||||
:param error_sleep:
|
||||
:param allowed_updates:
|
||||
:return:
|
||||
"""
|
||||
if self._polling:
|
||||
|
|
@ -325,10 +377,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
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,
|
||||
allowed_updates=allowed_updates
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
except:
|
||||
except Exception as e:
|
||||
log.exception('Cause exception while getting updates.')
|
||||
await asyncio.sleep(error_sleep)
|
||||
continue
|
||||
|
|
@ -337,7 +394,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
log.debug(f"Received {len(updates)} updates.")
|
||||
offset = updates[-1].update_id + 1
|
||||
|
||||
self.loop.create_task(self._process_polling_updates(updates, fast))
|
||||
self._loop_create_task(self._process_polling_updates(updates, fast))
|
||||
|
||||
if relax:
|
||||
await asyncio.sleep(relax)
|
||||
|
|
@ -381,7 +438,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
|
||||
:return:
|
||||
"""
|
||||
await asyncio.shield(self._close_waiter, loop=self.loop)
|
||||
await asyncio.shield(self._close_waiter)
|
||||
|
||||
def is_polling(self):
|
||||
"""
|
||||
|
|
@ -974,6 +1031,118 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
|
||||
return decorator
|
||||
|
||||
def register_my_chat_member_handler(self,
|
||||
callback: typing.Callable,
|
||||
*custom_filters,
|
||||
run_task: typing.Optional[bool] = None,
|
||||
**kwargs) -> None:
|
||||
"""
|
||||
Register handler for my_chat_member
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
dp.register_my_chat_member_handler(some_my_chat_member_handler)
|
||||
|
||||
:param callback:
|
||||
:param custom_filters:
|
||||
:param run_task: run callback in task (no wait results)
|
||||
:param kwargs:
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.my_chat_member_handlers,
|
||||
*custom_filters,
|
||||
**kwargs,
|
||||
)
|
||||
self.my_chat_member_handlers.register(
|
||||
handler=self._wrap_async_task(callback, run_task),
|
||||
filters=filters_set,
|
||||
)
|
||||
|
||||
def my_chat_member_handler(self, *custom_filters, run_task=None, **kwargs):
|
||||
"""
|
||||
Decorator for my_chat_member handler
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
@dp.my_chat_member_handler()
|
||||
async def some_handler(my_chat_member: types.ChatMemberUpdated)
|
||||
|
||||
:param custom_filters:
|
||||
:param run_task: run callback in task (no wait results)
|
||||
:param kwargs:
|
||||
"""
|
||||
|
||||
def decorator(callback):
|
||||
self.register_my_chat_member_handler(
|
||||
callback,
|
||||
*custom_filters,
|
||||
run_task=run_task,
|
||||
**kwargs,
|
||||
)
|
||||
return callback
|
||||
|
||||
return decorator
|
||||
|
||||
def register_chat_member_handler(self,
|
||||
callback: typing.Callable,
|
||||
*custom_filters,
|
||||
run_task: typing.Optional[bool] = None,
|
||||
**kwargs) -> None:
|
||||
"""
|
||||
Register handler for chat_member
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
dp.register_chat_member_handler(some_chat_member_handler)
|
||||
|
||||
:param callback:
|
||||
:param custom_filters:
|
||||
:param run_task: run callback in task (no wait results)
|
||||
:param kwargs:
|
||||
"""
|
||||
filters_set = self.filters_factory.resolve(
|
||||
self.chat_member_handlers,
|
||||
*custom_filters,
|
||||
**kwargs,
|
||||
)
|
||||
self.chat_member_handlers.register(
|
||||
handler=self._wrap_async_task(callback, run_task),
|
||||
filters=filters_set,
|
||||
)
|
||||
|
||||
def chat_member_handler(self, *custom_filters, run_task=None, **kwargs):
|
||||
"""
|
||||
Decorator for chat_member handler
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
@dp.chat_member_handler()
|
||||
async def some_handler(chat_member: types.ChatMemberUpdated)
|
||||
|
||||
:param custom_filters:
|
||||
:param run_task: run callback in task (no wait results)
|
||||
:param kwargs:
|
||||
"""
|
||||
|
||||
def decorator(callback):
|
||||
self.register_chat_member_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):
|
||||
"""
|
||||
Register handler for errors
|
||||
|
|
@ -1053,8 +1222,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
if rate is None:
|
||||
rate = self.throttling_rate_limit
|
||||
if user_id is None and chat_id is None:
|
||||
user_id = types.User.get_current().id
|
||||
chat_id = types.Chat.get_current().id
|
||||
chat_obj = types.Chat.get_current()
|
||||
chat_id = chat_obj.id if chat_obj else None
|
||||
|
||||
user_obj = types.User.get_current()
|
||||
user_id = user_obj.id if user_obj else None
|
||||
|
||||
# Detect current time
|
||||
now = time.time()
|
||||
|
|
@ -1105,8 +1277,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
raise RuntimeError('This storage does not provide Leaky Bucket')
|
||||
|
||||
if user_id is None and chat_id is None:
|
||||
user_id = types.User.get_current()
|
||||
chat_id = types.Chat.get_current()
|
||||
chat_obj = types.Chat.get_current()
|
||||
chat_id = chat_obj.id if chat_obj else None
|
||||
|
||||
user_obj = types.User.get_current()
|
||||
user_id = user_obj.id if user_obj else None
|
||||
|
||||
bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
|
||||
data = bucket.get(key, {})
|
||||
|
|
@ -1127,8 +1302,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
raise RuntimeError('This storage does not provide Leaky Bucket')
|
||||
|
||||
if user_id is None and chat_id is None:
|
||||
user_id = types.User.get_current()
|
||||
chat_id = types.Chat.get_current()
|
||||
chat_obj = types.Chat.get_current()
|
||||
chat_id = chat_obj.id if chat_obj else None
|
||||
|
||||
user_obj = types.User.get_current()
|
||||
user_id = user_obj.id if user_obj else None
|
||||
|
||||
bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
|
||||
if bucket and key in bucket:
|
||||
|
|
@ -1158,15 +1336,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
try:
|
||||
response = task.result()
|
||||
except Exception as e:
|
||||
self.loop.create_task(
|
||||
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))
|
||||
self._loop_create_task(response.execute_response(self.bot))
|
||||
|
||||
@functools.wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
task = self.loop.create_task(func(*args, **kwargs))
|
||||
task = self._loop_create_task(func(*args, **kwargs))
|
||||
task.add_done_callback(process_response)
|
||||
|
||||
return wrapper
|
||||
|
|
@ -1213,29 +1391,26 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
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
|
||||
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
|
||||
)
|
||||
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,38 +1,39 @@
|
|||
from .builtin import Command, CommandHelp, CommandPrivacy, CommandSettings, CommandStart, ContentTypeFilter, \
|
||||
ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, \
|
||||
Text, IDFilter, AdminFilter, IsReplyFilter, IsSenderContact, ForwardedMessageFilter, \
|
||||
ChatTypeFilter
|
||||
ChatTypeFilter, MediaGroupFilter
|
||||
from .factory import FiltersFactory
|
||||
from .filters import AbstractFilter, BoundFilter, Filter, FilterNotPassed, FilterRecord, execute_filter, \
|
||||
check_filters, get_filter_spec, get_filters_spec
|
||||
|
||||
__all__ = [
|
||||
'AbstractFilter',
|
||||
'BoundFilter',
|
||||
__all__ = (
|
||||
'Command',
|
||||
'CommandStart',
|
||||
'CommandHelp',
|
||||
'CommandPrivacy',
|
||||
'CommandSettings',
|
||||
'CommandStart',
|
||||
'ContentTypeFilter',
|
||||
'ExceptionsFilter',
|
||||
'HashTag',
|
||||
'Filter',
|
||||
'FilterNotPassed',
|
||||
'FilterRecord',
|
||||
'FiltersFactory',
|
||||
'RegexpCommandsFilter',
|
||||
'Regexp',
|
||||
'RegexpCommandsFilter',
|
||||
'StateFilter',
|
||||
'Text',
|
||||
'IDFilter',
|
||||
'AdminFilter',
|
||||
'IsReplyFilter',
|
||||
'IsSenderContact',
|
||||
'AdminFilter',
|
||||
'get_filter_spec',
|
||||
'get_filters_spec',
|
||||
'execute_filter',
|
||||
'check_filters',
|
||||
'ForwardedMessageFilter',
|
||||
'ChatTypeFilter',
|
||||
]
|
||||
'MediaGroupFilter',
|
||||
'FiltersFactory',
|
||||
'AbstractFilter',
|
||||
'BoundFilter',
|
||||
'Filter',
|
||||
'FilterNotPassed',
|
||||
'FilterRecord',
|
||||
'execute_filter',
|
||||
'check_filters',
|
||||
'get_filter_spec',
|
||||
'get_filters_spec',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import typing
|
|||
import warnings
|
||||
from contextvars import ContextVar
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, Iterable, Optional, Union
|
||||
from typing import Any, Dict, Iterable, List, Optional, Union
|
||||
|
||||
from babel.support import LazyProxy
|
||||
|
||||
from aiogram import types
|
||||
from aiogram.dispatcher.filters.filters import BoundFilter, Filter
|
||||
from aiogram.types import CallbackQuery, ChatType, InlineQuery, Message, Poll
|
||||
from aiogram.types import CallbackQuery, ChatType, InlineQuery, Message, Poll, ChatMemberUpdated
|
||||
|
||||
ChatIDArgumentType = typing.Union[typing.Iterable[typing.Union[int, str]], str, int]
|
||||
|
||||
|
|
@ -110,7 +110,8 @@ class Command(Filter):
|
|||
if not text:
|
||||
return False
|
||||
|
||||
full_command = text.split()[0]
|
||||
full_command, *args_list = text.split(maxsplit=1)
|
||||
args = args_list[0] if args_list else None
|
||||
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():
|
||||
|
|
@ -120,7 +121,7 @@ class Command(Filter):
|
|||
if (command.lower() if ignore_case else command) not in commands:
|
||||
return False
|
||||
|
||||
return {'command': cls.CommandObj(command=command, prefix=prefix, mention=mention)}
|
||||
return {'command': cls.CommandObj(command=command, prefix=prefix, mention=mention, args=args)}
|
||||
|
||||
@dataclass
|
||||
class CommandObj:
|
||||
|
|
@ -278,9 +279,10 @@ class Text(Filter):
|
|||
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))
|
||||
equals, contains, endswith, startswith = map(
|
||||
lambda e: [e] if isinstance(e, (str, LazyProxy)) else e,
|
||||
(equals, contains, endswith, startswith),
|
||||
)
|
||||
self.equals = equals
|
||||
self.contains = contains
|
||||
self.endswith = endswith
|
||||
|
|
@ -529,6 +531,8 @@ class StateFilter(BoundFilter):
|
|||
self.states = states
|
||||
|
||||
def get_target(self, obj):
|
||||
if isinstance(obj, CallbackQuery):
|
||||
return getattr(getattr(getattr(obj, 'message', None),'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):
|
||||
|
|
@ -604,7 +608,7 @@ class IDFilter(Filter):
|
|||
|
||||
return result
|
||||
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]):
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, ChatMemberUpdated]):
|
||||
if isinstance(obj, Message):
|
||||
user_id = None
|
||||
if obj.from_user is not None:
|
||||
|
|
@ -619,6 +623,9 @@ class IDFilter(Filter):
|
|||
elif isinstance(obj, InlineQuery):
|
||||
user_id = obj.from_user.id
|
||||
chat_id = None
|
||||
elif isinstance(obj, ChatMemberUpdated):
|
||||
user_id = obj.from_user.id
|
||||
chat_id = obj.chat.id
|
||||
else:
|
||||
return False
|
||||
|
||||
|
|
@ -663,19 +670,21 @@ class AdminFilter(Filter):
|
|||
|
||||
return result
|
||||
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]) -> bool:
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, ChatMemberUpdated]) -> bool:
|
||||
user_id = obj.from_user.id
|
||||
|
||||
if self._check_current:
|
||||
if isinstance(obj, Message):
|
||||
message = obj
|
||||
chat = obj.chat
|
||||
elif isinstance(obj, CallbackQuery) and obj.message:
|
||||
message = obj.message
|
||||
chat = obj.message.chat
|
||||
elif isinstance(obj, ChatMemberUpdated):
|
||||
chat = obj.chat
|
||||
else:
|
||||
return False
|
||||
if ChatType.is_private(message): # there is no admin in private chats
|
||||
if chat.type == ChatType.PRIVATE: # there is no admin in private chats
|
||||
return False
|
||||
chat_ids = [message.chat.id]
|
||||
chat_ids = [chat.id]
|
||||
else:
|
||||
chat_ids = self._chat_ids
|
||||
|
||||
|
|
@ -719,13 +728,32 @@ class ChatTypeFilter(BoundFilter):
|
|||
|
||||
self.chat_type: typing.Set[str] = set(chat_type)
|
||||
|
||||
async def check(self, obj: Union[Message, CallbackQuery]):
|
||||
async def check(self, obj: Union[Message, CallbackQuery, ChatMemberUpdated]):
|
||||
if isinstance(obj, Message):
|
||||
obj = obj.chat
|
||||
elif isinstance(obj, CallbackQuery):
|
||||
obj = obj.message.chat
|
||||
elif isinstance(obj, ChatMemberUpdated):
|
||||
obj = obj.chat
|
||||
else:
|
||||
warnings.warn("ChatTypeFilter doesn't support %s as input", type(obj))
|
||||
return False
|
||||
|
||||
return obj.type in self.chat_type
|
||||
|
||||
|
||||
class MediaGroupFilter(BoundFilter):
|
||||
"""
|
||||
Check if message is part of a media group.
|
||||
|
||||
`is_media_group=True` - the message is part of a media group
|
||||
`is_media_group=False` - the message is NOT part of a media group
|
||||
"""
|
||||
|
||||
key = "is_media_group"
|
||||
|
||||
def __init__(self, is_media_group: bool):
|
||||
self.is_media_group = is_media_group
|
||||
|
||||
async def check(self, message: types.Message) -> bool:
|
||||
return bool(getattr(message, "media_group_id")) is self.is_media_group
|
||||
|
|
|
|||
|
|
@ -48,9 +48,13 @@ class FiltersFactory:
|
|||
:param full_config:
|
||||
:return:
|
||||
"""
|
||||
filters_set = []
|
||||
filters_set.extend(self._resolve_registered(event_handler,
|
||||
{k: v for k, v in full_config.items() if v is not None}))
|
||||
filters_set = list(
|
||||
self._resolve_registered(
|
||||
event_handler,
|
||||
{k: v for k, v in full_config.items() if v is not None},
|
||||
)
|
||||
)
|
||||
|
||||
if custom_filters:
|
||||
filters_set.extend(custom_filters)
|
||||
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ class NotFilter(_LogicFilter):
|
|||
class AndFilter(_LogicFilter):
|
||||
|
||||
def __init__(self, *targets):
|
||||
self.targets = list(wrap_async(target) for target in targets)
|
||||
self.targets = [wrap_async(target) for target in targets]
|
||||
|
||||
async def check(self, *args):
|
||||
"""
|
||||
|
|
@ -268,7 +268,7 @@ class AndFilter(_LogicFilter):
|
|||
|
||||
class OrFilter(_LogicFilter):
|
||||
def __init__(self, *targets):
|
||||
self.targets = list(wrap_async(target) for target in targets)
|
||||
self.targets = [wrap_async(target) for target in targets]
|
||||
|
||||
async def check(self, *args):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -25,15 +25,14 @@ class CancelHandler(Exception):
|
|||
def _get_spec(func: callable):
|
||||
while hasattr(func, '__wrapped__'): # Try to resolve decorated callbacks
|
||||
func = func.__wrapped__
|
||||
spec = inspect.getfullargspec(func)
|
||||
return spec
|
||||
return inspect.getfullargspec(func)
|
||||
|
||||
|
||||
def _check_spec(spec: inspect.FullArgSpec, kwargs: dict):
|
||||
if spec.varkw:
|
||||
return kwargs
|
||||
|
||||
return {k: v for k, v in kwargs.items() if k in spec.args}
|
||||
return {k: v for k, v in kwargs.items() if k in set(spec.args + spec.kwonlyargs)}
|
||||
|
||||
|
||||
class Handler:
|
||||
|
|
|
|||
|
|
@ -16,11 +16,14 @@ class MiddlewareManager:
|
|||
:param dispatcher: instance of Dispatcher
|
||||
"""
|
||||
self.dispatcher = dispatcher
|
||||
self.loop = dispatcher.loop
|
||||
self.bot = dispatcher.bot
|
||||
self.storage = dispatcher.storage
|
||||
self.applications = []
|
||||
|
||||
@property
|
||||
def loop(self):
|
||||
return self.dispatcher.loop
|
||||
|
||||
def setup(self, middleware):
|
||||
"""
|
||||
Setup middleware
|
||||
|
|
|
|||
|
|
@ -40,24 +40,27 @@ class BaseStorage:
|
|||
@classmethod
|
||||
def check_address(cls, *,
|
||||
chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None) -> (typing.Union[str, int], typing.Union[str, int]):
|
||||
user: typing.Union[str, int, None] = None,
|
||||
) -> (typing.Union[str, int], typing.Union[str, int]):
|
||||
"""
|
||||
In all storage's methods chat or user is always required.
|
||||
If one of them is not provided, you have to set missing value based on the provided one.
|
||||
|
||||
This method performs the check described above.
|
||||
|
||||
:param chat:
|
||||
:param user:
|
||||
:param chat: chat_id
|
||||
:param user: user_id
|
||||
:return:
|
||||
"""
|
||||
if chat is None and user is None:
|
||||
raise ValueError('`user` or `chat` parameter is required but no one is provided!')
|
||||
|
||||
if user is None and chat is not None:
|
||||
if user is None:
|
||||
user = chat
|
||||
elif user is not None and chat is None:
|
||||
|
||||
elif chat is None:
|
||||
chat = user
|
||||
|
||||
return chat, user
|
||||
|
||||
async def get_state(self, *,
|
||||
|
|
@ -270,6 +273,21 @@ class BaseStorage:
|
|||
"""
|
||||
await self.set_data(chat=chat, user=user, data={})
|
||||
|
||||
@staticmethod
|
||||
def resolve_state(value):
|
||||
from .filters.state import State
|
||||
|
||||
if value is None:
|
||||
return
|
||||
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
|
||||
if isinstance(value, State):
|
||||
return value.state
|
||||
|
||||
return str(value)
|
||||
|
||||
|
||||
class FSMContext:
|
||||
def __init__(self, storage, chat, user):
|
||||
|
|
@ -279,20 +297,8 @@ class FSMContext:
|
|||
def proxy(self):
|
||||
return FSMContextProxy(self)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_state(value):
|
||||
from .filters.state import State
|
||||
|
||||
if value is None:
|
||||
return
|
||||
elif isinstance(value, str):
|
||||
return value
|
||||
elif isinstance(value, State):
|
||||
return value.state
|
||||
return str(value)
|
||||
|
||||
async def get_state(self, default: typing.Optional[str] = None) -> typing.Optional[str]:
|
||||
return await self.storage.get_state(chat=self.chat, user=self.user, default=self._resolve_state(default))
|
||||
return await self.storage.get_state(chat=self.chat, user=self.user, default=default)
|
||||
|
||||
async def get_data(self, default: typing.Optional[str] = None) -> typing.Dict:
|
||||
return await self.storage.get_data(chat=self.chat, user=self.user, default=default)
|
||||
|
|
@ -300,8 +306,8 @@ class FSMContext:
|
|||
async def update_data(self, data: typing.Dict = None, **kwargs):
|
||||
await self.storage.update_data(chat=self.chat, user=self.user, data=data, **kwargs)
|
||||
|
||||
async def set_state(self, state: typing.Union[typing.AnyStr, None] = None):
|
||||
await self.storage.set_state(chat=self.chat, user=self.user, state=self._resolve_state(state))
|
||||
async def set_state(self, state: typing.Optional[typing.AnyStr] = None):
|
||||
await self.storage.set_state(chat=self.chat, user=self.user, state=state)
|
||||
|
||||
async def set_data(self, data: typing.Dict = None):
|
||||
await self.storage.set_data(chat=self.chat, user=self.user, data=data)
|
||||
|
|
@ -397,7 +403,7 @@ class FSMContextProxy:
|
|||
def setdefault(self, key, default):
|
||||
self._check_closed()
|
||||
|
||||
self._data.setdefault(key, default)
|
||||
return self._data.setdefault(key, default)
|
||||
|
||||
def update(self, data=None, **kwargs):
|
||||
self._check_closed()
|
||||
|
|
|
|||
|
|
@ -116,8 +116,7 @@ class WebhookRequestHandler(web.View):
|
|||
:return: :class:`aiogram.types.Update`
|
||||
"""
|
||||
data = await self.request.json()
|
||||
update = types.Update(**data)
|
||||
return update
|
||||
return types.Update(**data)
|
||||
|
||||
async def post(self):
|
||||
"""
|
||||
|
|
@ -169,7 +168,7 @@ class WebhookRequestHandler(web.View):
|
|||
:return:
|
||||
"""
|
||||
dispatcher = self.get_dispatcher()
|
||||
loop = dispatcher.loop
|
||||
loop = dispatcher.loop or asyncio.get_event_loop()
|
||||
|
||||
# Analog of `asyncio.wait_for` but without cancelling task
|
||||
waiter = loop.create_future()
|
||||
|
|
@ -189,10 +188,9 @@ class WebhookRequestHandler(web.View):
|
|||
|
||||
if fut.done():
|
||||
return fut.result()
|
||||
else:
|
||||
# context.set_value(WEBHOOK_CONNECTION, False)
|
||||
fut.remove_done_callback(cb)
|
||||
fut.add_done_callback(self.respond_via_request)
|
||||
# context.set_value(WEBHOOK_CONNECTION, False)
|
||||
fut.remove_done_callback(cb)
|
||||
fut.add_done_callback(self.respond_via_request)
|
||||
finally:
|
||||
timeout_handle.cancel()
|
||||
|
||||
|
|
@ -209,7 +207,7 @@ class WebhookRequestHandler(web.View):
|
|||
TimeoutWarning)
|
||||
|
||||
dispatcher = self.get_dispatcher()
|
||||
loop = dispatcher.loop
|
||||
loop = dispatcher.loop or asyncio.get_event_loop()
|
||||
|
||||
try:
|
||||
results = task.result()
|
||||
|
|
@ -620,7 +618,7 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for
|
||||
Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data.
|
||||
:param caption: String (Optional) - Photo caption (may also be used when resending photos by file_id),
|
||||
0-200 characters
|
||||
0-1024 characters after entities parsing
|
||||
:param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive
|
||||
a notification with no sound.
|
||||
:param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message
|
||||
|
|
@ -673,7 +671,7 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
to send an audio file that exists on the Telegram servers (recommended), pass an HTTP URL
|
||||
as a String for Telegram to get an audio file from the Internet, or upload a new one
|
||||
using multipart/form-data.
|
||||
:param caption: String (Optional) - Audio caption, 0-200 characters
|
||||
:param caption: String (Optional) - Audio caption, 0-1024 characters after entities parsing
|
||||
:param duration: Integer (Optional) - Duration of the audio in seconds
|
||||
:param performer: String (Optional) - Performer
|
||||
:param title: String (Optional) - Track name
|
||||
|
|
@ -732,7 +730,7 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
as a String for Telegram to get a file from the Internet, or upload a new one
|
||||
using multipart/form-data.
|
||||
:param caption: String (Optional) - Document caption
|
||||
(may also be used when resending documents by file_id), 0-200 characters
|
||||
(may also be used when resending documents by file_id), 0-1024 characters after entities parsing
|
||||
:param disable_notification: Boolean (Optional) - Sends the message silently.
|
||||
Users will receive a notification with no sound.
|
||||
:param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message
|
||||
|
|
@ -789,7 +787,7 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
:param width: Integer (Optional) - Video width
|
||||
:param height: Integer (Optional) - Video height
|
||||
:param caption: String (Optional) - Video caption (may also be used when resending videos by file_id),
|
||||
0-200 characters
|
||||
0-1024 characters after entities parsing
|
||||
:param disable_notification: Boolean (Optional) - Sends the message silently.
|
||||
Users will receive a notification with no sound.
|
||||
:param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message
|
||||
|
|
@ -846,7 +844,7 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
to send a file that exists on the Telegram servers (recommended), pass an HTTP URL
|
||||
as a String for Telegram to get a file from the Internet, or upload a new one
|
||||
using multipart/form-data.
|
||||
:param caption: String (Optional) - Voice message caption, 0-200 characters
|
||||
:param caption: String (Optional) - Voice message caption, 0-1024 characters after entities parsing
|
||||
:param duration: Integer (Optional) - Duration of the voice message in seconds
|
||||
:param disable_notification: Boolean (Optional) - Sends the message silently.
|
||||
Users will receive a notification with no sound.
|
||||
|
|
@ -939,8 +937,8 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
|
||||
def __init__(self, chat_id: Union[Integer, String],
|
||||
media: Union[types.MediaGroup, List] = None,
|
||||
disable_notification: typing.Union[Boolean, None] = None,
|
||||
reply_to_message_id: typing.Union[Integer, None] = None):
|
||||
disable_notification: typing.Optional[Boolean] = None,
|
||||
reply_to_message_id: typing.Optional[Integer] = None):
|
||||
"""
|
||||
Use this method to send a group of photos or videos as an album.
|
||||
|
||||
|
|
@ -951,9 +949,9 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
:param media: A JSON-serialized array describing photos and videos to be sent
|
||||
:type media: :obj:`typing.Union[types.MediaGroup, typing.List]`
|
||||
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type disable_notification: :obj:`typing.Optional[base.Boolean]`
|
||||
:param reply_to_message_id: If the message is a reply, ID of the original message
|
||||
:type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
|
||||
:type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
|
||||
:return: On success, an array of the sent Messages is returned.
|
||||
:rtype: typing.List[types.Message]
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -4,10 +4,19 @@ from .animation import Animation
|
|||
from .audio import Audio
|
||||
from .auth_widget_data import AuthWidgetData
|
||||
from .bot_command import BotCommand
|
||||
from .bot_command_scope import BotCommandScope, BotCommandScopeAllChatAdministrators, \
|
||||
BotCommandScopeAllGroupChats, BotCommandScopeAllPrivateChats, BotCommandScopeChat, \
|
||||
BotCommandScopeChatAdministrators, BotCommandScopeChatMember, \
|
||||
BotCommandScopeDefault, BotCommandScopeType
|
||||
from .callback_game import CallbackGame
|
||||
from .callback_query import CallbackQuery
|
||||
from .chat import Chat, ChatActions, ChatType
|
||||
from .chat_member import ChatMember, ChatMemberStatus
|
||||
from .chat_invite_link import ChatInviteLink
|
||||
from .chat_location import ChatLocation
|
||||
from .chat_member import ChatMember, ChatMemberAdministrator, ChatMemberBanned, \
|
||||
ChatMemberLeft, ChatMemberMember, ChatMemberOwner, ChatMemberRestricted, \
|
||||
ChatMemberStatus
|
||||
from .chat_member_updated import ChatMemberUpdated
|
||||
from .chat_permissions import ChatPermissions
|
||||
from .chat_photo import ChatPhoto
|
||||
from .chosen_inline_result import ChosenInlineResult
|
||||
|
|
@ -32,14 +41,16 @@ from .input_file import InputFile
|
|||
from .input_media import InputMedia, InputMediaAnimation, InputMediaAudio, InputMediaDocument, InputMediaPhoto, \
|
||||
InputMediaVideo, MediaGroup
|
||||
from .input_message_content import InputContactMessageContent, InputLocationMessageContent, InputMessageContent, \
|
||||
InputTextMessageContent, InputVenueMessageContent
|
||||
InputTextMessageContent, InputVenueMessageContent, InputInvoiceMessageContent
|
||||
from .invoice import Invoice
|
||||
from .labeled_price import LabeledPrice
|
||||
from .location import Location
|
||||
from .login_url import LoginUrl
|
||||
from .mask_position import MaskPosition
|
||||
from .message import ContentType, ContentTypes, Message, ParseMode
|
||||
from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged
|
||||
from .message_entity import MessageEntity, MessageEntityType
|
||||
from .message_id import MessageId
|
||||
from .order_info import OrderInfo
|
||||
from .passport_data import PassportData
|
||||
from .passport_element_error import PassportElementError, PassportElementErrorDataField, PassportElementErrorFile, \
|
||||
|
|
@ -49,6 +60,7 @@ from .passport_file import PassportFile
|
|||
from .photo_size import PhotoSize
|
||||
from .poll import PollOption, Poll, PollAnswer, PollType
|
||||
from .pre_checkout_query import PreCheckoutQuery
|
||||
from .proximity_alert_triggered import ProximityAlertTriggered
|
||||
from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, KeyboardButtonPollType
|
||||
from .response_parameters import ResponseParameters
|
||||
from .shipping_address import ShippingAddress
|
||||
|
|
@ -64,6 +76,10 @@ from .venue import Venue
|
|||
from .video import Video
|
||||
from .video_note import VideoNote
|
||||
from .voice import Voice
|
||||
from .voice_chat_ended import VoiceChatEnded
|
||||
from .voice_chat_participants_invited import VoiceChatParticipantsInvited
|
||||
from .voice_chat_scheduled import VoiceChatScheduled
|
||||
from .voice_chat_started import VoiceChatStarted
|
||||
from .webhook_info import WebhookInfo
|
||||
|
||||
__all__ = (
|
||||
|
|
@ -72,12 +88,31 @@ __all__ = (
|
|||
'Audio',
|
||||
'AuthWidgetData',
|
||||
'BotCommand',
|
||||
'BotCommandScope',
|
||||
'BotCommandScopeAllChatAdministrators',
|
||||
'BotCommandScopeAllGroupChats',
|
||||
'BotCommandScopeAllPrivateChats',
|
||||
'BotCommandScopeChat',
|
||||
'BotCommandScopeChatAdministrators',
|
||||
'BotCommandScopeChatMember',
|
||||
'BotCommandScopeDefault',
|
||||
'BotCommandScopeType',
|
||||
'CallbackGame',
|
||||
'CallbackQuery',
|
||||
'Chat',
|
||||
'ChatActions',
|
||||
'ChatInviteLink',
|
||||
'ChatLocation',
|
||||
'ChatMember',
|
||||
'ChatMemberStatus',
|
||||
'ChatMemberUpdated',
|
||||
'ChatMemberOwner',
|
||||
'ChatMemberAdministrator',
|
||||
'ChatMemberMember',
|
||||
'ChatMemberRestricted',
|
||||
'ChatMemberLeft',
|
||||
'ChatMemberBanned',
|
||||
'ChatPermissions',
|
||||
'ChatPhoto',
|
||||
'ChatType',
|
||||
'ChosenInlineResult',
|
||||
|
|
@ -118,6 +153,7 @@ __all__ = (
|
|||
'InlineQueryResultVideo',
|
||||
'InlineQueryResultVoice',
|
||||
'InputContactMessageContent',
|
||||
'InputInvoiceMessageContent',
|
||||
'InputFile',
|
||||
'InputLocationMessageContent',
|
||||
'InputMedia',
|
||||
|
|
@ -138,8 +174,10 @@ __all__ = (
|
|||
'MaskPosition',
|
||||
'MediaGroup',
|
||||
'Message',
|
||||
'MessageAutoDeleteTimerChanged',
|
||||
'MessageEntity',
|
||||
'MessageEntityType',
|
||||
'MessageId',
|
||||
'OrderInfo',
|
||||
'ParseMode',
|
||||
'PassportData',
|
||||
|
|
@ -157,6 +195,7 @@ __all__ = (
|
|||
'PollOption',
|
||||
'PollType',
|
||||
'PreCheckoutQuery',
|
||||
'ProximityAlertTriggered',
|
||||
'ReplyKeyboardMarkup',
|
||||
'ReplyKeyboardRemove',
|
||||
'ResponseParameters',
|
||||
|
|
@ -173,6 +212,10 @@ __all__ = (
|
|||
'Video',
|
||||
'VideoNote',
|
||||
'Voice',
|
||||
'VoiceChatEnded',
|
||||
'VoiceChatParticipantsInvited',
|
||||
'VoiceChatScheduled',
|
||||
'VoiceChatStarted',
|
||||
'WebhookInfo',
|
||||
'base',
|
||||
'fields',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ class Animation(base.TelegramObject, mixins.Downloadable):
|
|||
|
||||
file_id: base.String = fields.Field()
|
||||
file_unique_id: base.String = fields.Field()
|
||||
width: base.Integer = fields.Field()
|
||||
height: base.Integer = fields.Field()
|
||||
duration: base.Integer = fields.Field()
|
||||
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
||||
file_name: base.String = fields.Field()
|
||||
mime_type: base.String = fields.Field()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class Audio(base.TelegramObject, mixins.Downloadable):
|
|||
duration: base.Integer = fields.Field()
|
||||
performer: base.String = fields.Field()
|
||||
title: base.String = fields.Field()
|
||||
file_name: base.String = fields.Field()
|
||||
mime_type: base.String = fields.Field()
|
||||
file_size: base.Integer = fields.Field()
|
||||
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import logging
|
||||
import typing
|
||||
from typing import TypeVar
|
||||
|
||||
|
|
@ -26,6 +27,9 @@ Float = TypeVar('Float', bound=float)
|
|||
Boolean = TypeVar('Boolean', bound=bool)
|
||||
T = TypeVar('T')
|
||||
|
||||
# Main aiogram logger
|
||||
log = logging.getLogger('aiogram')
|
||||
|
||||
|
||||
class MetaTelegramObject(type):
|
||||
"""
|
||||
|
|
@ -135,14 +139,18 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
return type(self).telegram_types
|
||||
|
||||
@classmethod
|
||||
def to_object(cls: typing.Type[T], data: typing.Dict[str, typing.Any]) -> T:
|
||||
def to_object(cls: typing.Type[T],
|
||||
data: typing.Dict[str, typing.Any],
|
||||
conf: typing.Dict[str, typing.Any] = None
|
||||
) -> T:
|
||||
"""
|
||||
Deserialize object
|
||||
|
||||
:param data:
|
||||
:param conf:
|
||||
:return:
|
||||
"""
|
||||
return cls(**data)
|
||||
return cls(conf=conf, **data)
|
||||
|
||||
@property
|
||||
def bot(self) -> Bot:
|
||||
|
|
@ -212,7 +220,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
"""
|
||||
if item in self.props:
|
||||
return self.props[item].get_value(self)
|
||||
raise KeyError(item)
|
||||
return self.values[item]
|
||||
|
||||
def __setitem__(self, key: str, value: typing.Any) -> None:
|
||||
"""
|
||||
|
|
@ -224,7 +232,10 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
"""
|
||||
if key in self.props:
|
||||
return self.props[key].set_value(self, value, self.conf.get('parent', None))
|
||||
raise KeyError(key)
|
||||
self.values[key] = value
|
||||
|
||||
# Log warning when Telegram silently adds new Fields
|
||||
log.warning("Field '%s' doesn't exist in %s", key, self.__class__)
|
||||
|
||||
def __contains__(self, item: str) -> bool:
|
||||
"""
|
||||
|
|
@ -242,8 +253,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
|
|||
|
||||
:return:
|
||||
"""
|
||||
for item in self.to_python().items():
|
||||
yield item
|
||||
yield from self.to_python().items()
|
||||
|
||||
def iter_keys(self) -> typing.Generator[typing.Any, None, None]:
|
||||
"""
|
||||
|
|
|
|||
121
aiogram/types/bot_command_scope.py
Normal file
121
aiogram/types/bot_command_scope.py
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import typing
|
||||
|
||||
from . import base, fields
|
||||
from ..utils import helper
|
||||
|
||||
|
||||
class BotCommandScopeType(helper.Helper):
|
||||
mode = helper.HelperMode.snake_case
|
||||
|
||||
DEFAULT = helper.Item() # default
|
||||
ALL_PRIVATE_CHATS = helper.Item() # all_private_chats
|
||||
ALL_GROUP_CHATS = helper.Item() # all_group_chats
|
||||
ALL_CHAT_ADMINISTRATORS = helper.Item() # all_chat_administrators
|
||||
CHAT = helper.Item() # chat
|
||||
CHAT_ADMINISTRATORS = helper.Item() # chat_administrators
|
||||
CHAT_MEMBER = helper.Item() # chat_member
|
||||
|
||||
|
||||
class BotCommandScope(base.TelegramObject):
|
||||
"""
|
||||
This object represents the scope to which bot commands are applied.
|
||||
Currently, the following 7 scopes are supported:
|
||||
BotCommandScopeDefault
|
||||
BotCommandScopeAllPrivateChats
|
||||
BotCommandScopeAllGroupChats
|
||||
BotCommandScopeAllChatAdministrators
|
||||
BotCommandScopeChat
|
||||
BotCommandScopeChatAdministrators
|
||||
BotCommandScopeChatMember
|
||||
|
||||
https://core.telegram.org/bots/api#botcommandscope
|
||||
"""
|
||||
type: base.String = fields.Field()
|
||||
|
||||
@classmethod
|
||||
def from_type(cls, type: str, **kwargs: typing.Any):
|
||||
if type == BotCommandScopeType.DEFAULT:
|
||||
return BotCommandScopeDefault(type=type, **kwargs)
|
||||
if type == BotCommandScopeType.ALL_PRIVATE_CHATS:
|
||||
return BotCommandScopeAllPrivateChats(type=type, **kwargs)
|
||||
if type == BotCommandScopeType.ALL_GROUP_CHATS:
|
||||
return BotCommandScopeAllGroupChats(type=type, **kwargs)
|
||||
if type == BotCommandScopeType.ALL_CHAT_ADMINISTRATORS:
|
||||
return BotCommandScopeAllChatAdministrators(type=type, **kwargs)
|
||||
if type == BotCommandScopeType.CHAT:
|
||||
return BotCommandScopeChat(type=type, **kwargs)
|
||||
if type == BotCommandScopeType.CHAT_ADMINISTRATORS:
|
||||
return BotCommandScopeChatAdministrators(type=type, **kwargs)
|
||||
if type == BotCommandScopeType.CHAT_MEMBER:
|
||||
return BotCommandScopeChatMember(type=type, **kwargs)
|
||||
raise ValueError(f"Unknown BotCommandScope type {type!r}")
|
||||
|
||||
|
||||
class BotCommandScopeDefault(BotCommandScope):
|
||||
"""
|
||||
Represents the default scope of bot commands.
|
||||
Default commands are used if no commands with a narrower scope are
|
||||
specified for the user.
|
||||
"""
|
||||
type = fields.Field(default=BotCommandScopeType.DEFAULT)
|
||||
|
||||
|
||||
class BotCommandScopeAllPrivateChats(BotCommandScope):
|
||||
"""
|
||||
Represents the scope of bot commands, covering all private chats.
|
||||
"""
|
||||
type = fields.Field(default=BotCommandScopeType.ALL_PRIVATE_CHATS)
|
||||
|
||||
|
||||
class BotCommandScopeAllGroupChats(BotCommandScope):
|
||||
"""
|
||||
Represents the scope of bot commands, covering all group and
|
||||
supergroup chats.
|
||||
"""
|
||||
type = fields.Field(default=BotCommandScopeType.ALL_GROUP_CHATS)
|
||||
|
||||
|
||||
class BotCommandScopeAllChatAdministrators(BotCommandScope):
|
||||
"""
|
||||
Represents the scope of bot commands, covering all group and
|
||||
supergroup chat administrators.
|
||||
"""
|
||||
type = fields.Field(default=BotCommandScopeType.ALL_CHAT_ADMINISTRATORS)
|
||||
|
||||
|
||||
class BotCommandScopeChat(BotCommandScope):
|
||||
"""
|
||||
Represents the scope of bot commands, covering a specific chat.
|
||||
"""
|
||||
type = fields.Field(default=BotCommandScopeType.CHAT)
|
||||
chat_id: typing.Union[base.String, base.Integer] = fields.Field()
|
||||
|
||||
def __init__(self, chat_id: typing.Union[base.String, base.Integer], **kwargs):
|
||||
super().__init__(chat_id=chat_id, **kwargs)
|
||||
|
||||
|
||||
class BotCommandScopeChatAdministrators(BotCommandScopeChat):
|
||||
"""
|
||||
Represents the scope of bot commands, covering all administrators
|
||||
of a specific group or supergroup chat.
|
||||
"""
|
||||
type = fields.Field(default=BotCommandScopeType.CHAT_ADMINISTRATORS)
|
||||
chat_id: typing.Union[base.String, base.Integer] = fields.Field()
|
||||
|
||||
|
||||
class BotCommandScopeChatMember(BotCommandScopeChat):
|
||||
"""
|
||||
Represents the scope of bot commands, covering a specific member of
|
||||
a group or supergroup chat.
|
||||
"""
|
||||
type = fields.Field(default=BotCommandScopeType.CHAT_MEMBER)
|
||||
chat_id: typing.Union[base.String, base.Integer] = fields.Field()
|
||||
user_id: base.Integer = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chat_id: typing.Union[base.String, base.Integer],
|
||||
user_id: base.Integer,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(chat_id=chat_id, user_id=user_id, **kwargs)
|
||||
|
|
@ -28,10 +28,10 @@ class CallbackQuery(base.TelegramObject):
|
|||
data: base.String = fields.Field()
|
||||
game_short_name: base.String = fields.Field()
|
||||
|
||||
async def answer(self, text: typing.Union[base.String, None] = None,
|
||||
show_alert: typing.Union[base.Boolean, None] = None,
|
||||
url: typing.Union[base.String, None] = None,
|
||||
cache_time: typing.Union[base.Integer, None] = None):
|
||||
async def answer(self, text: typing.Optional[base.String] = None,
|
||||
show_alert: typing.Optional[base.Boolean] = None,
|
||||
url: typing.Optional[base.String] = None,
|
||||
cache_time: typing.Optional[base.Integer] = None):
|
||||
"""
|
||||
Use this method to send answers to callback queries sent from inline keyboards.
|
||||
The answer will be displayed to the user as a notification at the top of the chat screen or as an alert.
|
||||
|
|
@ -43,19 +43,22 @@ class CallbackQuery(base.TelegramObject):
|
|||
Source: https://core.telegram.org/bots/api#answercallbackquery
|
||||
|
||||
:param text: Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters
|
||||
:type text: :obj:`typing.Union[base.String, None]`
|
||||
:type text: :obj:`typing.Optional[base.String]`
|
||||
:param show_alert: If true, an alert will be shown by the client instead of a notification
|
||||
at the top of the chat screen. Defaults to false.
|
||||
:type show_alert: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type show_alert: :obj:`typing.Optional[base.Boolean]`
|
||||
:param url: URL that will be opened by the user's client.
|
||||
:type url: :obj:`typing.Union[base.String, None]`
|
||||
:type url: :obj:`typing.Optional[base.String]`
|
||||
:param cache_time: The maximum amount of time in seconds that the
|
||||
result of the callback query may be cached client-side.
|
||||
:type cache_time: :obj:`typing.Union[base.Integer, None]`
|
||||
:type cache_time: :obj:`typing.Optional[base.Integer]`
|
||||
:return: On success, True is returned.
|
||||
:rtype: :obj:`base.Boolean`"""
|
||||
await self.bot.answer_callback_query(callback_query_id=self.id, text=text,
|
||||
show_alert=show_alert, url=url, cache_time=cache_time)
|
||||
return await self.bot.answer_callback_query(callback_query_id=self.id,
|
||||
text=text,
|
||||
show_alert=show_alert,
|
||||
url=url,
|
||||
cache_time=cache_time)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.id)
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ import asyncio
|
|||
import datetime
|
||||
import typing
|
||||
|
||||
from ..utils import helper, markdown
|
||||
from . import base, fields
|
||||
from .chat_invite_link import ChatInviteLink
|
||||
from .chat_location import ChatLocation
|
||||
from .chat_member import ChatMember
|
||||
from .chat_permissions import ChatPermissions
|
||||
from .chat_photo import ChatPhoto
|
||||
from .input_file import InputFile
|
||||
from ..utils.deprecated import deprecated
|
||||
from ..utils import helper, markdown
|
||||
from ..utils.deprecated import deprecated, DeprecatedReadOnlyClassVar
|
||||
|
||||
|
||||
class Chat(base.TelegramObject):
|
||||
|
|
@ -27,13 +29,17 @@ class Chat(base.TelegramObject):
|
|||
last_name: base.String = fields.Field()
|
||||
all_members_are_administrators: base.Boolean = fields.Field()
|
||||
photo: ChatPhoto = fields.Field(base=ChatPhoto)
|
||||
bio: base.String = fields.Field()
|
||||
description: base.String = fields.Field()
|
||||
invite_link: base.String = fields.Field()
|
||||
pinned_message: 'Message' = fields.Field(base='Message')
|
||||
permissions: ChatPermissions = fields.Field(base=ChatPermissions)
|
||||
slow_mode_delay: base.Integer = fields.Field()
|
||||
message_auto_delete_time: base.Integer = fields.Field()
|
||||
sticker_set_name: base.String = fields.Field()
|
||||
can_set_sticker_set: base.Boolean = fields.Field()
|
||||
linked_chat_id: base.Integer = fields.Field()
|
||||
location: ChatLocation = fields.Field()
|
||||
|
||||
def __hash__(self):
|
||||
return self.id
|
||||
|
|
@ -48,7 +54,7 @@ class Chat(base.TelegramObject):
|
|||
return self.title
|
||||
|
||||
@property
|
||||
def mention(self) -> typing.Union[base.String, None]:
|
||||
def mention(self) -> typing.Optional[base.String]:
|
||||
"""
|
||||
Get mention if a Chat has a username, or get full name if this is a Private Chat, otherwise None is returned
|
||||
"""
|
||||
|
|
@ -109,7 +115,7 @@ class Chat(base.TelegramObject):
|
|||
|
||||
async def update_chat(self):
|
||||
"""
|
||||
User this method to update Chat data
|
||||
Use this method to update Chat data
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
|
@ -175,59 +181,91 @@ class Chat(base.TelegramObject):
|
|||
Source: https://core.telegram.org/bots/api#setchatdescription
|
||||
|
||||
:param description: New chat description, 0-255 characters
|
||||
:type description: :obj:`typing.Union[base.String, None]`
|
||||
:type description: :obj:`typing.Optional[base.String]`
|
||||
:return: Returns True on success.
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
return await self.bot.set_chat_description(self.id, description)
|
||||
|
||||
async def kick(self, user_id: base.Integer,
|
||||
until_date: typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None] = None) -> base.Boolean:
|
||||
async def kick(self,
|
||||
user_id: base.Integer,
|
||||
until_date: typing.Union[base.Integer, datetime.datetime,
|
||||
datetime.timedelta, None] = None,
|
||||
revoke_messages: typing.Optional[base.Boolean] = None,
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
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
|
||||
on their own using invite links, etc., unless unbanned first.
|
||||
In the case of supergroups and channels, the user will not be able to return
|
||||
to the chat on their own using invite links, etc., unless unbanned first.
|
||||
|
||||
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
|
||||
Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ setting
|
||||
is off in the target group.
|
||||
Otherwise members may only be removed by the group's creator or by the member that added them.
|
||||
The bot must be an administrator in the chat for this to work and must have
|
||||
the appropriate admin rights.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#kickchatmember
|
||||
|
||||
:param user_id: Unique identifier of the target user
|
||||
:type user_id: :obj:`base.Integer`
|
||||
:param until_date: Date when the user will be unbanned, unix time.
|
||||
:type until_date: :obj:`typing.Union[base.Integer, None]`
|
||||
:return: Returns True on success.
|
||||
|
||||
:param until_date: Date when the user will be unbanned. If user is banned
|
||||
for more than 366 days or less than 30 seconds from the current time they
|
||||
are considered to be banned forever. Applied for supergroups and channels
|
||||
only.
|
||||
:type until_date: :obj:`typing.Union[base.Integer, datetime.datetime,
|
||||
datetime.timedelta, None]`
|
||||
|
||||
:param revoke_messages: Pass True to delete all messages from the chat for
|
||||
the user that is being removed. If False, the user will be able to see
|
||||
messages in the group that were sent before the user was removed. Always
|
||||
True for supergroups and channels.
|
||||
:type revoke_messages: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:return: Returns True on success
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
return await self.bot.kick_chat_member(self.id, user_id=user_id, until_date=until_date)
|
||||
return await self.bot.kick_chat_member(
|
||||
chat_id=self.id,
|
||||
user_id=user_id,
|
||||
until_date=until_date,
|
||||
revoke_messages=revoke_messages,
|
||||
)
|
||||
|
||||
async def unban(self, user_id: base.Integer) -> base.Boolean:
|
||||
async def unban(self,
|
||||
user_id: base.Integer,
|
||||
only_if_banned: typing.Optional[base.Boolean] = None,
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method to unban a previously kicked user in a supergroup or channel. `
|
||||
The user will not return to the group or channel automatically, but will be able to join via link, etc.
|
||||
|
||||
The bot must be an administrator for this to work.
|
||||
Use this method to unban a previously kicked user in a supergroup or channel.
|
||||
The user will not return to the group or channel automatically, but will be
|
||||
able to join via link, etc. The bot must be an administrator for this to
|
||||
work. By default, this method guarantees that after the call the user is not
|
||||
a member of the chat, but will be able to join it. So if the user is a member
|
||||
of the chat they will also be removed from the chat. If you don't want this,
|
||||
use the parameter only_if_banned. Returns True on success.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#unbanchatmember
|
||||
|
||||
:param user_id: Unique identifier of the target user
|
||||
:type user_id: :obj:`base.Integer`
|
||||
|
||||
:param only_if_banned: Do nothing if the user is not banned
|
||||
:type only_if_banned: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:return: Returns True on success.
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
return await self.bot.unban_chat_member(self.id, user_id=user_id)
|
||||
return await self.bot.unban_chat_member(
|
||||
chat_id=self.id,
|
||||
user_id=user_id,
|
||||
only_if_banned=only_if_banned,
|
||||
)
|
||||
|
||||
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_send_messages: typing.Optional[base.Boolean] = None,
|
||||
can_send_media_messages: typing.Optional[base.Boolean] = None,
|
||||
can_send_other_messages: typing.Optional[base.Boolean] = None,
|
||||
can_add_web_page_previews: typing.Optional[base.Boolean] = 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.
|
||||
|
|
@ -240,18 +278,18 @@ class Chat(base.TelegramObject):
|
|||
: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]`
|
||||
:type until_date: :obj:`typing.Optional[base.Integer]`
|
||||
:param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues
|
||||
:type can_send_messages: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_send_messages: :obj:`typing.Optional[base.Boolean]`
|
||||
:param can_send_media_messages: Pass True, if the user can send audios, documents, photos, videos,
|
||||
video notes and voice notes, implies can_send_messages
|
||||
:type can_send_media_messages: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_send_media_messages: :obj:`typing.Optional[base.Boolean]`
|
||||
:param can_send_other_messages: Pass True, if the user can send animations, games, stickers and
|
||||
use inline bots, implies can_send_media_messages
|
||||
:type can_send_other_messages: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_send_other_messages: :obj:`typing.Optional[base.Boolean]`
|
||||
:param can_add_web_page_previews: Pass True, if the user may add web page previews to their messages,
|
||||
implies can_send_media_messages
|
||||
:type can_add_web_page_previews: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_add_web_page_previews: :obj:`typing.Optional[base.Boolean]`
|
||||
:return: Returns True on success.
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
|
|
@ -263,15 +301,17 @@ class Chat(base.TelegramObject):
|
|||
can_send_other_messages=can_send_other_messages,
|
||||
can_add_web_page_previews=can_add_web_page_previews)
|
||||
|
||||
async def promote(self, user_id: base.Integer,
|
||||
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,
|
||||
can_delete_messages: typing.Union[base.Boolean, None] = None,
|
||||
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:
|
||||
async def promote(self,
|
||||
user_id: base.Integer,
|
||||
is_anonymous: typing.Optional[base.Boolean] = None,
|
||||
can_change_info: typing.Optional[base.Boolean] = None,
|
||||
can_post_messages: typing.Optional[base.Boolean] = None,
|
||||
can_edit_messages: typing.Optional[base.Boolean] = None,
|
||||
can_delete_messages: typing.Optional[base.Boolean] = None,
|
||||
can_invite_users: typing.Optional[base.Boolean] = None,
|
||||
can_restrict_members: typing.Optional[base.Boolean] = None,
|
||||
can_pin_messages: typing.Optional[base.Boolean] = None,
|
||||
can_promote_members: typing.Optional[base.Boolean] = 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.
|
||||
|
|
@ -281,29 +321,42 @@ class Chat(base.TelegramObject):
|
|||
|
||||
:param user_id: Unique identifier of the target user
|
||||
:type user_id: :obj:`base.Integer`
|
||||
|
||||
:param is_anonymous: Pass True, if the administrator's presence in the chat is hidden
|
||||
:type is_anonymous: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:param can_change_info: Pass True, if the administrator can change chat title, photo and other settings
|
||||
:type can_change_info: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_change_info: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:param can_post_messages: Pass True, if the administrator can create channel posts, channels only
|
||||
:type can_post_messages: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_post_messages: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:param can_edit_messages: Pass True, if the administrator can edit messages of other users, channels only
|
||||
:type can_edit_messages: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_edit_messages: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:param can_delete_messages: Pass True, if the administrator can delete messages of other users
|
||||
:type can_delete_messages: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_delete_messages: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:param can_invite_users: Pass True, if the administrator can invite new users to the chat
|
||||
:type can_invite_users: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_invite_users: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:param can_restrict_members: Pass True, if the administrator can restrict, ban or unban chat members
|
||||
:type can_restrict_members: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_restrict_members: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:param can_pin_messages: Pass True, if the administrator can pin messages, supergroups only
|
||||
:type can_pin_messages: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_pin_messages: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:param can_promote_members: Pass True, if the administrator can add new administrators
|
||||
with a subset of his own privileges or demote administrators that he has promoted,
|
||||
directly or indirectly (promoted by administrators that were appointed by him)
|
||||
:type can_promote_members: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type can_promote_members: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:return: Returns True on success.
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
return await self.bot.promote_chat_member(self.id,
|
||||
user_id=user_id,
|
||||
is_anonymous=is_anonymous,
|
||||
can_change_info=can_change_info,
|
||||
can_post_messages=can_post_messages,
|
||||
can_edit_messages=can_edit_messages,
|
||||
|
|
@ -338,36 +391,73 @@ class Chat(base.TelegramObject):
|
|||
:param custom_title: New custom title for the administrator; 0-16 characters, emoji are not allowed
|
||||
:return: True on success.
|
||||
"""
|
||||
return await self.bot.set_chat_administrator_custom_title(chat_id=self.id, user_id=user_id, custom_title=custom_title)
|
||||
return await self.bot.set_chat_administrator_custom_title(chat_id=self.id, user_id=user_id,
|
||||
custom_title=custom_title)
|
||||
|
||||
async def pin_message(self, message_id: base.Integer, disable_notification: base.Boolean = False) -> base.Boolean:
|
||||
async def pin_message(self,
|
||||
message_id: base.Integer,
|
||||
disable_notification: typing.Optional[base.Boolean] = False,
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method to pin a message in a supergroup.
|
||||
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
Use this method to add a message to the list of pinned messages in a chat.
|
||||
If the chat is not a private chat, the bot must be an administrator in the
|
||||
chat for this to work and must have the 'can_pin_messages' admin right in a
|
||||
supergroup or 'can_edit_messages' admin right in a channel. Returns True on
|
||||
success.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#pinchatmessage
|
||||
|
||||
:param message_id: Identifier of a message to pin
|
||||
:type message_id: :obj:`base.Integer`
|
||||
:param disable_notification: Pass True, if it is not necessary to send a notification to
|
||||
all group members about the new pinned message
|
||||
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||
:return: Returns True on success.
|
||||
|
||||
:param disable_notification: Pass True, if it is not necessary to send a
|
||||
notification to all group members about the new pinned message
|
||||
:type disable_notification: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:return: Returns True on success
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
return await self.bot.pin_chat_message(self.id, message_id, disable_notification)
|
||||
|
||||
async def unpin_message(self) -> base.Boolean:
|
||||
async def unpin_message(self,
|
||||
message_id: typing.Optional[base.Integer] = None,
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method to unpin a message in a supergroup chat.
|
||||
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
Use this method to remove a message from the list of pinned messages in a
|
||||
chat. If the chat is not a private chat, the bot must be an administrator in
|
||||
the chat for this to work and must have the 'can_pin_messages' admin right in
|
||||
a supergroup or 'can_edit_messages' admin right in a channel. Returns True on
|
||||
success.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#unpinchatmessage
|
||||
|
||||
:return: Returns True on success.
|
||||
:param message_id: Identifier of a message to unpin. If not specified, the
|
||||
most recent pinned message (by sending date) will be unpinned.
|
||||
:type message_id: :obj:`typing.Optional[base.Integer]`
|
||||
|
||||
:return: Returns True on success
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
return await self.bot.unpin_chat_message(self.id)
|
||||
return await self.bot.unpin_chat_message(
|
||||
chat_id=self.id,
|
||||
message_id=message_id,
|
||||
)
|
||||
|
||||
async def unpin_all_messages(self):
|
||||
"""
|
||||
Use this method to clear the list of pinned messages in a chat. If the chat
|
||||
is not a private chat, the bot must be an administrator in the chat for this
|
||||
to work and must have the 'can_pin_messages' admin right in a supergroup or
|
||||
'can_edit_messages' admin right in a channel. Returns True on success.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#unpinallchatmessages
|
||||
|
||||
:return: Returns True on success
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
return await self.bot.unpin_all_chat_messages(
|
||||
chat_id=self.id,
|
||||
)
|
||||
|
||||
async def leave(self) -> base.Boolean:
|
||||
"""
|
||||
|
|
@ -394,16 +484,20 @@ class Chat(base.TelegramObject):
|
|||
"""
|
||||
return await self.bot.get_chat_administrators(self.id)
|
||||
|
||||
async def get_members_count(self) -> base.Integer:
|
||||
async def get_member_count(self) -> base.Integer:
|
||||
"""
|
||||
Use this method to get the number of members in a chat.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#getchatmemberscount
|
||||
Source: https://core.telegram.org/bots/api#getchatmembercount
|
||||
|
||||
:return: Returns Int on success.
|
||||
:rtype: :obj:`base.Integer`
|
||||
"""
|
||||
return await self.bot.get_chat_members_count(self.id)
|
||||
return await self.bot.get_chat_member_count(self.id)
|
||||
|
||||
async def get_members_count(self) -> base.Integer:
|
||||
"""Renamed to get_member_count."""
|
||||
return await self.get_member_count(self.id)
|
||||
|
||||
async def get_member(self, user_id: base.Integer) -> ChatMember:
|
||||
"""
|
||||
|
|
@ -483,6 +577,50 @@ class Chat(base.TelegramObject):
|
|||
|
||||
return self.invite_link
|
||||
|
||||
async def create_invite_link(self,
|
||||
expire_date: typing.Union[base.Integer, datetime.datetime,
|
||||
datetime.timedelta, None] = None,
|
||||
member_limit: typing.Optional[base.Integer] = None,
|
||||
) -> ChatInviteLink:
|
||||
""" Shortcut for createChatInviteLink method. """
|
||||
return await self.bot.create_chat_invite_link(
|
||||
chat_id=self.id,
|
||||
expire_date=expire_date,
|
||||
member_limit=member_limit,
|
||||
)
|
||||
|
||||
async def edit_invite_link(self,
|
||||
invite_link: base.String,
|
||||
expire_date: typing.Union[base.Integer, datetime.datetime,
|
||||
datetime.timedelta, None] = None,
|
||||
member_limit: typing.Optional[base.Integer] = None,
|
||||
) -> ChatInviteLink:
|
||||
""" Shortcut for editChatInviteLink method. """
|
||||
return await self.bot.edit_chat_invite_link(
|
||||
chat_id=self.id,
|
||||
invite_link=invite_link,
|
||||
expire_date=expire_date,
|
||||
member_limit=member_limit,
|
||||
)
|
||||
|
||||
async def revoke_invite_link(self,
|
||||
invite_link: base.String,
|
||||
) -> ChatInviteLink:
|
||||
""" Shortcut for revokeChatInviteLink method. """
|
||||
return await self.bot.revoke_chat_invite_link(
|
||||
chat_id=self.id,
|
||||
invite_link=invite_link,
|
||||
)
|
||||
|
||||
async def delete_message(self,
|
||||
message_id: base.Integer,
|
||||
) -> base.Boolean:
|
||||
""" Shortcut for deleteMessage method. """
|
||||
return await self.bot.delete_message(
|
||||
chat_id=self.id,
|
||||
message_id=message_id,
|
||||
)
|
||||
|
||||
def __int__(self):
|
||||
return self.id
|
||||
|
||||
|
|
@ -494,6 +632,7 @@ class ChatType(helper.Helper):
|
|||
:key: PRIVATE
|
||||
:key: GROUP
|
||||
:key: SUPER_GROUP
|
||||
:key: SUPERGROUP
|
||||
:key: CHANNEL
|
||||
"""
|
||||
|
||||
|
|
@ -501,9 +640,14 @@ class ChatType(helper.Helper):
|
|||
|
||||
PRIVATE = helper.Item() # private
|
||||
GROUP = helper.Item() # group
|
||||
SUPER_GROUP = helper.Item() # supergroup
|
||||
SUPERGROUP = helper.Item() # supergroup
|
||||
CHANNEL = helper.Item() # channel
|
||||
|
||||
SUPER_GROUP: DeprecatedReadOnlyClassVar[ChatType, helper.Item] \
|
||||
= DeprecatedReadOnlyClassVar(
|
||||
"SUPER_GROUP chat type is deprecated, use SUPERGROUP instead.",
|
||||
new_value_getter=lambda cls: cls.SUPERGROUP)
|
||||
|
||||
@staticmethod
|
||||
def _check(obj, chat_types) -> bool:
|
||||
if hasattr(obj, 'chat'):
|
||||
|
|
@ -543,7 +687,7 @@ class ChatType(helper.Helper):
|
|||
:param obj:
|
||||
:return:
|
||||
"""
|
||||
return cls._check(obj, [cls.SUPER_GROUP])
|
||||
return cls._check(obj, [cls.SUPER_GROUP, cls.SUPERGROUP])
|
||||
|
||||
@classmethod
|
||||
@deprecated("This filter was moved to ChatTypeFilter, and will be removed in aiogram v3.0")
|
||||
|
|
@ -554,7 +698,7 @@ class ChatType(helper.Helper):
|
|||
:param obj:
|
||||
:return:
|
||||
"""
|
||||
return cls._check(obj, [cls.GROUP, cls.SUPER_GROUP])
|
||||
return cls._check(obj, [cls.GROUP, cls.SUPER_GROUP, cls.SUPERGROUP])
|
||||
|
||||
@classmethod
|
||||
@deprecated("This filter was moved to ChatTypeFilter, and will be removed in aiogram v3.0")
|
||||
|
|
@ -592,6 +736,8 @@ class ChatActions(helper.Helper):
|
|||
UPLOAD_VIDEO: str = helper.Item() # upload_video
|
||||
RECORD_AUDIO: str = helper.Item() # record_audio
|
||||
UPLOAD_AUDIO: str = helper.Item() # upload_audio
|
||||
RECORD_VOICE: str = helper.Item() # record_voice
|
||||
UPLOAD_VOICE: str = helper.Item() # upload_voice
|
||||
UPLOAD_DOCUMENT: str = helper.Item() # upload_document
|
||||
FIND_LOCATION: str = helper.Item() # find_location
|
||||
RECORD_VIDEO_NOTE: str = helper.Item() # record_video_note
|
||||
|
|
@ -677,6 +823,26 @@ class ChatActions(helper.Helper):
|
|||
"""
|
||||
await cls._do(cls.UPLOAD_AUDIO, sleep)
|
||||
|
||||
@classmethod
|
||||
async def record_voice(cls, sleep=None):
|
||||
"""
|
||||
Do record voice
|
||||
|
||||
:param sleep: sleep timeout
|
||||
:return:
|
||||
"""
|
||||
await cls._do(cls.RECORD_VOICE, sleep)
|
||||
|
||||
@classmethod
|
||||
async def upload_voice(cls, sleep=None):
|
||||
"""
|
||||
Do upload voice
|
||||
|
||||
:param sleep: sleep timeout
|
||||
:return:
|
||||
"""
|
||||
await cls._do(cls.UPLOAD_VOICE, sleep)
|
||||
|
||||
@classmethod
|
||||
async def upload_document(cls, sleep=None):
|
||||
"""
|
||||
|
|
|
|||
20
aiogram/types/chat_invite_link.py
Normal file
20
aiogram/types/chat_invite_link.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from datetime import datetime
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
from .user import User
|
||||
|
||||
|
||||
class ChatInviteLink(base.TelegramObject):
|
||||
"""
|
||||
Represents an invite link for a chat.
|
||||
|
||||
https://core.telegram.org/bots/api#chatinvitelink
|
||||
"""
|
||||
|
||||
invite_link: base.String = fields.Field()
|
||||
creator: User = fields.Field(base=User)
|
||||
is_primary: base.Boolean = fields.Field()
|
||||
is_revoked: base.Boolean = fields.Field()
|
||||
expire_date: datetime = fields.DateTimeField()
|
||||
member_limit: base.Integer = fields.Field()
|
||||
16
aiogram/types/chat_location.py
Normal file
16
aiogram/types/chat_location.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from . import base
|
||||
from . import fields
|
||||
from .location import Location
|
||||
|
||||
|
||||
class ChatLocation(base.TelegramObject):
|
||||
"""
|
||||
Represents a location to which a chat is connected.
|
||||
|
||||
https://core.telegram.org/bots/api#chatlocation
|
||||
"""
|
||||
location: Location = fields.Field()
|
||||
address: base.String = fields.Field()
|
||||
|
||||
def __init__(self, location: Location, address: base.String):
|
||||
super().__init__(location=location, address=address)
|
||||
|
|
@ -1,47 +1,13 @@
|
|||
import datetime
|
||||
import warnings
|
||||
import typing
|
||||
from typing import Optional
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
from . import base, fields
|
||||
from .user import User
|
||||
from ..utils import helper
|
||||
|
||||
|
||||
class ChatMember(base.TelegramObject):
|
||||
"""
|
||||
This object contains information about one member of a chat.
|
||||
|
||||
https://core.telegram.org/bots/api#chatmember
|
||||
"""
|
||||
user: User = fields.Field(base=User)
|
||||
status: base.String = fields.Field()
|
||||
custom_title: base.String = fields.Field()
|
||||
until_date: datetime.datetime = fields.DateTimeField()
|
||||
can_be_edited: base.Boolean = fields.Field()
|
||||
can_change_info: base.Boolean = fields.Field()
|
||||
can_post_messages: base.Boolean = fields.Field()
|
||||
can_edit_messages: base.Boolean = fields.Field()
|
||||
can_delete_messages: base.Boolean = fields.Field()
|
||||
can_invite_users: base.Boolean = fields.Field()
|
||||
can_restrict_members: base.Boolean = fields.Field()
|
||||
can_pin_messages: base.Boolean = fields.Field()
|
||||
can_promote_members: base.Boolean = fields.Field()
|
||||
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_chat_admin(self) -> bool:
|
||||
return ChatMemberStatus.is_chat_admin(self.status)
|
||||
|
||||
def is_chat_member(self) -> bool:
|
||||
return ChatMemberStatus.is_chat_member(self.status)
|
||||
|
||||
def __int__(self) -> int:
|
||||
return self.user.id
|
||||
T = typing.TypeVar('T')
|
||||
|
||||
|
||||
class ChatMemberStatus(helper.Helper):
|
||||
|
|
@ -51,16 +17,176 @@ class ChatMemberStatus(helper.Helper):
|
|||
mode = helper.HelperMode.lowercase
|
||||
|
||||
CREATOR = helper.Item() # creator
|
||||
OWNER = CREATOR # creator
|
||||
ADMINISTRATOR = helper.Item() # administrator
|
||||
MEMBER = helper.Item() # member
|
||||
RESTRICTED = helper.Item() # restricted
|
||||
LEFT = helper.Item() # left
|
||||
KICKED = helper.Item() # kicked
|
||||
BANNED = KICKED # kicked
|
||||
|
||||
@classmethod
|
||||
def is_chat_creator(cls, role: str) -> bool:
|
||||
return role == cls.CREATOR
|
||||
|
||||
@classmethod
|
||||
def is_chat_admin(cls, role: str) -> bool:
|
||||
return role in [cls.ADMINISTRATOR, cls.CREATOR]
|
||||
return role in (cls.ADMINISTRATOR, cls.CREATOR)
|
||||
|
||||
@classmethod
|
||||
def is_chat_member(cls, role: str) -> bool:
|
||||
return role in [cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR, cls.RESTRICTED]
|
||||
return role in (cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR, cls.RESTRICTED)
|
||||
|
||||
@classmethod
|
||||
def get_class_by_status(cls, status: str) -> Optional["ChatMember"]:
|
||||
return {
|
||||
cls.OWNER: ChatMemberOwner,
|
||||
cls.ADMINISTRATOR: ChatMemberAdministrator,
|
||||
cls.MEMBER: ChatMemberMember,
|
||||
cls.RESTRICTED: ChatMemberRestricted,
|
||||
cls.LEFT: ChatMemberLeft,
|
||||
cls.BANNED: ChatMemberBanned,
|
||||
}.get(status)
|
||||
|
||||
|
||||
class ChatMember(base.TelegramObject):
|
||||
"""
|
||||
This object contains information about one member of a chat.
|
||||
Currently, the following 6 types of chat members are supported:
|
||||
ChatMemberOwner
|
||||
ChatMemberAdministrator
|
||||
ChatMemberMember
|
||||
ChatMemberRestricted
|
||||
ChatMemberLeft
|
||||
ChatMemberBanned
|
||||
|
||||
https://core.telegram.org/bots/api#chatmember
|
||||
"""
|
||||
status: base.String = fields.Field()
|
||||
user: User = fields.Field(base=User)
|
||||
|
||||
def __int__(self) -> int:
|
||||
return self.user.id
|
||||
|
||||
@classmethod
|
||||
def resolve(cls, **kwargs) -> "ChatMember":
|
||||
status = kwargs.get("status")
|
||||
mapping = {
|
||||
ChatMemberStatus.OWNER: ChatMemberOwner,
|
||||
ChatMemberStatus.ADMINISTRATOR: ChatMemberAdministrator,
|
||||
ChatMemberStatus.MEMBER: ChatMemberMember,
|
||||
ChatMemberStatus.RESTRICTED: ChatMemberRestricted,
|
||||
ChatMemberStatus.LEFT: ChatMemberLeft,
|
||||
ChatMemberStatus.BANNED: ChatMemberBanned,
|
||||
}
|
||||
class_ = mapping.get(status)
|
||||
if class_ is None:
|
||||
raise ValueError(f"Can't find `ChatMember` class for status `{status}`")
|
||||
|
||||
return class_(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def to_object(cls,
|
||||
data: typing.Dict[str, typing.Any],
|
||||
conf: typing.Dict[str, typing.Any] = None
|
||||
) -> "ChatMember":
|
||||
return cls.resolve(**data)
|
||||
|
||||
def is_chat_creator(self) -> bool:
|
||||
return ChatMemberStatus.is_chat_creator(self.status)
|
||||
|
||||
def is_chat_admin(self) -> bool:
|
||||
return ChatMemberStatus.is_chat_admin(self.status)
|
||||
|
||||
def is_chat_member(self) -> bool:
|
||||
return ChatMemberStatus.is_chat_member(self.status)
|
||||
|
||||
|
||||
class ChatMemberOwner(ChatMember):
|
||||
"""
|
||||
Represents a chat member that owns the chat and has all
|
||||
administrator privileges.
|
||||
https://core.telegram.org/bots/api#chatmemberowner
|
||||
"""
|
||||
status: base.String = fields.Field(default=ChatMemberStatus.OWNER)
|
||||
user: User = fields.Field(base=User)
|
||||
custom_title: base.String = fields.Field()
|
||||
is_anonymous: base.Boolean = fields.Field()
|
||||
|
||||
|
||||
class ChatMemberAdministrator(ChatMember):
|
||||
"""
|
||||
Represents a chat member that has some additional privileges.
|
||||
|
||||
https://core.telegram.org/bots/api#chatmemberadministrator
|
||||
"""
|
||||
status: base.String = fields.Field(default=ChatMemberStatus.ADMINISTRATOR)
|
||||
user: User = fields.Field(base=User)
|
||||
can_be_edited: base.Boolean = fields.Field()
|
||||
custom_title: base.String = fields.Field()
|
||||
is_anonymous: base.Boolean = fields.Field()
|
||||
can_manage_chat: base.Boolean = fields.Field()
|
||||
can_post_messages: base.Boolean = fields.Field()
|
||||
can_edit_messages: base.Boolean = fields.Field()
|
||||
can_delete_messages: base.Boolean = fields.Field()
|
||||
can_manage_voice_chats: base.Boolean = fields.Field()
|
||||
can_restrict_members: base.Boolean = fields.Field()
|
||||
can_promote_members: 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()
|
||||
|
||||
|
||||
class ChatMemberMember(ChatMember):
|
||||
"""
|
||||
Represents a chat member that has no additional privileges or
|
||||
restrictions.
|
||||
|
||||
https://core.telegram.org/bots/api#chatmembermember
|
||||
"""
|
||||
status: base.String = fields.Field(default=ChatMemberStatus.MEMBER)
|
||||
user: User = fields.Field(base=User)
|
||||
|
||||
|
||||
class ChatMemberRestricted(ChatMember):
|
||||
"""
|
||||
Represents a chat member that is under certain restrictions in the
|
||||
chat. Supergroups only.
|
||||
|
||||
https://core.telegram.org/bots/api#chatmemberrestricted
|
||||
"""
|
||||
status: base.String = fields.Field(default=ChatMemberStatus.RESTRICTED)
|
||||
user: User = fields.Field(base=User)
|
||||
is_member: 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()
|
||||
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()
|
||||
until_date: datetime.datetime = fields.DateTimeField()
|
||||
|
||||
|
||||
class ChatMemberLeft(ChatMember):
|
||||
"""
|
||||
Represents a chat member that isn't currently a member of the chat,
|
||||
but may join it themselves.
|
||||
|
||||
https://core.telegram.org/bots/api#chatmemberleft
|
||||
"""
|
||||
status: base.String = fields.Field(default=ChatMemberStatus.LEFT)
|
||||
user: User = fields.Field(base=User)
|
||||
|
||||
|
||||
class ChatMemberBanned(ChatMember):
|
||||
"""
|
||||
Represents a chat member that was banned in the chat and can't
|
||||
return to the chat or view chat messages.
|
||||
|
||||
https://core.telegram.org/bots/api#chatmemberbanned
|
||||
"""
|
||||
status: base.String = fields.Field(default=ChatMemberStatus.BANNED)
|
||||
user: User = fields.Field(base=User)
|
||||
until_date: datetime.datetime = fields.DateTimeField()
|
||||
|
|
|
|||
22
aiogram/types/chat_member_updated.py
Normal file
22
aiogram/types/chat_member_updated.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import datetime
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
from .chat import Chat
|
||||
from .chat_invite_link import ChatInviteLink
|
||||
from .chat_member import ChatMember
|
||||
from .user import User
|
||||
|
||||
|
||||
class ChatMemberUpdated(base.TelegramObject):
|
||||
"""
|
||||
This object represents changes in the status of a chat member.
|
||||
|
||||
https://core.telegram.org/bots/api#chatmemberupdated
|
||||
"""
|
||||
chat: Chat = fields.Field(base=Chat)
|
||||
from_user: User = fields.Field(alias="from", base=User)
|
||||
date: datetime.datetime = fields.DateTimeField()
|
||||
old_chat_member: ChatMember = fields.Field(base=ChatMember)
|
||||
new_chat_member: ChatMember = fields.Field(base=ChatMember)
|
||||
invite_link: ChatInviteLink = fields.Field(base=ChatInviteLink)
|
||||
|
|
@ -3,9 +3,7 @@ from . import base, fields
|
|||
|
||||
class Dice(base.TelegramObject):
|
||||
"""
|
||||
This object represents a dice with random value from 1 to 6.
|
||||
(Yes, we're aware of the “proper” singular of die.
|
||||
But it's awkward, and we decided to help it change. One dice at a time!)
|
||||
This object represents an animated emoji that displays a random value.
|
||||
|
||||
https://core.telegram.org/bots/api#dice
|
||||
"""
|
||||
|
|
@ -17,3 +15,6 @@ class DiceEmoji:
|
|||
DICE = '🎲'
|
||||
DART = '🎯'
|
||||
BASKETBALL = '🏀'
|
||||
FOOTBALL = '⚽'
|
||||
SLOT_MACHINE = '🎰'
|
||||
BOWLING = '🎳'
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from . import base
|
|||
from . import fields
|
||||
from . import mixins
|
||||
from .photo_size import PhotoSize
|
||||
from ..utils import helper
|
||||
|
||||
|
||||
class Document(base.TelegramObject, mixins.Downloadable):
|
||||
|
|
@ -16,3 +17,34 @@ class Document(base.TelegramObject, mixins.Downloadable):
|
|||
file_name: base.String = fields.Field()
|
||||
mime_type: base.String = fields.Field()
|
||||
file_size: base.Integer = fields.Field()
|
||||
|
||||
@property
|
||||
def mime_base(self) -> str:
|
||||
base_type, _, _ = self.mime_type.partition('/')
|
||||
return base_type
|
||||
|
||||
@property
|
||||
def mime_subtype(self) -> str:
|
||||
_, _, subtype = self.mime_type.partition('/')
|
||||
return subtype
|
||||
|
||||
|
||||
class MimeBase(helper.Helper):
|
||||
"""
|
||||
List of mime base types registered in IANA
|
||||
|
||||
https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||
"""
|
||||
|
||||
mode = helper.HelperMode.lowercase
|
||||
|
||||
APPLICATION = helper.Item() # application
|
||||
AUDIO = helper.Item() # audio
|
||||
EXAMPLE = helper.Item() # example
|
||||
FONT = helper.Item() # font
|
||||
IMAGE = helper.Item() # image
|
||||
MESSAGE = helper.Item() # message
|
||||
MODEL = helper.Item() # model
|
||||
MULTIPART = helper.Item() # multipart
|
||||
TEXT = helper.Item() # text
|
||||
VIDEO = helper.Item() # video
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ class Field(BaseField):
|
|||
and not hasattr(value, 'to_python'):
|
||||
if not isinstance(parent, weakref.ReferenceType):
|
||||
parent = weakref.ref(parent)
|
||||
return self.base_object(conf={'parent':parent}, **value)
|
||||
return self.base_object.to_object(conf={'parent': parent}, data=value)
|
||||
return value
|
||||
|
||||
|
||||
|
|
@ -129,18 +129,16 @@ class ListField(Field):
|
|||
super(ListField, self).__init__(*args, default=default, **kwargs)
|
||||
|
||||
def serialize(self, value):
|
||||
result = []
|
||||
if value is None:
|
||||
return None
|
||||
serialize = super(ListField, self).serialize
|
||||
for item in value:
|
||||
result.append(serialize(item))
|
||||
return result
|
||||
return [serialize(item) for item in value]
|
||||
|
||||
def deserialize(self, value, parent=None):
|
||||
result = []
|
||||
if value is None:
|
||||
return None
|
||||
deserialize = super(ListField, self).deserialize
|
||||
for item in value:
|
||||
result.append(deserialize(item, parent=parent))
|
||||
return result
|
||||
return [deserialize(item, parent=parent) for item in value]
|
||||
|
||||
|
||||
class ListOfLists(Field):
|
||||
|
|
@ -148,9 +146,7 @@ class ListOfLists(Field):
|
|||
result = []
|
||||
serialize = super(ListOfLists, self).serialize
|
||||
for row in value:
|
||||
row_result = []
|
||||
for item in row:
|
||||
row_result.append(serialize(item))
|
||||
row_result = [serialize(item) for item in row]
|
||||
result.append(row_result)
|
||||
return result
|
||||
|
||||
|
|
@ -159,9 +155,7 @@ class ListOfLists(Field):
|
|||
deserialize = super(ListOfLists, self).deserialize
|
||||
if hasattr(value, '__iter__'):
|
||||
for row in value:
|
||||
row_result = []
|
||||
for item in row:
|
||||
row_result.append(deserialize(item, parent=parent))
|
||||
row_result = [deserialize(item, parent=parent) for item in row]
|
||||
result.append(row_result)
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -6,31 +6,28 @@ from . import fields
|
|||
|
||||
class ForceReply(base.TelegramObject):
|
||||
"""
|
||||
Upon receiving a message with this object,
|
||||
Telegram clients will display a reply interface to the user
|
||||
(act as if the user has selected the bot‘s message and tapped ’Reply').
|
||||
This can be extremely useful if you want to create user-friendly step-by-step
|
||||
Upon receiving a message with this object, Telegram clients will
|
||||
display a reply interface to the user (act as if the user has
|
||||
selected the bot's message and tapped 'Reply'). This can be
|
||||
extremely useful if you want to create user-friendly step-by-step
|
||||
interfaces without having to sacrifice privacy mode.
|
||||
|
||||
Example: A poll bot for groups runs in privacy mode
|
||||
(only receives commands, replies to its messages and mentions).
|
||||
There could be two ways to create a new poll
|
||||
|
||||
The last option is definitely more attractive.
|
||||
And if you use ForceReply in your bot‘s questions, it will receive the user’s answers even
|
||||
if it only receives replies, commands and mentions — without any extra work for the user.
|
||||
|
||||
https://core.telegram.org/bots/api#forcereply
|
||||
"""
|
||||
force_reply: base.Boolean = fields.Field(default=True)
|
||||
input_field_placeholder: base.String = fields.Field()
|
||||
selective: base.Boolean = fields.Field()
|
||||
|
||||
@classmethod
|
||||
def create(cls, selective: typing.Optional[base.Boolean] = None):
|
||||
def create(cls,
|
||||
input_field_placeholder: typing.Optional[base.String] = None,
|
||||
selective: typing.Optional[base.Boolean] = None,
|
||||
) -> 'ForceReply':
|
||||
"""
|
||||
Create new force reply
|
||||
|
||||
:param selective:
|
||||
:param input_field_placeholder:
|
||||
:return:
|
||||
"""
|
||||
return cls(selective=selective)
|
||||
return cls(selective=selective, input_field_placeholder=input_field_placeholder)
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class InlineKeyboardMarkup(base.TelegramObject):
|
|||
if index % self.row_width == 0:
|
||||
self.inline_keyboard.append(row)
|
||||
row = []
|
||||
if len(row) > 0:
|
||||
if row:
|
||||
self.inline_keyboard.append(row)
|
||||
return self
|
||||
|
||||
|
|
@ -62,9 +62,7 @@ class InlineKeyboardMarkup(base.TelegramObject):
|
|||
:return: self
|
||||
:rtype: :obj:`types.InlineKeyboardMarkup`
|
||||
"""
|
||||
btn_array = []
|
||||
for button in args:
|
||||
btn_array.append(button)
|
||||
btn_array = [button for button in args]
|
||||
self.inline_keyboard.append(btn_array)
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -17,17 +17,18 @@ class InlineQuery(base.TelegramObject):
|
|||
"""
|
||||
id: base.String = fields.Field()
|
||||
from_user: User = fields.Field(alias='from', base=User)
|
||||
location: Location = fields.Field(base=Location)
|
||||
query: base.String = fields.Field()
|
||||
offset: base.String = fields.Field()
|
||||
chat_type: base.String = fields.Field()
|
||||
location: Location = fields.Field(base=Location)
|
||||
|
||||
async def answer(self,
|
||||
results: typing.List[InlineQueryResult],
|
||||
cache_time: typing.Union[base.Integer, None] = None,
|
||||
is_personal: typing.Union[base.Boolean, None] = None,
|
||||
next_offset: typing.Union[base.String, None] = None,
|
||||
switch_pm_text: typing.Union[base.String, None] = None,
|
||||
switch_pm_parameter: typing.Union[base.String, None] = None):
|
||||
cache_time: typing.Optional[base.Integer] = None,
|
||||
is_personal: typing.Optional[base.Boolean] = None,
|
||||
next_offset: typing.Optional[base.String] = None,
|
||||
switch_pm_text: typing.Optional[base.String] = None,
|
||||
switch_pm_parameter: typing.Optional[base.String] = None):
|
||||
"""
|
||||
Use this method to send answers to an inline query.
|
||||
No more than 50 results per query are allowed.
|
||||
|
|
@ -38,22 +39,22 @@ class InlineQuery(base.TelegramObject):
|
|||
:type results: :obj:`typing.List[types.InlineQueryResult]`
|
||||
:param cache_time: The maximum amount of time in seconds that the result of the
|
||||
inline query may be cached on the server. Defaults to 300.
|
||||
:type cache_time: :obj:`typing.Union[base.Integer, None]`
|
||||
:type cache_time: :obj:`typing.Optional[base.Integer]`
|
||||
:param is_personal: Pass True, if results may be cached on the server side only
|
||||
for the user that sent the query. By default, results may be returned to any user who sends the same query
|
||||
:type is_personal: :obj:`typing.Union[base.Boolean, None]`
|
||||
:type is_personal: :obj:`typing.Optional[base.Boolean]`
|
||||
:param next_offset: Pass the offset that a client should send in the
|
||||
next query with the same text to receive more results.
|
||||
Pass an empty string if there are no more results or if you don‘t support pagination.
|
||||
Offset length can’t exceed 64 bytes.
|
||||
:type next_offset: :obj:`typing.Union[base.String, None]`
|
||||
:type next_offset: :obj:`typing.Optional[base.String]`
|
||||
:param switch_pm_text: If passed, clients will display a button with specified text that
|
||||
switches the user to a private chat with the bot and sends the bot a start message
|
||||
with the parameter switch_pm_parameter
|
||||
:type switch_pm_text: :obj:`typing.Union[base.String, None]`
|
||||
:type switch_pm_text: :obj:`typing.Optional[base.String]`
|
||||
:param switch_pm_parameter: Deep-linking parameter for the /start message sent to the bot when
|
||||
user presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed.
|
||||
:type switch_pm_parameter: :obj:`typing.Union[base.String, None]`
|
||||
:type switch_pm_parameter: :obj:`typing.Optional[base.String]`
|
||||
:return: On success, True is returned
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from . import base
|
|||
from . import fields
|
||||
from .inline_keyboard import InlineKeyboardMarkup
|
||||
from .input_message_content import InputMessageContent
|
||||
from .message_entity import MessageEntity
|
||||
|
||||
|
||||
class InlineQueryResult(base.TelegramObject):
|
||||
|
|
@ -83,23 +84,29 @@ class InlineQueryResultPhoto(InlineQueryResult):
|
|||
caption: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
photo_url: base.String,
|
||||
thumb_url: base.String,
|
||||
photo_width: typing.Optional[base.Integer] = None,
|
||||
photo_height: typing.Optional[base.Integer] = None,
|
||||
title: typing.Optional[base.String] = None,
|
||||
description: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultPhoto, self).__init__(id=id, photo_url=photo_url, thumb_url=thumb_url,
|
||||
photo_width=photo_width, photo_height=photo_height, title=title,
|
||||
description=description, caption=caption,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
photo_url: base.String,
|
||||
thumb_url: base.String,
|
||||
photo_width: typing.Optional[base.Integer] = None,
|
||||
photo_height: typing.Optional[base.Integer] = None,
|
||||
title: typing.Optional[base.String] = None,
|
||||
description: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, photo_url=photo_url, thumb_url=thumb_url,
|
||||
photo_width=photo_width, photo_height=photo_height, title=title,
|
||||
description=description, caption=caption,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
reply_markup=reply_markup, input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultGif(InlineQueryResult):
|
||||
|
|
@ -123,23 +130,29 @@ class InlineQueryResultGif(InlineQueryResult):
|
|||
caption: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
gif_url: base.String,
|
||||
gif_width: typing.Optional[base.Integer] = None,
|
||||
gif_height: typing.Optional[base.Integer] = None,
|
||||
gif_duration: typing.Optional[base.Integer] = None,
|
||||
thumb_url: typing.Optional[base.String] = None,
|
||||
title: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultGif, self).__init__(id=id, gif_url=gif_url, gif_width=gif_width,
|
||||
gif_height=gif_height, gif_duration=gif_duration,
|
||||
thumb_url=thumb_url, title=title, caption=caption,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
gif_url: base.String,
|
||||
gif_width: typing.Optional[base.Integer] = None,
|
||||
gif_height: typing.Optional[base.Integer] = None,
|
||||
gif_duration: typing.Optional[base.Integer] = None,
|
||||
thumb_url: typing.Optional[base.String] = None,
|
||||
title: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, gif_url=gif_url, gif_width=gif_width, gif_height=gif_height,
|
||||
gif_duration=gif_duration, thumb_url=thumb_url, title=title,
|
||||
caption=caption, parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
caption_entities=caption_entities,
|
||||
input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultMpeg4Gif(InlineQueryResult):
|
||||
|
|
@ -163,23 +176,30 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
|
|||
caption: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
mpeg4_url: base.String,
|
||||
thumb_url: base.String,
|
||||
mpeg4_width: typing.Optional[base.Integer] = None,
|
||||
mpeg4_height: typing.Optional[base.Integer] = None,
|
||||
mpeg4_duration: typing.Optional[base.Integer] = None,
|
||||
title: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultMpeg4Gif, self).__init__(id=id, mpeg4_url=mpeg4_url, mpeg4_width=mpeg4_width,
|
||||
mpeg4_height=mpeg4_height, mpeg4_duration=mpeg4_duration,
|
||||
thumb_url=thumb_url, title=title, caption=caption,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
mpeg4_url: base.String,
|
||||
thumb_url: base.String,
|
||||
mpeg4_width: typing.Optional[base.Integer] = None,
|
||||
mpeg4_height: typing.Optional[base.Integer] = None,
|
||||
mpeg4_duration: typing.Optional[base.Integer] = None,
|
||||
title: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, mpeg4_url=mpeg4_url, mpeg4_width=mpeg4_width,
|
||||
mpeg4_height=mpeg4_height, mpeg4_duration=mpeg4_duration,
|
||||
thumb_url=thumb_url, title=title, caption=caption,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
caption_entities=caption_entities,
|
||||
input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultVideo(InlineQueryResult):
|
||||
|
|
@ -207,26 +227,32 @@ class InlineQueryResultVideo(InlineQueryResult):
|
|||
description: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
video_url: base.String,
|
||||
mime_type: base.String,
|
||||
thumb_url: base.String,
|
||||
title: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
video_width: typing.Optional[base.Integer] = None,
|
||||
video_height: typing.Optional[base.Integer] = None,
|
||||
video_duration: typing.Optional[base.Integer] = None,
|
||||
description: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultVideo, self).__init__(id=id, video_url=video_url, mime_type=mime_type,
|
||||
thumb_url=thumb_url, title=title, caption=caption,
|
||||
video_width=video_width, video_height=video_height,
|
||||
video_duration=video_duration, description=description,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
video_url: base.String,
|
||||
mime_type: base.String,
|
||||
thumb_url: base.String,
|
||||
title: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
video_width: typing.Optional[base.Integer] = None,
|
||||
video_height: typing.Optional[base.Integer] = None,
|
||||
video_duration: typing.Optional[base.Integer] = None,
|
||||
description: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, video_url=video_url, mime_type=mime_type, thumb_url=thumb_url,
|
||||
title=title, caption=caption, video_width=video_width,
|
||||
video_height=video_height, video_duration=video_duration,
|
||||
description=description, parse_mode=parse_mode,
|
||||
reply_markup=reply_markup, caption_entities=caption_entities,
|
||||
input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultAudio(InlineQueryResult):
|
||||
|
|
@ -248,21 +274,27 @@ class InlineQueryResultAudio(InlineQueryResult):
|
|||
audio_duration: base.Integer = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
audio_url: base.String,
|
||||
title: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
performer: typing.Optional[base.String] = None,
|
||||
audio_duration: typing.Optional[base.Integer] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultAudio, self).__init__(id=id, audio_url=audio_url, title=title,
|
||||
caption=caption, parse_mode=parse_mode,
|
||||
performer=performer, audio_duration=audio_duration,
|
||||
reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
audio_url: base.String,
|
||||
title: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
performer: typing.Optional[base.String] = None,
|
||||
audio_duration: typing.Optional[base.Integer] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, audio_url=audio_url, title=title,
|
||||
caption=caption, parse_mode=parse_mode,
|
||||
performer=performer, audio_duration=audio_duration,
|
||||
reply_markup=reply_markup, caption_entities=caption_entities,
|
||||
input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultVoice(InlineQueryResult):
|
||||
|
|
@ -285,19 +317,25 @@ class InlineQueryResultVoice(InlineQueryResult):
|
|||
voice_duration: base.Integer = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
voice_url: base.String,
|
||||
title: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
voice_duration: typing.Optional[base.Integer] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultVoice, self).__init__(id=id, voice_url=voice_url, title=title,
|
||||
caption=caption, voice_duration=voice_duration,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
voice_url: base.String,
|
||||
title: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
voice_duration: typing.Optional[base.Integer] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, voice_url=voice_url, title=title, caption=caption,
|
||||
voice_duration=voice_duration, parse_mode=parse_mode,
|
||||
reply_markup=reply_markup, caption_entities=caption_entities,
|
||||
input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultDocument(InlineQueryResult):
|
||||
|
|
@ -323,25 +361,31 @@ class InlineQueryResultDocument(InlineQueryResult):
|
|||
thumb_width: base.Integer = fields.Field()
|
||||
thumb_height: base.Integer = fields.Field()
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
title: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
document_url: typing.Optional[base.String] = None,
|
||||
mime_type: typing.Optional[base.String] = None,
|
||||
description: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
thumb_url: typing.Optional[base.String] = None,
|
||||
thumb_width: typing.Optional[base.Integer] = None,
|
||||
thumb_height: typing.Optional[base.Integer] = None):
|
||||
super(InlineQueryResultDocument, self).__init__(id=id, title=title, caption=caption,
|
||||
document_url=document_url, mime_type=mime_type,
|
||||
description=description, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content,
|
||||
thumb_url=thumb_url, thumb_width=thumb_width,
|
||||
thumb_height=thumb_height, parse_mode=parse_mode)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
title: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
document_url: typing.Optional[base.String] = None,
|
||||
mime_type: typing.Optional[base.String] = None,
|
||||
description: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
thumb_url: typing.Optional[base.String] = None,
|
||||
thumb_width: typing.Optional[base.Integer] = None,
|
||||
thumb_height: typing.Optional[base.Integer] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, title=title, caption=caption, parse_mode=parse_mode,
|
||||
caption_entities=caption_entities, document_url=document_url,
|
||||
mime_type=mime_type, description=description, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content,
|
||||
thumb_url=thumb_url, thumb_width=thumb_width,
|
||||
thumb_height=thumb_height,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultLocation(InlineQueryResult):
|
||||
|
|
@ -352,16 +396,16 @@ class InlineQueryResultLocation(InlineQueryResult):
|
|||
Alternatively, you can use input_message_content to send a message with the specified content
|
||||
instead of the location.
|
||||
|
||||
Note: This will only work in Telegram versions released after 9 April, 2016.
|
||||
Older clients will ignore them.
|
||||
|
||||
https://core.telegram.org/bots/api#inlinequeryresultlocation
|
||||
"""
|
||||
type: base.String = fields.Field(alias='type', default='location')
|
||||
latitude: base.Float = fields.Field()
|
||||
longitude: base.Float = fields.Field()
|
||||
title: base.String = fields.Field()
|
||||
horizontal_accuracy: typing.Optional[base.Float] = fields.Field()
|
||||
live_period: base.Integer = fields.Field()
|
||||
heading: typing.Optional[base.Integer] = fields.Field()
|
||||
proximity_alert_radius: typing.Optional[base.Integer] = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
thumb_url: base.String = fields.Field()
|
||||
thumb_width: base.Integer = fields.Field()
|
||||
|
|
@ -372,18 +416,31 @@ class InlineQueryResultLocation(InlineQueryResult):
|
|||
latitude: base.Float,
|
||||
longitude: base.Float,
|
||||
title: base.String,
|
||||
horizontal_accuracy: typing.Optional[base.Float] = None,
|
||||
live_period: typing.Optional[base.Integer] = None,
|
||||
heading: typing.Optional[base.Integer] = None,
|
||||
proximity_alert_radius: typing.Optional[base.Integer] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
thumb_url: typing.Optional[base.String] = None,
|
||||
thumb_width: typing.Optional[base.Integer] = None,
|
||||
thumb_height: typing.Optional[base.Integer] = None):
|
||||
super(InlineQueryResultLocation, self).__init__(id=id, latitude=latitude, longitude=longitude,
|
||||
title=title, live_period=live_period,
|
||||
reply_markup=reply_markup,
|
||||
input_message_content=input_message_content,
|
||||
thumb_url=thumb_url, thumb_width=thumb_width,
|
||||
thumb_height=thumb_height)
|
||||
thumb_height: typing.Optional[base.Integer] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id,
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
title=title,
|
||||
horizontal_accuracy=horizontal_accuracy,
|
||||
live_period=live_period,
|
||||
heading=heading,
|
||||
proximity_alert_radius=proximity_alert_radius,
|
||||
reply_markup=reply_markup,
|
||||
input_message_content=input_message_content,
|
||||
thumb_url=thumb_url,
|
||||
thumb_width=thumb_width,
|
||||
thumb_height=thumb_height
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultVenue(InlineQueryResult):
|
||||
|
|
@ -404,31 +461,40 @@ class InlineQueryResultVenue(InlineQueryResult):
|
|||
title: base.String = fields.Field()
|
||||
address: base.String = fields.Field()
|
||||
foursquare_id: base.String = fields.Field()
|
||||
foursquare_type: base.String = fields.Field()
|
||||
google_place_id: base.String = fields.Field()
|
||||
google_place_type: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
thumb_url: base.String = fields.Field()
|
||||
thumb_width: base.Integer = fields.Field()
|
||||
thumb_height: base.Integer = fields.Field()
|
||||
foursquare_type: base.String = fields.Field()
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
latitude: base.Float,
|
||||
longitude: base.Float,
|
||||
title: base.String,
|
||||
address: base.String,
|
||||
foursquare_id: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
thumb_url: typing.Optional[base.String] = None,
|
||||
thumb_width: typing.Optional[base.Integer] = None,
|
||||
thumb_height: typing.Optional[base.Integer] = None,
|
||||
foursquare_type: typing.Optional[base.String] = None):
|
||||
super(InlineQueryResultVenue, self).__init__(id=id, latitude=latitude, longitude=longitude,
|
||||
title=title, address=address, foursquare_id=foursquare_id,
|
||||
reply_markup=reply_markup,
|
||||
input_message_content=input_message_content, thumb_url=thumb_url,
|
||||
thumb_width=thumb_width, thumb_height=thumb_height,
|
||||
foursquare_type=foursquare_type)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
latitude: base.Float,
|
||||
longitude: base.Float,
|
||||
title: base.String,
|
||||
address: base.String,
|
||||
foursquare_id: typing.Optional[base.String] = None,
|
||||
foursquare_type: typing.Optional[base.String] = None,
|
||||
google_place_id: typing.Optional[base.String] = None,
|
||||
google_place_type: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
thumb_url: typing.Optional[base.String] = None,
|
||||
thumb_width: typing.Optional[base.Integer] = None,
|
||||
thumb_height: typing.Optional[base.Integer] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, latitude=latitude, longitude=longitude, title=title,
|
||||
address=address, foursquare_id=foursquare_id,
|
||||
foursquare_type=foursquare_type, google_place_id=google_place_id,
|
||||
google_place_type=google_place_type, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content, thumb_url=thumb_url,
|
||||
thumb_width=thumb_width, thumb_height=thumb_height,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultContact(InlineQueryResult):
|
||||
|
|
@ -510,19 +576,24 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
|
|||
caption: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
photo_file_id: base.String,
|
||||
title: typing.Optional[base.String] = None,
|
||||
description: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultCachedPhoto, self).__init__(id=id, photo_file_id=photo_file_id, title=title,
|
||||
description=description, caption=caption,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
photo_file_id: base.String,
|
||||
title: typing.Optional[base.String] = None,
|
||||
description: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, photo_file_id=photo_file_id, title=title, description=description,
|
||||
caption=caption, parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
reply_markup=reply_markup, input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultCachedGif(InlineQueryResult):
|
||||
|
|
@ -541,18 +612,23 @@ class InlineQueryResultCachedGif(InlineQueryResult):
|
|||
caption: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
gif_file_id: base.String,
|
||||
title: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultCachedGif, self).__init__(id=id, gif_file_id=gif_file_id,
|
||||
title=title, caption=caption,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
gif_file_id: base.String,
|
||||
title: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, gif_file_id=gif_file_id, title=title, caption=caption,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
reply_markup=reply_markup, input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
|
||||
|
|
@ -571,18 +647,23 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
|
|||
caption: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
mpeg4_file_id: base.String,
|
||||
title: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultCachedMpeg4Gif, self).__init__(id=id, mpeg4_file_id=mpeg4_file_id,
|
||||
title=title, caption=caption,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
mpeg4_file_id: base.String,
|
||||
title: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, mpeg4_file_id=mpeg4_file_id, title=title, caption=caption,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
reply_markup=reply_markup, input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultCachedSticker(InlineQueryResult):
|
||||
|
|
@ -631,20 +712,25 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
|
|||
caption: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
title: base.String,
|
||||
document_file_id: base.String,
|
||||
description: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultCachedDocument, self).__init__(id=id, title=title,
|
||||
document_file_id=document_file_id,
|
||||
description=description, caption=caption,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
title: base.String,
|
||||
document_file_id: base.String,
|
||||
description: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, title=title, document_file_id=document_file_id,
|
||||
description=description, caption=caption, parse_mode=parse_mode,
|
||||
caption_entities=caption_entities, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultCachedVideo(InlineQueryResult):
|
||||
|
|
@ -664,19 +750,24 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
|
|||
caption: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
video_file_id: base.String,
|
||||
title: base.String,
|
||||
description: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultCachedVideo, self).__init__(id=id, video_file_id=video_file_id, title=title,
|
||||
description=description, caption=caption,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
video_file_id: base.String,
|
||||
title: base.String,
|
||||
description: typing.Optional[base.String] = None,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, video_file_id=video_file_id, title=title, description=description,
|
||||
caption=caption, parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
reply_markup=reply_markup, input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultCachedVoice(InlineQueryResult):
|
||||
|
|
@ -697,18 +788,23 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
|
|||
caption: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
voice_file_id: base.String,
|
||||
title: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultCachedVoice, self).__init__(id=id, voice_file_id=voice_file_id,
|
||||
title=title, caption=caption,
|
||||
parse_mode=parse_mode, reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
voice_file_id: base.String,
|
||||
title: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, voice_file_id=voice_file_id, title=title, caption=caption,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
reply_markup=reply_markup, input_message_content=input_message_content,
|
||||
)
|
||||
|
||||
|
||||
class InlineQueryResultCachedAudio(InlineQueryResult):
|
||||
|
|
@ -729,14 +825,19 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
|
|||
caption: base.String = fields.Field()
|
||||
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
|
||||
|
||||
def __init__(self, *,
|
||||
id: base.String,
|
||||
audio_file_id: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None):
|
||||
super(InlineQueryResultCachedAudio, self).__init__(id=id, audio_file_id=audio_file_id,
|
||||
caption=caption, parse_mode=parse_mode,
|
||||
reply_markup=reply_markup,
|
||||
input_message_content=input_message_content)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: base.String,
|
||||
audio_file_id: base.String,
|
||||
caption: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
|
||||
input_message_content: typing.Optional[InputMessageContent] = None,
|
||||
):
|
||||
super().__init__(
|
||||
id=id, audio_file_id=audio_file_id, caption=caption,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
reply_markup=reply_markup, input_message_content=input_message_content,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import io
|
|||
import logging
|
||||
import os
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
import aiohttp
|
||||
|
||||
|
|
@ -25,7 +27,7 @@ class InputFile(base.TelegramObject):
|
|||
https://core.telegram.org/bots/api#inputfile
|
||||
"""
|
||||
|
||||
def __init__(self, path_or_bytesio, filename=None, conf=None):
|
||||
def __init__(self, path_or_bytesio: Union[str, io.IOBase, Path], filename=None, conf=None):
|
||||
"""
|
||||
|
||||
:param path_or_bytesio:
|
||||
|
|
@ -39,12 +41,14 @@ class InputFile(base.TelegramObject):
|
|||
self._path = path_or_bytesio
|
||||
if filename is None:
|
||||
filename = os.path.split(path_or_bytesio)[-1]
|
||||
elif isinstance(path_or_bytesio, io.IOBase):
|
||||
self._path = None
|
||||
self._file = path_or_bytesio
|
||||
elif isinstance(path_or_bytesio, _WebPipe):
|
||||
elif isinstance(path_or_bytesio, (io.IOBase, _WebPipe)):
|
||||
self._path = None
|
||||
self._file = path_or_bytesio
|
||||
elif isinstance(path_or_bytesio, Path):
|
||||
self._file = path_or_bytesio.open("rb")
|
||||
self._path = path_or_bytesio.resolve()
|
||||
if filename is None:
|
||||
filename = path_or_bytesio.name
|
||||
else:
|
||||
raise TypeError('Not supported file type.')
|
||||
|
||||
|
|
@ -166,10 +170,7 @@ class _WebPipe:
|
|||
def name(self):
|
||||
if not self._name:
|
||||
*_, part = self.url.rpartition('/')
|
||||
if part:
|
||||
self._name = part
|
||||
else:
|
||||
self._name = secrets.token_urlsafe(24)
|
||||
self._name = part or secrets.token_urlsafe(24)
|
||||
return self._name
|
||||
|
||||
async def open(self):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import typing
|
|||
from . import base
|
||||
from . import fields
|
||||
from .input_file import InputFile
|
||||
from .message_entity import MessageEntity
|
||||
|
||||
ATTACHMENT_PREFIX = 'attach://'
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ class InputMedia(base.TelegramObject):
|
|||
thumb: typing.Union[base.InputFile, base.String] = fields.Field(alias='thumb', on_change='_thumb_changed')
|
||||
caption: base.String = fields.Field()
|
||||
parse_mode: base.String = fields.Field()
|
||||
caption_entities: typing.List[MessageEntity] = fields.ListField(base=MessageEntity)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._thumb_file = None
|
||||
|
|
@ -106,28 +108,48 @@ class InputMediaAnimation(InputMedia):
|
|||
height: base.Integer = fields.Field()
|
||||
duration: base.Integer = fields.Field()
|
||||
|
||||
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.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)
|
||||
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.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
type='animation', media=media, thumb=thumb, caption=caption, width=width,
|
||||
height=height, duration=duration, parse_mode=parse_mode,
|
||||
caption_entities=caption_entities, conf=kwargs,
|
||||
)
|
||||
|
||||
|
||||
class InputMediaDocument(InputMedia):
|
||||
"""
|
||||
Represents a photo to be sent.
|
||||
Represents a general file to be sent.
|
||||
|
||||
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.String = 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] = None,
|
||||
caption: base.String = None,
|
||||
parse_mode: base.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
disable_content_type_detection: typing.Optional[base.Boolean] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
type='document', media=media, thumb=thumb, caption=caption,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
disable_content_type_detection=disable_content_type_detection,
|
||||
conf=kwargs,
|
||||
)
|
||||
|
||||
|
||||
class InputMediaAudio(InputMedia):
|
||||
|
|
@ -141,17 +163,23 @@ class InputMediaAudio(InputMedia):
|
|||
performer: base.String = fields.Field()
|
||||
title: base.String = fields.Field()
|
||||
|
||||
def __init__(self, media: base.InputFile,
|
||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
caption: base.String = None,
|
||||
duration: base.Integer = None,
|
||||
performer: base.String = None,
|
||||
title: base.String = None,
|
||||
parse_mode: base.String = None, **kwargs):
|
||||
super(InputMediaAudio, self).__init__(type='audio', media=media, thumb=thumb,
|
||||
caption=caption, duration=duration,
|
||||
performer=performer, title=title,
|
||||
parse_mode=parse_mode, conf=kwargs)
|
||||
def __init__(
|
||||
self,
|
||||
media: base.InputFile,
|
||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
caption: base.String = None,
|
||||
duration: base.Integer = None,
|
||||
performer: base.String = None,
|
||||
title: base.String = None,
|
||||
parse_mode: base.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
type='audio', media=media, thumb=thumb, caption=caption,
|
||||
duration=duration, performer=performer, title=title,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities, conf=kwargs,
|
||||
)
|
||||
|
||||
|
||||
class InputMediaPhoto(InputMedia):
|
||||
|
|
@ -161,11 +189,18 @@ 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.String = 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,
|
||||
caption: base.String = None,
|
||||
parse_mode: base.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
type='photo', media=media, caption=caption, parse_mode=parse_mode,
|
||||
caption_entities=caption_entities, conf=kwargs,
|
||||
)
|
||||
|
||||
|
||||
class InputMediaVideo(InputMedia):
|
||||
|
|
@ -179,16 +214,25 @@ class InputMediaVideo(InputMedia):
|
|||
duration: base.Integer = fields.Field()
|
||||
supports_streaming: base.Boolean = fields.Field()
|
||||
|
||||
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.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)
|
||||
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.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
supports_streaming: base.Boolean = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
type='video', media=media, thumb=thumb, caption=caption,
|
||||
width=width, height=height, duration=duration,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
supports_streaming=supports_streaming, conf=kwargs
|
||||
)
|
||||
|
||||
|
||||
class MediaGroup(base.TelegramObject):
|
||||
|
|
@ -227,10 +271,10 @@ class MediaGroup(base.TelegramObject):
|
|||
media = InputMediaPhoto(**media)
|
||||
elif media_type == 'video':
|
||||
media = InputMediaVideo(**media)
|
||||
# elif media_type == 'document':
|
||||
# media = InputMediaDocument(**media)
|
||||
# elif media_type == 'audio':
|
||||
# media = InputMediaAudio(**media)
|
||||
elif media_type == 'document':
|
||||
media = InputMediaDocument(**media)
|
||||
elif media_type == 'audio':
|
||||
media = InputMediaAudio(**media)
|
||||
# elif media_type == 'animation':
|
||||
# media = InputMediaAnimation(**media)
|
||||
else:
|
||||
|
|
@ -239,8 +283,8 @@ class MediaGroup(base.TelegramObject):
|
|||
elif not isinstance(media, InputMedia):
|
||||
raise TypeError(f"Media must be an instance of InputMedia or dict, not {type(media).__name__}")
|
||||
|
||||
elif media.type in ['document', 'audio', 'animation']:
|
||||
raise ValueError(f"This type of media is not supported by media groups!")
|
||||
elif media.type == 'animation':
|
||||
raise ValueError("This type of media is not supported by media groups!")
|
||||
|
||||
self.media.append(media)
|
||||
|
||||
|
|
@ -266,78 +310,98 @@ class MediaGroup(base.TelegramObject):
|
|||
width=width, height=height, duration=duration,
|
||||
parse_mode=parse_mode)
|
||||
self.attach(animation)
|
||||
'''
|
||||
|
||||
def attach_audio(self, audio: base.InputFile,
|
||||
def attach_audio(self, audio: typing.Union[InputMediaAudio, 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,
|
||||
performer: base.String = None,
|
||||
title: base.String = None,
|
||||
parse_mode: base.String = None):
|
||||
parse_mode: base.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None):
|
||||
"""
|
||||
Attach animation
|
||||
Attach audio
|
||||
|
||||
:param audio:
|
||||
:param thumb:
|
||||
:param caption:
|
||||
:param width:
|
||||
:param height:
|
||||
:param duration:
|
||||
:param performer:
|
||||
:param title:
|
||||
:param parse_mode:
|
||||
:param caption_entities:
|
||||
"""
|
||||
if not isinstance(audio, InputMedia):
|
||||
audio = InputMediaAudio(media=audio, thumb=thumb, caption=caption,
|
||||
width=width, height=height, duration=duration,
|
||||
duration=duration,
|
||||
performer=performer, title=title,
|
||||
parse_mode=parse_mode)
|
||||
parse_mode=parse_mode,
|
||||
caption_entities=caption_entities)
|
||||
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.String = None):
|
||||
def attach_document(self, document: typing.Union[InputMediaDocument, base.InputFile],
|
||||
thumb: typing.Union[base.InputFile, base.String] = None,
|
||||
caption: base.String = None, parse_mode: base.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
disable_content_type_detection: typing.Optional[base.Boolean] = None):
|
||||
"""
|
||||
Attach document
|
||||
|
||||
:param parse_mode:
|
||||
|
||||
:param document:
|
||||
:param caption:
|
||||
:param thumb:
|
||||
:param document:
|
||||
:param parse_mode:
|
||||
:param caption_entities:
|
||||
:param disable_content_type_detection:
|
||||
"""
|
||||
if not isinstance(document, InputMedia):
|
||||
document = InputMediaDocument(media=document, thumb=thumb, caption=caption, parse_mode=parse_mode)
|
||||
document = InputMediaDocument(media=document, thumb=thumb, caption=caption,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
disable_content_type_detection=disable_content_type_detection)
|
||||
self.attach(document)
|
||||
'''
|
||||
|
||||
def attach_photo(self, photo: typing.Union[InputMediaPhoto, base.InputFile],
|
||||
caption: base.String = None):
|
||||
caption: base.String = None, parse_mode: base.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None):
|
||||
"""
|
||||
Attach photo
|
||||
|
||||
:param photo:
|
||||
:param caption:
|
||||
:param parse_mode:
|
||||
:param caption_entities:
|
||||
"""
|
||||
if not isinstance(photo, InputMedia):
|
||||
photo = InputMediaPhoto(media=photo, caption=caption)
|
||||
photo = InputMediaPhoto(media=photo, caption=caption, parse_mode=parse_mode,
|
||||
caption_entities=caption_entities)
|
||||
self.attach(photo)
|
||||
|
||||
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, parse_mode: base.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
supports_streaming: base.Boolean = None):
|
||||
"""
|
||||
Attach video
|
||||
|
||||
:param video:
|
||||
:param thumb:
|
||||
:param caption:
|
||||
:param width:
|
||||
:param height:
|
||||
:param duration:
|
||||
:param parse_mode:
|
||||
:param caption_entities:
|
||||
:param supports_streaming:
|
||||
"""
|
||||
if not isinstance(video, InputMedia):
|
||||
video = InputMediaVideo(media=video, thumb=thumb, caption=caption,
|
||||
width=width, height=height, duration=duration)
|
||||
width=width, height=height, duration=duration,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
supports_streaming=supports_streaming)
|
||||
self.attach(video)
|
||||
|
||||
def to_python(self) -> typing.List:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ import typing
|
|||
|
||||
from . import base
|
||||
from . import fields
|
||||
from .message_entity import MessageEntity
|
||||
from .labeled_price import LabeledPrice
|
||||
from ..utils.payload import generate_payload
|
||||
|
||||
|
||||
class InputMessageContent(base.TelegramObject):
|
||||
|
|
@ -26,31 +29,112 @@ class InputContactMessageContent(InputMessageContent):
|
|||
"""
|
||||
phone_number: base.String = fields.Field()
|
||||
first_name: base.String = fields.Field()
|
||||
last_name: base.String = fields.Field()
|
||||
vcard: base.String = fields.Field()
|
||||
last_name: typing.Optional[base.String] = fields.Field()
|
||||
vcard: typing.Optional[base.String] = fields.Field()
|
||||
|
||||
def __init__(self, phone_number: base.String,
|
||||
first_name: typing.Optional[base.String] = None,
|
||||
last_name: typing.Optional[base.String] = None):
|
||||
super(InputContactMessageContent, self).__init__(phone_number=phone_number, first_name=first_name,
|
||||
last_name=last_name)
|
||||
def __init__(self,
|
||||
phone_number: base.String,
|
||||
first_name: base.String = None,
|
||||
last_name: typing.Optional[base.String] = None,
|
||||
vcard: typing.Optional[base.String] = None,
|
||||
):
|
||||
super().__init__(
|
||||
phone_number=phone_number,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
vcard=vcard
|
||||
)
|
||||
|
||||
|
||||
class InputInvoiceMessageContent(InputMessageContent):
|
||||
"""
|
||||
Represents the content of an invoice message to be sent as the
|
||||
result of an inline query.
|
||||
|
||||
https://core.telegram.org/bots/api#inputinvoicemessagecontent
|
||||
"""
|
||||
|
||||
title: base.String = fields.Field()
|
||||
description: base.String = fields.Field()
|
||||
payload: base.String = fields.Field()
|
||||
provider_token: base.String = fields.Field()
|
||||
currency: base.String = fields.Field()
|
||||
prices: typing.List[LabeledPrice] = fields.ListField(base=LabeledPrice)
|
||||
max_tip_amount: typing.Optional[base.Integer] = fields.Field()
|
||||
suggested_tip_amounts: typing.Optional[
|
||||
typing.List[base.Integer]
|
||||
] = fields.ListField(base=base.Integer)
|
||||
provider_data: typing.Optional[base.String] = fields.Field()
|
||||
photo_url: typing.Optional[base.String] = fields.Field()
|
||||
photo_size: typing.Optional[base.Integer] = fields.Field()
|
||||
photo_width: typing.Optional[base.Integer] = fields.Field()
|
||||
photo_height: typing.Optional[base.Integer] = fields.Field()
|
||||
need_name: typing.Optional[base.Boolean] = fields.Field()
|
||||
need_phone_number: typing.Optional[base.Boolean] = fields.Field()
|
||||
need_email: typing.Optional[base.Boolean] = fields.Field()
|
||||
need_shipping_address: typing.Optional[base.Boolean] = fields.Field()
|
||||
send_phone_number_to_provider: typing.Optional[base.Boolean] = fields.Field()
|
||||
send_email_to_provider: typing.Optional[base.Boolean] = fields.Field()
|
||||
is_flexible: typing.Optional[base.Boolean] = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: base.String,
|
||||
description: base.String,
|
||||
payload: base.String,
|
||||
provider_token: base.String,
|
||||
currency: base.String,
|
||||
prices: typing.List[LabeledPrice] = None,
|
||||
max_tip_amount: typing.Optional[base.Integer] = None,
|
||||
suggested_tip_amounts: typing.Optional[typing.List[base.Integer]] = None,
|
||||
provider_data: typing.Optional[base.String] = None,
|
||||
photo_url: typing.Optional[base.String] = None,
|
||||
photo_size: typing.Optional[base.Integer] = None,
|
||||
photo_width: typing.Optional[base.Integer] = None,
|
||||
photo_height: typing.Optional[base.Integer] = None,
|
||||
need_name: typing.Optional[base.Boolean] = None,
|
||||
need_phone_number: typing.Optional[base.Boolean] = None,
|
||||
need_email: typing.Optional[base.Boolean] = None,
|
||||
need_shipping_address: typing.Optional[base.Boolean] = None,
|
||||
send_phone_number_to_provider: typing.Optional[base.Boolean] = None,
|
||||
send_email_to_provider: typing.Optional[base.Boolean] = None,
|
||||
is_flexible: typing.Optional[base.Boolean] = None,
|
||||
):
|
||||
if prices is None:
|
||||
prices = []
|
||||
payload = generate_payload(**locals())
|
||||
super().__init__(**payload)
|
||||
|
||||
|
||||
class InputLocationMessageContent(InputMessageContent):
|
||||
"""
|
||||
Represents the content of a location message to be sent as the result of an inline query.
|
||||
|
||||
Note: This will only work in Telegram versions released after 9 April, 2016.
|
||||
Older clients will ignore them.
|
||||
|
||||
https://core.telegram.org/bots/api#inputlocationmessagecontent
|
||||
"""
|
||||
latitude: base.Float = fields.Field()
|
||||
longitude: base.Float = fields.Field()
|
||||
horizontal_accuracy: typing.Optional[base.Float] = fields.Field()
|
||||
live_period: typing.Optional[base.Integer] = fields.Field()
|
||||
heading: typing.Optional[base.Integer] = fields.Field()
|
||||
proximity_alert_radius: typing.Optional[base.Integer] = fields.Field()
|
||||
|
||||
def __init__(self, latitude: base.Float,
|
||||
longitude: base.Float):
|
||||
super(InputLocationMessageContent, self).__init__(latitude=latitude, longitude=longitude)
|
||||
def __init__(self,
|
||||
latitude: base.Float,
|
||||
longitude: base.Float,
|
||||
horizontal_accuracy: typing.Optional[base.Float] = None,
|
||||
live_period: typing.Optional[base.Integer] = None,
|
||||
heading: typing.Optional[base.Integer] = None,
|
||||
proximity_alert_radius: typing.Optional[base.Integer] = None,
|
||||
):
|
||||
super().__init__(
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
horizontal_accuracy=horizontal_accuracy,
|
||||
live_period=live_period,
|
||||
heading=heading,
|
||||
proximity_alert_radius=proximity_alert_radius,
|
||||
)
|
||||
|
||||
|
||||
class InputTextMessageContent(InputMessageContent):
|
||||
|
|
@ -60,7 +144,8 @@ class InputTextMessageContent(InputMessageContent):
|
|||
https://core.telegram.org/bots/api#inputtextmessagecontent
|
||||
"""
|
||||
message_text: base.String = fields.Field()
|
||||
parse_mode: base.String = fields.Field()
|
||||
parse_mode: typing.Optional[base.String] = fields.Field()
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = fields.Field()
|
||||
disable_web_page_preview: base.Boolean = fields.Field()
|
||||
|
||||
def safe_get_parse_mode(self):
|
||||
|
|
@ -69,14 +154,22 @@ class InputTextMessageContent(InputMessageContent):
|
|||
except RuntimeError:
|
||||
pass
|
||||
|
||||
def __init__(self, message_text: typing.Optional[base.String] = None,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
disable_web_page_preview: typing.Optional[base.Boolean] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message_text: base.String,
|
||||
parse_mode: typing.Optional[base.String] = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
disable_web_page_preview: typing.Optional[base.Boolean] = None,
|
||||
):
|
||||
if parse_mode is None:
|
||||
parse_mode = self.safe_get_parse_mode()
|
||||
|
||||
super(InputTextMessageContent, self).__init__(message_text=message_text, parse_mode=parse_mode,
|
||||
disable_web_page_preview=disable_web_page_preview)
|
||||
super().__init__(
|
||||
message_text=message_text,
|
||||
parse_mode=parse_mode,
|
||||
caption_entities=caption_entities,
|
||||
disable_web_page_preview=disable_web_page_preview,
|
||||
)
|
||||
|
||||
|
||||
class InputVenueMessageContent(InputMessageContent):
|
||||
|
|
@ -92,12 +185,29 @@ class InputVenueMessageContent(InputMessageContent):
|
|||
longitude: base.Float = fields.Field()
|
||||
title: base.String = fields.Field()
|
||||
address: base.String = fields.Field()
|
||||
foursquare_id: base.String = fields.Field()
|
||||
foursquare_id: typing.Optional[base.String] = fields.Field()
|
||||
foursquare_type: typing.Optional[base.String] = fields.Field()
|
||||
google_place_id: typing.Optional[base.String] = fields.Field()
|
||||
google_place_type: typing.Optional[base.String] = fields.Field()
|
||||
|
||||
def __init__(self, latitude: typing.Optional[base.Float] = None,
|
||||
longitude: typing.Optional[base.Float] = None,
|
||||
title: typing.Optional[base.String] = None,
|
||||
address: typing.Optional[base.String] = None,
|
||||
foursquare_id: typing.Optional[base.String] = None):
|
||||
super(InputVenueMessageContent, self).__init__(latitude=latitude, longitude=longitude, title=title,
|
||||
address=address, foursquare_id=foursquare_id)
|
||||
def __init__(
|
||||
self,
|
||||
latitude: base.Float,
|
||||
longitude: base.Float,
|
||||
title: base.String,
|
||||
address: base.String,
|
||||
foursquare_id: typing.Optional[base.String] = None,
|
||||
foursquare_type: typing.Optional[base.String] = None,
|
||||
google_place_id: typing.Optional[base.String] = None,
|
||||
google_place_type: typing.Optional[base.String] = None,
|
||||
):
|
||||
super().__init__(
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
title=title,
|
||||
address=address,
|
||||
foursquare_id=foursquare_id,
|
||||
foursquare_type=foursquare_type,
|
||||
google_place_id=google_place_id,
|
||||
google_place_type=google_place_type,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import typing
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
|
||||
|
|
@ -10,3 +12,7 @@ class Location(base.TelegramObject):
|
|||
"""
|
||||
longitude: base.Float = fields.Field()
|
||||
latitude: base.Float = fields.Field()
|
||||
horizontal_accuracy: typing.Optional[base.Float] = fields.Field()
|
||||
live_period: typing.Optional[base.Integer] = fields.Field()
|
||||
heading: typing.Optional[base.Integer] = fields.Field()
|
||||
proximity_alert_radius: typing.Optional[base.Integer] = fields.Field()
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
11
aiogram/types/message_auto_delete_timer_changed.py
Normal file
11
aiogram/types/message_auto_delete_timer_changed.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
from . import base
|
||||
from . import fields
|
||||
|
||||
|
||||
class MessageAutoDeleteTimerChanged(base.TelegramObject):
|
||||
"""
|
||||
This object represents a service message about a change in auto-delete timer settings.
|
||||
|
||||
https://core.telegram.org/bots/api#messageautodeletetimerchanged
|
||||
"""
|
||||
message_auto_delete_time: base.Integer = fields.Field()
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
import sys
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
from .user import User
|
||||
from ..utils import helper, markdown
|
||||
from ..utils.deprecated import deprecated
|
||||
from . import base, fields
|
||||
from .user import User
|
||||
|
||||
|
||||
class MessageEntity(base.TelegramObject):
|
||||
|
|
@ -13,6 +12,7 @@ class MessageEntity(base.TelegramObject):
|
|||
|
||||
https://core.telegram.org/bots/api#messageentity
|
||||
"""
|
||||
|
||||
type: base.String = fields.Field()
|
||||
offset: base.Integer = fields.Field()
|
||||
length: base.Integer = fields.Field()
|
||||
|
|
@ -20,6 +20,26 @@ class MessageEntity(base.TelegramObject):
|
|||
user: User = fields.Field(base=User)
|
||||
language: base.String = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
type: base.String,
|
||||
offset: base.Integer,
|
||||
length: base.Integer,
|
||||
url: base.String = None,
|
||||
user: User = None,
|
||||
language: base.String = None,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(
|
||||
type=type,
|
||||
offset=offset,
|
||||
length=length,
|
||||
url=url,
|
||||
user=user,
|
||||
language=language,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def get_text(self, text):
|
||||
"""
|
||||
Get value of entity
|
||||
|
|
@ -27,18 +47,18 @@ class MessageEntity(base.TelegramObject):
|
|||
:param text: full text
|
||||
:return: part of text
|
||||
"""
|
||||
if sys.maxunicode == 0xffff:
|
||||
return text[self.offset:self.offset + self.length]
|
||||
if sys.maxunicode == 0xFFFF:
|
||||
return text[self.offset : self.offset + self.length]
|
||||
|
||||
if not isinstance(text, bytes):
|
||||
entity_text = text.encode('utf-16-le')
|
||||
else:
|
||||
entity_text = text
|
||||
entity_text = (
|
||||
text.encode("utf-16-le") if not isinstance(text, bytes) else text
|
||||
)
|
||||
entity_text = entity_text[self.offset * 2 : (self.offset + self.length) * 2]
|
||||
return entity_text.decode("utf-16-le")
|
||||
|
||||
entity_text = entity_text[self.offset * 2:(self.offset + self.length) * 2]
|
||||
return entity_text.decode('utf-16-le')
|
||||
|
||||
@deprecated("This method doesn't work with nested entities and will be removed in aiogram 3.0")
|
||||
@deprecated(
|
||||
"This method doesn't work with nested entities and will be removed in aiogram 3.0"
|
||||
)
|
||||
def parse(self, text, as_html=True):
|
||||
"""
|
||||
Get entity value with markup
|
||||
|
|
@ -95,6 +115,7 @@ class MessageEntityType(helper.Helper):
|
|||
:key: TEXT_LINK
|
||||
:key: TEXT_MENTION
|
||||
"""
|
||||
|
||||
mode = helper.HelperMode.snake_case
|
||||
|
||||
MENTION = helper.Item() # mention - @username
|
||||
|
|
|
|||
10
aiogram/types/message_id.py
Normal file
10
aiogram/types/message_id.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from . import base, fields
|
||||
|
||||
|
||||
class MessageId(base.TelegramObject):
|
||||
"""
|
||||
This object represents a unique message identifier.
|
||||
|
||||
https://core.telegram.org/bots/api#messageid
|
||||
"""
|
||||
message_id: base.String = fields.Field()
|
||||
15
aiogram/types/proximity_alert_triggered.py
Normal file
15
aiogram/types/proximity_alert_triggered.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from . import base
|
||||
from . import fields
|
||||
from .user import User
|
||||
|
||||
|
||||
class ProximityAlertTriggered(base.TelegramObject):
|
||||
"""
|
||||
This object represents the content of a service message, sent whenever a user in
|
||||
the chat triggers a proximity alert set by another user.
|
||||
|
||||
https://core.telegram.org/bots/api#proximityalerttriggered
|
||||
"""
|
||||
traveler: User = fields.Field()
|
||||
watcher: User = fields.Field()
|
||||
distance: base.Integer = fields.Field()
|
||||
|
|
@ -18,23 +18,32 @@ class KeyboardButtonPollType(base.TelegramObject):
|
|||
|
||||
class ReplyKeyboardMarkup(base.TelegramObject):
|
||||
"""
|
||||
This object represents a custom keyboard with reply options (see Introduction to bots for details and examples).
|
||||
This object represents a custom keyboard with reply options
|
||||
(see https://core.telegram.org/bots#keyboards to bots for details
|
||||
and examples).
|
||||
|
||||
https://core.telegram.org/bots/api#replykeyboardmarkup
|
||||
"""
|
||||
keyboard: 'typing.List[typing.List[KeyboardButton]]' = fields.ListOfLists(base='KeyboardButton', default=[])
|
||||
resize_keyboard: base.Boolean = fields.Field()
|
||||
one_time_keyboard: base.Boolean = fields.Field()
|
||||
input_field_placeholder: base.String = fields.Field()
|
||||
selective: base.Boolean = fields.Field()
|
||||
|
||||
def __init__(self, keyboard: 'typing.List[typing.List[KeyboardButton]]' = None,
|
||||
resize_keyboard: base.Boolean = None,
|
||||
one_time_keyboard: base.Boolean = None,
|
||||
input_field_placeholder: base.String = None,
|
||||
selective: base.Boolean = None,
|
||||
row_width: base.Integer = 3):
|
||||
super(ReplyKeyboardMarkup, self).__init__(keyboard=keyboard, resize_keyboard=resize_keyboard,
|
||||
one_time_keyboard=one_time_keyboard, selective=selective,
|
||||
conf={'row_width': row_width})
|
||||
super().__init__(
|
||||
keyboard=keyboard,
|
||||
resize_keyboard=resize_keyboard,
|
||||
one_time_keyboard=one_time_keyboard,
|
||||
input_field_placeholder=input_field_placeholder,
|
||||
selective=selective,
|
||||
conf={'row_width': row_width},
|
||||
)
|
||||
|
||||
@property
|
||||
def row_width(self):
|
||||
|
|
@ -58,7 +67,7 @@ class ReplyKeyboardMarkup(base.TelegramObject):
|
|||
if index % self.row_width == 0:
|
||||
self.keyboard.append(row)
|
||||
row = []
|
||||
if len(row) > 0:
|
||||
if row:
|
||||
self.keyboard.append(row)
|
||||
return self
|
||||
|
||||
|
|
@ -70,9 +79,7 @@ class ReplyKeyboardMarkup(base.TelegramObject):
|
|||
:return: self
|
||||
:rtype: :obj:`types.ReplyKeyboardMarkup`
|
||||
"""
|
||||
btn_array = []
|
||||
for button in args:
|
||||
btn_array.append(button)
|
||||
btn_array = [button for button in args]
|
||||
self.keyboard.append(btn_array)
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ from __future__ import annotations
|
|||
from . import base
|
||||
from . import fields
|
||||
from .callback_query import CallbackQuery
|
||||
from .chat_member_updated import ChatMemberUpdated
|
||||
from .chosen_inline_result import ChosenInlineResult
|
||||
from .inline_query import InlineQuery
|
||||
from .message import Message
|
||||
from .poll import Poll, PollAnswer
|
||||
from .pre_checkout_query import PreCheckoutQuery
|
||||
from .shipping_query import ShippingQuery
|
||||
from ..utils import helper
|
||||
from ..utils import helper, deprecated
|
||||
|
||||
|
||||
class Update(base.TelegramObject):
|
||||
|
|
@ -31,6 +32,8 @@ class Update(base.TelegramObject):
|
|||
pre_checkout_query: PreCheckoutQuery = fields.Field(base=PreCheckoutQuery)
|
||||
poll: Poll = fields.Field(base=Poll)
|
||||
poll_answer: PollAnswer = fields.Field(base=PollAnswer)
|
||||
my_chat_member: ChatMemberUpdated = fields.Field(base=ChatMemberUpdated)
|
||||
chat_member: ChatMemberUpdated = fields.Field(base=ChatMemberUpdated)
|
||||
|
||||
def __hash__(self):
|
||||
return self.update_id
|
||||
|
|
@ -55,9 +58,21 @@ class AllowedUpdates(helper.Helper):
|
|||
CHANNEL_POST = helper.ListItem() # channel_post
|
||||
EDITED_CHANNEL_POST = helper.ListItem() # edited_channel_post
|
||||
INLINE_QUERY = helper.ListItem() # inline_query
|
||||
CHOSEN_INLINE_QUERY = helper.ListItem() # chosen_inline_result
|
||||
CHOSEN_INLINE_RESULT = helper.ListItem() # chosen_inline_result
|
||||
CALLBACK_QUERY = helper.ListItem() # callback_query
|
||||
SHIPPING_QUERY = helper.ListItem() # shipping_query
|
||||
PRE_CHECKOUT_QUERY = helper.ListItem() # pre_checkout_query
|
||||
POLL = helper.ListItem() # poll
|
||||
POLL_ANSWER = helper.ListItem() # poll_answer
|
||||
MY_CHAT_MEMBER = helper.ListItem() # my_chat_member
|
||||
CHAT_MEMBER = helper.ListItem() # chat_member
|
||||
|
||||
CHOSEN_INLINE_QUERY = deprecated.DeprecatedReadOnlyClassVar(
|
||||
"`CHOSEN_INLINE_QUERY` is a deprecated value for allowed update. "
|
||||
"Use `CHOSEN_INLINE_RESULT`",
|
||||
new_value_getter=lambda cls: cls.CHOSEN_INLINE_RESULT,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -14,3 +14,5 @@ class Venue(base.TelegramObject):
|
|||
address: base.String = fields.Field()
|
||||
foursquare_id: base.String = fields.Field()
|
||||
foursquare_type: base.String = fields.Field()
|
||||
google_place_id: base.String = fields.Field()
|
||||
google_place_type: base.String = fields.Field()
|
||||
|
|
|
|||
|
|
@ -16,5 +16,6 @@ class Video(base.TelegramObject, mixins.Downloadable):
|
|||
height: base.Integer = fields.Field()
|
||||
duration: base.Integer = fields.Field()
|
||||
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
||||
file_name: base.String = fields.Field()
|
||||
mime_type: base.String = fields.Field()
|
||||
file_size: base.Integer = fields.Field()
|
||||
|
|
|
|||
13
aiogram/types/voice_chat_ended.py
Normal file
13
aiogram/types/voice_chat_ended.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from . import base
|
||||
from . import fields
|
||||
from . import mixins
|
||||
|
||||
|
||||
class VoiceChatEnded(base.TelegramObject, mixins.Downloadable):
|
||||
"""
|
||||
This object represents a service message about a voice chat ended in the chat.
|
||||
|
||||
https://core.telegram.org/bots/api#voicechatended
|
||||
"""
|
||||
|
||||
duration: base.Integer = fields.Field()
|
||||
16
aiogram/types/voice_chat_participants_invited.py
Normal file
16
aiogram/types/voice_chat_participants_invited.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import typing
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
from . import mixins
|
||||
from .user import User
|
||||
|
||||
|
||||
class VoiceChatParticipantsInvited(base.TelegramObject, mixins.Downloadable):
|
||||
"""
|
||||
This object represents a service message about new members invited to a voice chat.
|
||||
|
||||
https://core.telegram.org/bots/api#voicechatparticipantsinvited
|
||||
"""
|
||||
|
||||
users: typing.List[User] = fields.ListField(base=User)
|
||||
15
aiogram/types/voice_chat_scheduled.py
Normal file
15
aiogram/types/voice_chat_scheduled.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from datetime import datetime
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
from .user import User
|
||||
|
||||
|
||||
class VoiceChatScheduled(base.TelegramObject):
|
||||
"""
|
||||
This object represents a service message about a voice chat scheduled in the chat.
|
||||
|
||||
https://core.telegram.org/bots/api#voicechatscheduled
|
||||
"""
|
||||
|
||||
start_date: datetime = fields.DateTimeField()
|
||||
12
aiogram/types/voice_chat_started.py
Normal file
12
aiogram/types/voice_chat_started.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from . import base
|
||||
from . import mixins
|
||||
|
||||
|
||||
class VoiceChatStarted(base.TelegramObject, mixins.Downloadable):
|
||||
"""
|
||||
This object represents a service message about a voice chat started in the chat.
|
||||
Currently holds no information.
|
||||
|
||||
https://core.telegram.org/bots/api#voicechatstarted
|
||||
"""
|
||||
pass
|
||||
|
|
@ -13,6 +13,7 @@ class WebhookInfo(base.TelegramObject):
|
|||
url: base.String = fields.Field()
|
||||
has_custom_certificate: base.Boolean = fields.Field()
|
||||
pending_update_count: base.Integer = fields.Field()
|
||||
ip_address: base.String = fields.Field()
|
||||
last_error_date: base.Integer = fields.Field()
|
||||
last_error_message: base.String = fields.Field()
|
||||
max_connections: base.Integer = fields.Field()
|
||||
|
|
|
|||
|
|
@ -130,7 +130,6 @@ class CallbackDataFilter(Filter):
|
|||
if isinstance(value, (list, tuple, set, frozenset)):
|
||||
if data.get(key) not in value:
|
||||
return False
|
||||
else:
|
||||
if data.get(key) != value:
|
||||
return False
|
||||
elif data.get(key) != value:
|
||||
return False
|
||||
return {'callback_data': data}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"""
|
||||
Deep linking
|
||||
|
||||
Telegram bots have a deep linking mechanism, that allows for passing additional
|
||||
parameters to the bot on startup. It could be a command that launches the bot — or
|
||||
an auth token to connect the user's Telegram account to their account on some
|
||||
external service.
|
||||
Telegram bots have a deep linking mechanism, that allows for passing
|
||||
additional parameters to the bot on startup. It could be a command that
|
||||
launches the bot — or an auth token to connect the user's Telegram
|
||||
account to their account on some external service.
|
||||
|
||||
You can read detailed description in the source:
|
||||
https://core.telegram.org/bots#deep-linking
|
||||
|
|
@ -16,86 +16,123 @@ Basic link example:
|
|||
.. code-block:: python
|
||||
|
||||
from aiogram.utils.deep_linking import get_start_link
|
||||
link = await get_start_link('foo') # result: 'https://t.me/MyBot?start=foo'
|
||||
link = await get_start_link('foo')
|
||||
|
||||
# result: 'https://t.me/MyBot?start=foo'
|
||||
|
||||
Encoded link example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from aiogram.utils.deep_linking import get_start_link, decode_payload
|
||||
link = await get_start_link('foo', encode=True) # result: 'https://t.me/MyBot?start=Zm9v'
|
||||
# and decode it back:
|
||||
payload = decode_payload('Zm9v') # result: 'foo'
|
||||
from aiogram.utils.deep_linking import get_start_link
|
||||
|
||||
link = await get_start_link('foo', encode=True)
|
||||
# result: 'https://t.me/MyBot?start=Zm9v'
|
||||
|
||||
Decode it back example:
|
||||
.. code-block:: python
|
||||
|
||||
from aiogram.utils.deep_linking import decode_payload
|
||||
from aiogram.types import Message
|
||||
|
||||
@dp.message_handler(commands=["start"])
|
||||
async def handler(message: Message):
|
||||
args = message.get_args()
|
||||
payload = decode_payload(args)
|
||||
await message.answer(f"Your payload: {payload}")
|
||||
|
||||
"""
|
||||
import re
|
||||
from base64 import urlsafe_b64decode, urlsafe_b64encode
|
||||
|
||||
from ..bot import Bot
|
||||
|
||||
BAD_PATTERN = re.compile(r"[^_A-z0-9-]")
|
||||
|
||||
|
||||
async def get_start_link(payload: str, encode=False) -> str:
|
||||
"""
|
||||
Use this method to handy get 'start' deep link with your payload.
|
||||
If you need to encode payload or pass special characters - set encode as True
|
||||
Get 'start' deep link with your payload.
|
||||
|
||||
If you need to encode payload or pass special characters -
|
||||
set encode as True
|
||||
|
||||
:param payload: args passed with /start
|
||||
:param encode: encode payload with base64url
|
||||
:return: link
|
||||
"""
|
||||
return await _create_link('start', payload, encode)
|
||||
return await _create_link(
|
||||
link_type="start",
|
||||
payload=payload,
|
||||
encode=encode,
|
||||
)
|
||||
|
||||
|
||||
async def get_startgroup_link(payload: str, encode=False) -> str:
|
||||
"""
|
||||
Use this method to handy get 'startgroup' deep link with your payload.
|
||||
If you need to encode payload or pass special characters - set encode as True
|
||||
Get 'startgroup' deep link with your payload.
|
||||
|
||||
If you need to encode payload or pass special characters -
|
||||
set encode as True
|
||||
|
||||
:param payload: args passed with /start
|
||||
:param encode: encode payload with base64url
|
||||
:return: link
|
||||
"""
|
||||
return await _create_link('startgroup', payload, encode)
|
||||
return await _create_link(
|
||||
link_type="startgroup",
|
||||
payload=payload,
|
||||
encode=encode,
|
||||
)
|
||||
|
||||
|
||||
async def _create_link(link_type, payload: str, encode=False):
|
||||
"""
|
||||
Create deep link.
|
||||
|
||||
:param link_type: `start` or `startgroup`
|
||||
:param payload: any string-convertible data
|
||||
:param encode: pass True to encode the payload
|
||||
:return: deeplink
|
||||
"""
|
||||
bot = await _get_bot_user()
|
||||
payload = filter_payload(payload)
|
||||
if encode:
|
||||
payload = encode_payload(payload)
|
||||
return f'https://t.me/{bot.username}?{link_type}={payload}'
|
||||
|
||||
|
||||
def encode_payload(payload: str) -> str:
|
||||
""" Encode payload with URL-safe base64url. """
|
||||
from base64 import urlsafe_b64encode
|
||||
result: bytes = urlsafe_b64encode(payload.encode())
|
||||
return result.decode()
|
||||
|
||||
|
||||
def decode_payload(payload: str) -> str:
|
||||
""" Decode payload with URL-safe base64url. """
|
||||
from base64 import urlsafe_b64decode
|
||||
result: bytes = urlsafe_b64decode(payload + '=' * (4 - len(payload) % 4))
|
||||
return result.decode()
|
||||
|
||||
|
||||
def filter_payload(payload: str) -> str:
|
||||
""" Convert payload to text and search for not allowed symbols. """
|
||||
import re
|
||||
|
||||
# convert to string
|
||||
if not isinstance(payload, str):
|
||||
payload = str(payload)
|
||||
|
||||
# search for not allowed characters
|
||||
if re.search(r'[^_A-z0-9-]', payload):
|
||||
message = ('Wrong payload! Only A-Z, a-z, 0-9, _ and - are allowed. '
|
||||
'We recommend to encode parameters with binary and other '
|
||||
'types of content.')
|
||||
if encode:
|
||||
payload = encode_payload(payload)
|
||||
|
||||
if re.search(BAD_PATTERN, payload):
|
||||
message = (
|
||||
"Wrong payload! Only A-Z, a-z, 0-9, _ and - are allowed. "
|
||||
"Pass `encode=True` or encode payload manually."
|
||||
)
|
||||
raise ValueError(message)
|
||||
|
||||
return payload
|
||||
if len(payload) > 64:
|
||||
message = "Payload must be up to 64 characters long."
|
||||
raise ValueError(message)
|
||||
|
||||
return f"https://t.me/{bot.username}?{link_type}={payload}"
|
||||
|
||||
|
||||
def encode_payload(payload: str) -> str:
|
||||
"""Encode payload with URL-safe base64url."""
|
||||
payload = str(payload)
|
||||
bytes_payload: bytes = urlsafe_b64encode(payload.encode())
|
||||
str_payload = bytes_payload.decode()
|
||||
return str_payload.replace("=", "")
|
||||
|
||||
|
||||
def decode_payload(payload: str) -> str:
|
||||
"""Decode payload with URL-safe base64url."""
|
||||
payload += "=" * (4 - len(payload) % 4)
|
||||
result: bytes = urlsafe_b64decode(payload)
|
||||
return result.decode()
|
||||
|
||||
|
||||
async def _get_bot_user():
|
||||
""" Get current user of bot. """
|
||||
from ..bot import Bot
|
||||
"""Get current user of bot."""
|
||||
bot = Bot.get_current()
|
||||
return await bot.me
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import asyncio
|
|||
import inspect
|
||||
import warnings
|
||||
import functools
|
||||
from typing import Callable
|
||||
from typing import Callable, Generic, TypeVar, Type, Optional
|
||||
|
||||
|
||||
def deprecated(reason, stacklevel=2) -> Callable:
|
||||
|
|
@ -129,3 +129,36 @@ def renamed_argument(old_name: str, new_name: str, until_version: str, stackleve
|
|||
return wrapped
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
_VT = TypeVar("_VT")
|
||||
_OwnerCls = TypeVar("_OwnerCls")
|
||||
|
||||
|
||||
class DeprecatedReadOnlyClassVar(Generic[_OwnerCls, _VT]):
|
||||
"""
|
||||
DeprecatedReadOnlyClassVar[Owner, ValueType]
|
||||
|
||||
:param warning_message: Warning message when getter gets called
|
||||
:param new_value_getter: Any callable with (owner_class: Type[Owner]) -> ValueType
|
||||
signature that will be executed
|
||||
|
||||
Usage example:
|
||||
|
||||
>>> class MyClass:
|
||||
... some_attribute: DeprecatedReadOnlyClassVar[MyClass, int] = \
|
||||
... DeprecatedReadOnlyClassVar(
|
||||
... "Warning message.", lambda owner: 15)
|
||||
...
|
||||
>>> MyClass.some_attribute # does warning.warn with `Warning message` and returns 15 in the current case
|
||||
"""
|
||||
|
||||
__slots__ = "_new_value_getter", "_warning_message"
|
||||
|
||||
def __init__(self, warning_message: str, new_value_getter: Callable[[_OwnerCls], _VT]):
|
||||
self._warning_message = warning_message
|
||||
self._new_value_getter = new_value_getter
|
||||
|
||||
def __get__(self, instance: Optional[_OwnerCls], owner: Type[_OwnerCls]):
|
||||
warn_deprecated(self._warning_message, stacklevel=3)
|
||||
return self._new_value_getter(owner)
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@
|
|||
- MessageError
|
||||
- MessageNotModified
|
||||
- MessageToForwardNotFound
|
||||
- MessageIdInvalid
|
||||
- MessageToDeleteNotFound
|
||||
- MessageToPinNotFound
|
||||
- MessageIdentifierNotSpecified
|
||||
- MessageTextIsEmpty
|
||||
- MessageCantBeEdited
|
||||
- MessageCantBeDeleted
|
||||
- MessageCantBeForwarded
|
||||
- MessageToEditNotFound
|
||||
- MessageToReplyNotFound
|
||||
- ToMuchMessages
|
||||
|
|
@ -37,6 +40,7 @@
|
|||
- URLHostIsEmpty
|
||||
- StartParamInvalid
|
||||
- ButtonDataInvalid
|
||||
- FileIsTooBig
|
||||
- WrongFileIdentifier
|
||||
- GroupDeactivated
|
||||
- BadWebhook
|
||||
|
|
@ -175,6 +179,11 @@ class MessageToForwardNotFound(MessageError):
|
|||
match = 'message to forward not found'
|
||||
|
||||
|
||||
class MessageIdInvalid(MessageError):
|
||||
text = 'Invalid message id'
|
||||
match = 'message_id_invalid'
|
||||
|
||||
|
||||
class MessageToDeleteNotFound(MessageError):
|
||||
"""
|
||||
Will be raised when you try to delete very old or deleted or unknown message.
|
||||
|
|
@ -182,11 +191,18 @@ class MessageToDeleteNotFound(MessageError):
|
|||
match = 'message to delete not found'
|
||||
|
||||
|
||||
class MessageToPinNotFound(MessageError):
|
||||
"""
|
||||
Will be raised when you try to pin deleted or unknown message.
|
||||
"""
|
||||
match = 'message to pin 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 = 'Reply message not found'
|
||||
|
||||
|
||||
class MessageIdentifierNotSpecified(MessageError):
|
||||
|
|
@ -205,6 +221,10 @@ class MessageCantBeDeleted(MessageError):
|
|||
match = 'message can\'t be deleted'
|
||||
|
||||
|
||||
class MessageCantBeForwarded(MessageError):
|
||||
match = 'message can\'t be forwarded'
|
||||
|
||||
|
||||
class MessageToEditNotFound(MessageError):
|
||||
match = 'message to edit not found'
|
||||
|
||||
|
|
@ -339,12 +359,16 @@ class ButtonDataInvalid(BadRequest):
|
|||
text = 'Button data invalid'
|
||||
|
||||
|
||||
class FileIsTooBig(BadRequest):
|
||||
match = 'File is too big'
|
||||
|
||||
|
||||
class WrongFileIdentifier(BadRequest):
|
||||
match = 'wrong file identifier/HTTP URL specified'
|
||||
|
||||
|
||||
class GroupDeactivated(BadRequest):
|
||||
match = 'group is deactivated'
|
||||
match = 'Group chat was deactivated'
|
||||
|
||||
|
||||
class PhotoAsInputFileRequired(BadRequest):
|
||||
|
|
@ -470,6 +494,20 @@ class MethodIsNotAvailable(BadRequest):
|
|||
match = "Method is available only for supergroups"
|
||||
|
||||
|
||||
class CantRestrictChatOwner(BadRequest):
|
||||
"""
|
||||
Raises when bot restricts the chat owner
|
||||
"""
|
||||
match = 'Can\'t remove chat owner'
|
||||
|
||||
|
||||
class UserIsAnAdministratorOfTheChat(BadRequest):
|
||||
"""
|
||||
Raises when bot restricts the chat admin
|
||||
"""
|
||||
match = 'User is an administrator of the chat'
|
||||
|
||||
|
||||
class NotFound(TelegramAPIError, _MatchErrorMixin):
|
||||
__group = True
|
||||
|
||||
|
|
@ -497,7 +535,7 @@ class Unauthorized(TelegramAPIError, _MatchErrorMixin):
|
|||
|
||||
|
||||
class BotKicked(Unauthorized):
|
||||
match = 'bot was kicked from a chat'
|
||||
match = 'bot was kicked from'
|
||||
|
||||
|
||||
class BotBlocked(Unauthorized):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import asyncio
|
|||
import datetime
|
||||
import functools
|
||||
import secrets
|
||||
from typing import Callable, Union, Optional, Any
|
||||
from typing import Callable, Union, Optional, Any, List
|
||||
from warnings import warn
|
||||
|
||||
from aiohttp import web
|
||||
|
|
@ -23,7 +23,8 @@ def _setup_callbacks(executor: 'Executor', on_startup=None, on_shutdown=None):
|
|||
|
||||
|
||||
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):
|
||||
on_startup=None, on_shutdown=None, timeout=20, relax=0.1, fast=True,
|
||||
allowed_updates: Optional[List[str]] = None):
|
||||
"""
|
||||
Start bot in long-polling mode
|
||||
|
||||
|
|
@ -34,11 +35,20 @@ def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=Tr
|
|||
:param on_startup:
|
||||
:param on_shutdown:
|
||||
:param timeout:
|
||||
:param relax:
|
||||
:param fast:
|
||||
:param allowed_updates:
|
||||
"""
|
||||
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, relax=relax, fast=fast)
|
||||
executor.start_polling(
|
||||
reset_webhook=reset_webhook,
|
||||
timeout=timeout,
|
||||
relax=relax,
|
||||
fast=fast,
|
||||
allowed_updates=allowed_updates
|
||||
)
|
||||
|
||||
|
||||
def set_webhook(dispatcher: Dispatcher, webhook_path: str, *, loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||
|
|
@ -123,13 +133,13 @@ class Executor:
|
|||
"""
|
||||
|
||||
def __init__(self, dispatcher, skip_updates=None, check_ip=False, retry_after=None, loop=None):
|
||||
if loop is None:
|
||||
loop = dispatcher.loop
|
||||
if loop is not None:
|
||||
self._loop = loop
|
||||
|
||||
self.dispatcher = dispatcher
|
||||
self.skip_updates = skip_updates
|
||||
self.check_ip = check_ip
|
||||
self.retry_after = retry_after
|
||||
self.loop = loop
|
||||
|
||||
self._identity = secrets.token_urlsafe(16)
|
||||
self._web_app = None
|
||||
|
|
@ -145,13 +155,17 @@ class Executor:
|
|||
Bot.set_current(dispatcher.bot)
|
||||
Dispatcher.set_current(dispatcher)
|
||||
|
||||
@property
|
||||
def loop(self) -> asyncio.AbstractEventLoop:
|
||||
return getattr(self, "_loop", asyncio.get_event_loop())
|
||||
|
||||
@property
|
||||
def frozen(self):
|
||||
return self._freeze
|
||||
|
||||
def set_web_app(self, application: web.Application):
|
||||
"""
|
||||
Change instance of aiohttp.web.Applicaton
|
||||
Change instance of aiohttp.web.Application
|
||||
|
||||
:param application:
|
||||
"""
|
||||
|
|
@ -291,7 +305,8 @@ class Executor:
|
|||
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, relax=0.1, fast=True):
|
||||
def start_polling(self, reset_webhook=None, timeout=20, relax=0.1, fast=True,
|
||||
allowed_updates: Optional[List[str]] = None):
|
||||
"""
|
||||
Start bot in long-polling mode
|
||||
|
||||
|
|
@ -304,7 +319,7 @@ class Executor:
|
|||
try:
|
||||
loop.run_until_complete(self._startup_polling())
|
||||
loop.create_task(self.dispatcher.start_polling(reset_webhook=reset_webhook, timeout=timeout,
|
||||
relax=relax, fast=fast))
|
||||
relax=relax, fast=fast, allowed_updates=allowed_updates))
|
||||
loop.run_forever()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
# loop.stop()
|
||||
|
|
@ -350,7 +365,7 @@ class Executor:
|
|||
self.dispatcher.stop_polling()
|
||||
await self.dispatcher.storage.close()
|
||||
await self.dispatcher.storage.wait_closed()
|
||||
await self.dispatcher.bot.close()
|
||||
await self.dispatcher.bot.session.close()
|
||||
|
||||
async def _startup_polling(self):
|
||||
await self._welcome()
|
||||
|
|
|
|||
|
|
@ -103,10 +103,7 @@ class HelperMode(Helper):
|
|||
if symbol == '_' and pos > 0:
|
||||
need_upper = True
|
||||
else:
|
||||
if need_upper:
|
||||
result += symbol.upper()
|
||||
else:
|
||||
result += symbol.lower()
|
||||
result += symbol.upper() if need_upper else symbol.lower()
|
||||
need_upper = False
|
||||
if first_upper:
|
||||
result = result[0].upper() + result[1:]
|
||||
|
|
@ -201,10 +198,14 @@ 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)
|
||||
props_keys = [
|
||||
prop_name
|
||||
for prop_name in (
|
||||
name
|
||||
for name, prop in namespace.items()
|
||||
if isinstance(prop, (Item, ListItem))
|
||||
)
|
||||
]
|
||||
|
||||
setattr(cls, PROPS_KEYS_ATTR_NAME, props_keys)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@ def split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]:
|
|||
return [text[i:i + length] for i in range(0, len(text), length)]
|
||||
|
||||
|
||||
def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]:
|
||||
def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH, split_separator: str = ' ') -> typing.List[str]:
|
||||
"""
|
||||
Split long text
|
||||
|
||||
:param text:
|
||||
:param length:
|
||||
:param split_separator
|
||||
:return:
|
||||
"""
|
||||
# TODO: More informative description
|
||||
|
|
@ -30,7 +31,7 @@ def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[
|
|||
while temp_text:
|
||||
if len(temp_text) > length:
|
||||
try:
|
||||
split_pos = temp_text[:length].rindex(' ')
|
||||
split_pos = temp_text[:length].rindex(split_separator)
|
||||
except ValueError:
|
||||
split_pos = length
|
||||
if split_pos < length // 4 * 3:
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ if TYPE_CHECKING: # pragma: no cover
|
|||
from aiogram.types import MessageEntity
|
||||
|
||||
__all__ = (
|
||||
"TextDecoration",
|
||||
"HtmlDecoration",
|
||||
"MarkdownDecoration",
|
||||
"html_decoration",
|
||||
"markdown_decoration",
|
||||
'HtmlDecoration',
|
||||
'MarkdownDecoration',
|
||||
'TextDecoration',
|
||||
'html_decoration',
|
||||
'markdown_decoration',
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -55,16 +55,15 @@ class TextDecoration(ABC):
|
|||
:param entities: Array of MessageEntities
|
||||
:return:
|
||||
"""
|
||||
result = "".join(
|
||||
return "".join(
|
||||
self._unparse_entities(
|
||||
text, sorted(entities, key=lambda item: item.offset) if entities else []
|
||||
self._add_surrogates(text), sorted(entities, key=lambda item: item.offset) if entities else []
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
def _unparse_entities(
|
||||
self,
|
||||
text: str,
|
||||
text: bytes,
|
||||
entities: List[MessageEntity],
|
||||
offset: Optional[int] = None,
|
||||
length: Optional[int] = None,
|
||||
|
|
@ -74,15 +73,15 @@ class TextDecoration(ABC):
|
|||
length = length or len(text)
|
||||
|
||||
for index, entity in enumerate(entities):
|
||||
if entity.offset < offset:
|
||||
if entity.offset * 2 < offset:
|
||||
continue
|
||||
if entity.offset > offset:
|
||||
yield self.quote(text[offset : entity.offset])
|
||||
start = entity.offset
|
||||
offset = entity.offset + entity.length
|
||||
if entity.offset * 2 > offset:
|
||||
yield self.quote(self._remove_surrogates(text[offset : entity.offset * 2]))
|
||||
start = entity.offset * 2
|
||||
offset = entity.offset * 2 + entity.length * 2
|
||||
|
||||
sub_entities = list(
|
||||
filter(lambda e: e.offset < (offset or 0), entities[index + 1 :])
|
||||
filter(lambda e: e.offset * 2 < (offset or 0), entities[index + 1 :])
|
||||
)
|
||||
yield self.apply_entity(
|
||||
entity,
|
||||
|
|
@ -94,7 +93,15 @@ class TextDecoration(ABC):
|
|||
)
|
||||
|
||||
if offset < length:
|
||||
yield self.quote(text[offset:length])
|
||||
yield self.quote(self._remove_surrogates(text[offset:length]))
|
||||
|
||||
@staticmethod
|
||||
def _add_surrogates(text: str):
|
||||
return text.encode('utf-16-le')
|
||||
|
||||
@staticmethod
|
||||
def _remove_surrogates(text: bytes):
|
||||
return text.decode('utf-16-le')
|
||||
|
||||
@abstractmethod
|
||||
def link(self, value: str, link: str) -> str: # pragma: no cover
|
||||
|
|
@ -159,7 +166,7 @@ class HtmlDecoration(TextDecoration):
|
|||
return f"<s>{value}</s>"
|
||||
|
||||
def quote(self, value: str) -> str:
|
||||
return html.escape(value)
|
||||
return html.escape(value, quote=False)
|
||||
|
||||
|
||||
class MarkdownDecoration(TextDecoration):
|
||||
|
|
@ -172,19 +179,19 @@ class MarkdownDecoration(TextDecoration):
|
|||
return f"*{value}*"
|
||||
|
||||
def italic(self, value: str) -> str:
|
||||
return f"_{value}_\r"
|
||||
return f"_\r{value}_\r"
|
||||
|
||||
def code(self, value: str) -> str:
|
||||
return f"`{value}`"
|
||||
|
||||
def pre(self, value: str) -> str:
|
||||
return f"```{value}```"
|
||||
return f"```\n{value}\n```"
|
||||
|
||||
def pre_language(self, value: str, language: str) -> str:
|
||||
return f"```{language}\n{value}\n```"
|
||||
|
||||
def underline(self, value: str) -> str:
|
||||
return f"__{value}__"
|
||||
return f"__\r{value}__\r"
|
||||
|
||||
def strikethrough(self, value: str) -> str:
|
||||
return f"~{value}~"
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@
|
|||
ujson>=1.35
|
||||
python-rapidjson>=0.7.0
|
||||
emoji>=0.5.2
|
||||
pytest>=5.4
|
||||
pytest>=6.2.1
|
||||
pytest-asyncio>=0.10.0
|
||||
tox>=3.9.0
|
||||
aresponses>=1.1.1
|
||||
uvloop>=0.12.2
|
||||
aioredis>=1.2.0
|
||||
wheel>=0.31.1
|
||||
sphinx>=2.0.1
|
||||
|
|
@ -16,3 +15,5 @@ sphinxcontrib-programoutput>=0.14
|
|||
aiohttp-socks>=0.3.4
|
||||
rethinkdb>=2.4.1
|
||||
coverage==4.5.3
|
||||
motor>=2.2.0
|
||||
pytest-lazy-fixture==0.6.*
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Filter factory greatly simplifies the reuse of filters when registering handlers
|
|||
Filters factory
|
||||
===============
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.factory.FiltersFactory
|
||||
.. autoclass:: aiogram.dispatcher.filters.FiltersFactory
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
@ -21,28 +21,28 @@ Builtin filters
|
|||
Command
|
||||
-------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.Command
|
||||
.. autoclass:: aiogram.dispatcher.filters.Command
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
CommandStart
|
||||
------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.CommandStart
|
||||
.. autoclass:: aiogram.dispatcher.filters.CommandStart
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
CommandHelp
|
||||
-----------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.CommandHelp
|
||||
.. autoclass:: aiogram.dispatcher.filters.CommandHelp
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
CommandSettings
|
||||
---------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.CommandSettings
|
||||
.. autoclass:: aiogram.dispatcher.filters.CommandSettings
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ CommandSettings
|
|||
CommandPrivacy
|
||||
--------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.CommandPrivacy
|
||||
.. autoclass:: aiogram.dispatcher.filters.CommandPrivacy
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ CommandPrivacy
|
|||
Text
|
||||
----
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.Text
|
||||
.. autoclass:: aiogram.dispatcher.filters.Text
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ Text
|
|||
HashTag
|
||||
-------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.HashTag
|
||||
.. autoclass:: aiogram.dispatcher.filters.HashTag
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ HashTag
|
|||
Regexp
|
||||
------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.Regexp
|
||||
.. autoclass:: aiogram.dispatcher.filters.Regexp
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ Regexp
|
|||
RegexpCommandsFilter
|
||||
--------------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.RegexpCommandsFilter
|
||||
.. autoclass:: aiogram.dispatcher.filters.RegexpCommandsFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
@ -90,21 +90,21 @@ RegexpCommandsFilter
|
|||
ContentTypeFilter
|
||||
-----------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.ContentTypeFilter
|
||||
.. autoclass:: aiogram.dispatcher.filters.ContentTypeFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
IsSenderContact
|
||||
---------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.IsSenderContact
|
||||
.. autoclass:: aiogram.dispatcher.filters.IsSenderContact
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
StateFilter
|
||||
-----------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.StateFilter
|
||||
.. autoclass:: aiogram.dispatcher.filters.StateFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
@ -112,13 +112,13 @@ StateFilter
|
|||
ExceptionsFilter
|
||||
----------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.ExceptionsFilter
|
||||
.. autoclass:: aiogram.dispatcher.filters.ExceptionsFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
IDFilter
|
||||
----------------
|
||||
--------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.IDFilter
|
||||
:members:
|
||||
|
|
@ -126,9 +126,9 @@ IDFilter
|
|||
|
||||
|
||||
AdminFilter
|
||||
----------------
|
||||
-----------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.builtin.AdminFilter
|
||||
.. autoclass:: aiogram.dispatcher.filters.AdminFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
@ -136,23 +136,31 @@ AdminFilter
|
|||
IsReplyFilter
|
||||
-------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.filters.IsReplyFilter
|
||||
.. autoclass:: aiogram.dispatcher.filters.IsReplyFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
ForwardedMessageFilter
|
||||
-------------
|
||||
----------------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.filters.ForwardedMessageFilter
|
||||
.. autoclass:: aiogram.dispatcher.filters.ForwardedMessageFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
ChatTypeFilter
|
||||
--------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.ChatTypeFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
MediaGroupFilter
|
||||
-------------
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.filters.filters.ChatTypeFilter
|
||||
.. autoclass:: aiogram.dispatcher.filters.MediaGroupFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
@ -170,19 +178,19 @@ Own filter can be:
|
|||
|
||||
AbstractFilter
|
||||
--------------
|
||||
.. autoclass:: aiogram.dispatcher.filters.filters.AbstractFilter
|
||||
.. autoclass:: aiogram.dispatcher.filters.AbstractFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Filter
|
||||
------
|
||||
.. autoclass:: aiogram.dispatcher.filters.filters.Filter
|
||||
.. autoclass:: aiogram.dispatcher.filters.Filter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
BoundFilter
|
||||
-----------
|
||||
.. autoclass:: aiogram.dispatcher.filters.filters.BoundFilter
|
||||
.. autoclass:: aiogram.dispatcher.filters.BoundFilter
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
|
|||
|
|
@ -12,15 +12,29 @@ Coming soon...
|
|||
|
||||
Memory storage
|
||||
~~~~~~~~~~~~~~
|
||||
Coming soon...
|
||||
|
||||
.. autoclass:: aiogram.contrib.fsm_storage.memory.MemoryStorage
|
||||
:show-inheritance:
|
||||
|
||||
Redis storage
|
||||
~~~~~~~~~~~~~
|
||||
Coming soon...
|
||||
|
||||
.. autoclass:: aiogram.contrib.fsm_storage.redis.RedisStorage
|
||||
:show-inheritance:
|
||||
|
||||
Mongo storage
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: aiogram.contrib.fsm_storage.mongo.MongoStorage
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Rethink DB storage
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
Coming soon...
|
||||
|
||||
.. autoclass:: aiogram.contrib.fsm_storage.rethinkdb.RethinkDBStorage
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Making own storage's
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -1,28 +1,8 @@
|
|||
.. Autogenerated file at 2018-10-28 19:31:48.335963
|
||||
|
||||
=========================
|
||||
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 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 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/advanced_executor_example.py
|
||||
:caption: advanced_executor_example.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 25-
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.593501
|
||||
|
||||
=================
|
||||
Broadcast example
|
||||
=================
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.558059
|
||||
|
||||
===================
|
||||
Check user language
|
||||
===================
|
||||
|
|
@ -10,4 +8,3 @@ Babel is required.
|
|||
:caption: check_user_language.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 5-
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
========
|
||||
Echo bot
|
||||
========
|
||||
|
||||
Very simple example of the bot which will sent text of the received messages to the sender
|
||||
|
||||
.. literalinclude:: ../../../examples/echo_bot.py
|
||||
:caption: echo_bot.py
|
||||
:language: python
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.595032
|
||||
|
||||
============================
|
||||
Finite state machine example
|
||||
============================
|
||||
|
|
|
|||
|
|
@ -1,28 +1,8 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.591007
|
||||
|
||||
============
|
||||
I18n example
|
||||
============
|
||||
|
||||
Internalize your bot
|
||||
Step 1: extract texts
|
||||
# pybabel extract i18n_example.py -o locales/mybot.pot
|
||||
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
|
||||
Step 4: compile translations
|
||||
# pybabel compile -d locales -D mybot
|
||||
Step 5: When you change the code of your bot you need to update po & mo files.
|
||||
Step 5.1: regenerate pot file:
|
||||
command from step 1
|
||||
Step 5.2: update po files
|
||||
# pybabel update -d locales -D mybot -i locales/mybot.pot
|
||||
Step 5.3: update your translations
|
||||
Step 5.4: compile mo files
|
||||
command from step 4
|
||||
|
||||
.. literalinclude:: ../../../examples/i18n_example.py
|
||||
:caption: i18n_example.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 22-
|
||||
|
|
|
|||
|
|
@ -19,3 +19,4 @@ Examples
|
|||
payments
|
||||
broadcast_example
|
||||
media_group
|
||||
local_server
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.561907
|
||||
|
||||
==========
|
||||
Inline bot
|
||||
==========
|
||||
|
|
|
|||
8
docs/source/examples/local_server.rst
Normal file
8
docs/source/examples/local_server.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
============
|
||||
Local server
|
||||
============
|
||||
|
||||
.. literalinclude:: ../../../examples/local_server.py
|
||||
:caption: local_server.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.566615
|
||||
|
||||
===========
|
||||
Media group
|
||||
===========
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.560132
|
||||
|
||||
========================
|
||||
Middleware and antiflood
|
||||
========================
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.579017
|
||||
|
||||
========
|
||||
Payments
|
||||
========
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.555359
|
||||
|
||||
=================
|
||||
Proxy and emojize
|
||||
=================
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.568530
|
||||
|
||||
==============================
|
||||
Regexp commands filter example
|
||||
==============================
|
||||
|
|
|
|||
|
|
@ -1,14 +1,8 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.563878
|
||||
==================
|
||||
Throttling example
|
||||
==================
|
||||
|
||||
=================
|
||||
Throtling example
|
||||
=================
|
||||
|
||||
Example for throttling manager.
|
||||
You can use that for flood controlling.
|
||||
|
||||
.. literalinclude:: ../../../examples/throtling_example.py
|
||||
:caption: throtling_example.py
|
||||
.. literalinclude:: ../../../examples/throttling_example.py
|
||||
:caption: throttling_example.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 7-
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
.. Autogenerated file at 2018-10-28 19:31:48.341172
|
||||
|
||||
===============
|
||||
Webhook example
|
||||
===============
|
||||
|
||||
Example outdated
|
||||
|
||||
.. literalinclude:: ../../../examples/webhook_example.py
|
||||
:caption: webhook_example.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 5-
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
.. Autogenerated file at 2018-09-08 02:07:37.576034
|
||||
===================
|
||||
Webhook example old
|
||||
===================
|
||||
|
||||
=================
|
||||
Webhook example 2
|
||||
=================
|
||||
|
||||
.. literalinclude:: ../../../examples/webhook_example_2.py
|
||||
.. literalinclude:: ../../../examples/webhook_example_old.py
|
||||
:caption: webhook_example_2.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ 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.9-blue.svg?style=flat-square&logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.3-blue.svg?style=flat-square&logo=telegram
|
||||
:target: https://core.telegram.org/bots/api
|
||||
:alt: Telegram Bot API
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,13 @@ Using Pipenv
|
|||
|
||||
$ pipenv install aiogram
|
||||
|
||||
Using AUR
|
||||
Using Pacman
|
||||
---------
|
||||
*aiogram* is also available in Arch User Repository, so you can install this framework on any Arch-based distribution like ArchLinux, Antergos, Manjaro, etc. To do this, use your favorite AUR-helper and install the `python-aiogram <https://aur.archlinux.org/packages/python-aiogram/>`_ package.
|
||||
*aiogram* is also available in Arch Linux Repository, so you can install this framework on any Arch-based distribution like Arch Linux, Antergos, Manjaro, etc. To do this, just use pacman to install the `python-aiogram <https://archlinux.org/packages/community/any/python-aiogram/>`_ package:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pacman -S python-aiogram
|
||||
|
||||
From sources
|
||||
------------
|
||||
|
|
@ -28,7 +32,7 @@ From sources
|
|||
$ cd aiogram
|
||||
$ python setup.py install
|
||||
|
||||
Or if you want to install stable version (The same with version form PyPi):
|
||||
Or if you want to install stable version (The same with version from PyPi):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
|
|
|||
|
|
@ -24,17 +24,17 @@ Next step: interaction with bots starts with one command. Register your first co
|
|||
:language: python
|
||||
:lines: 20-25
|
||||
|
||||
If you want to handle all messages in the chat simply add handler without filters:
|
||||
If you want to handle all text messages in the chat simply add handler without filters:
|
||||
|
||||
.. literalinclude:: ../../examples/echo_bot.py
|
||||
:language: python
|
||||
:lines: 35-37
|
||||
:lines: 44-49
|
||||
|
||||
Last step: run long polling.
|
||||
|
||||
.. literalinclude:: ../../examples/echo_bot.py
|
||||
:language: python
|
||||
:lines: 40-41
|
||||
:lines: 52-53
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
|
@ -42,4 +42,4 @@ Summary
|
|||
.. literalinclude:: ../../examples/echo_bot.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: -19,27-
|
||||
:lines: -27,43-
|
||||
|
|
|
|||
|
|
@ -13,3 +13,4 @@ Utils
|
|||
parts
|
||||
json
|
||||
emoji
|
||||
deep_linking
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ async def query_post_vote(query: types.CallbackQuery, callback_data: typing.Dict
|
|||
|
||||
@dp.errors_handler(exception=MessageNotModified)
|
||||
async def message_not_modified_handler(update, error):
|
||||
return True
|
||||
return True # errors_handler must return True if error was handled correctly
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ async def callback_vote_action(query: types.CallbackQuery, callback_data: typing
|
|||
|
||||
@dp.errors_handler(exception=MessageNotModified) # handle the cases when this exception raises
|
||||
async def message_not_modified_handler(update, error):
|
||||
return True
|
||||
return True # errors_handler must return True if error was handled correctly
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ bot = Bot(token=API_TOKEN)
|
|||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
@dp.message_handler(chat_type=[ChatType.PRIVATE, ChatType.CHANNEL])
|
||||
@dp.message_handler(chat_type=[ChatType.PRIVATE, ChatType.SUPERGROUP])
|
||||
async def send_welcome(message: types.Message):
|
||||
"""
|
||||
This handler will be called when user sends `/start` or `/help` command
|
||||
This handler will be called when user sends message in private chat or supergroup
|
||||
"""
|
||||
await message.reply("Hi!\nI'm hearing your messages in private chats and channels")
|
||||
await message.reply("Hi!\nI'm hearing your messages in private chats and supergroups")
|
||||
|
||||
# propagate message to the next handler
|
||||
raise SkipHandler
|
||||
|
|
@ -33,7 +33,7 @@ async def send_welcome(message: types.Message):
|
|||
@dp.message_handler(chat_type=ChatType.PRIVATE)
|
||||
async def send_welcome(message: types.Message):
|
||||
"""
|
||||
This handler will be called when user sends `/start` or `/help` command
|
||||
This handler will be called when user sends message in private chat
|
||||
"""
|
||||
await message.reply("Hi!\nI'm hearing your messages only in private chats")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"""
|
||||
Internalize your bot
|
||||
Internationalize your bot
|
||||
|
||||
Step 1: extract texts
|
||||
# pybabel extract i18n_example.py -o locales/mybot.pot
|
||||
# pybabel extract --input-dirs=. -o locales/mybot.pot
|
||||
|
||||
Some useful options:
|
||||
- Extract texts with pluralization support
|
||||
|
|
@ -16,9 +16,14 @@ Step 1: extract texts
|
|||
- 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
|
||||
Step 2: create *.po files. E.g. create en, ru, uk locales.
|
||||
# pybabel init -i locales/mybot.pot -d locales -D mybot -l en
|
||||
# pybabel init -i locales/mybot.pot -d locales -D mybot -l ru
|
||||
# pybabel init -i locales/mybot.pot -d locales -D mybot -l uk
|
||||
|
||||
Step 3: translate texts located in locales/{language}/LC_MESSAGES/mybot.po
|
||||
To open .po file you can use basic text editor or any PO editor, e.g. https://poedit.net/
|
||||
|
||||
Step 4: compile translations
|
||||
# pybabel compile -d locales -D mybot
|
||||
|
||||
|
|
@ -27,7 +32,8 @@ Step 5: When you change the code of your bot you need to update po & mo files.
|
|||
command from step 1
|
||||
Step 5.2: update po files
|
||||
# pybabel update -d locales -D mybot -i locales/mybot.pot
|
||||
Step 5.3: update your translations
|
||||
Step 5.3: update your translations
|
||||
location and tools you know from step 3
|
||||
Step 5.4: compile mo files
|
||||
command from step 4
|
||||
"""
|
||||
|
|
@ -92,5 +98,6 @@ async def cmd_like(message: types.Message, locale):
|
|||
# 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)
|
||||
|
|
|
|||
27
examples/local_server.py
Normal file
27
examples/local_server.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import logging
|
||||
|
||||
from aiogram import Bot, Dispatcher, executor, types
|
||||
from aiogram.bot.api import TelegramAPIServer
|
||||
from aiogram.types import ContentType
|
||||
|
||||
API_TOKEN = 'BOT TOKEN HERE'
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Create private Bot API server endpoints wrapper
|
||||
local_server = TelegramAPIServer.from_base('http://localhost')
|
||||
|
||||
# Initialize bot with using local server
|
||||
bot = Bot(token=API_TOKEN, server=local_server)
|
||||
# ... and dispatcher
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=ContentType.ANY)
|
||||
async def echo(message: types.Message):
|
||||
await message.copy_to(message.chat.id)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
34
examples/separate_api_route_example.py
Normal file
34
examples/separate_api_route_example.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# NOTE: This is an example of an integration between
|
||||
# externally created Application object and the aiogram's dispatcher
|
||||
# This can be used for a custom route, for instance
|
||||
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.dispatcher.webhook import configure_app
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
bot = Bot(token=config.bot_token)
|
||||
dp = Dispatcher(bot)
|
||||
|
||||
|
||||
|
||||
@dp.message_handler(commands=["start"])
|
||||
async def cmd_start(message: types.Message):
|
||||
await message.reply("start!")
|
||||
|
||||
|
||||
# handle /api route
|
||||
async def api_handler(request):
|
||||
return web.json_response({"status": "OK"}, status=200)
|
||||
|
||||
|
||||
app = web.Application()
|
||||
# add a custom route
|
||||
app.add_routes([web.post('/api', api_handler)])
|
||||
# every request to /bot route will be retransmitted to dispatcher to be handled
|
||||
# as a bot update
|
||||
configure_app(dp, app, "/bot")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
web.run_app(app, port=9000)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue