mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-12 18:19:34 +00:00
Merge branch 'dev-1.x' of https://github.com/aiogram/aiogram into dev-1.x
This commit is contained in:
commit
4a9533ded5
60 changed files with 1433 additions and 387 deletions
15
.gitignore
vendored
15
.gitignore
vendored
|
|
@ -39,10 +39,6 @@ htmlcov/
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*,cover
|
*,cover
|
||||||
.hypothesis/
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
@ -50,22 +46,11 @@ docs/_build/
|
||||||
# pyenv
|
# pyenv
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# dotenv
|
|
||||||
.env
|
|
||||||
|
|
||||||
# virtualenv
|
# virtualenv
|
||||||
.venv
|
.venv
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
### JetBrains template
|
### JetBrains template
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
[](https://github.com/aiogram/aiogram/issues)
|
[](https://github.com/aiogram/aiogram/issues)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
**aiogram** is are pretty simple and fully asynchronously library for [Telegram Bot API](https://core.telegram.org/bots/api) written in Python 3.6 with [asyncio](https://docs.python.org/3/library/asyncio.html) and [aiohttp](https://github.com/aio-libs/aiohttp). It helps to make your bots more faster and simpler.
|
**aiogram** is a pretty simple and fully asynchronous library for [Telegram Bot API](https://core.telegram.org/bots/api) written in Python 3.6 with [asyncio](https://docs.python.org/3/library/asyncio.html) and [aiohttp](https://github.com/aio-libs/aiohttp). It helps you to make your bots faster and simpler.
|
||||||
|
|
||||||
You can [read the docs here](http://aiogram.readthedocs.io/en/latest/).
|
You can [read the docs here](http://aiogram.readthedocs.io/en/latest/).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ AIOGramBot
|
||||||
:alt: MIT License
|
:alt: MIT License
|
||||||
|
|
||||||
|
|
||||||
**aiogram** is are pretty simple and fully asynchronously library for `Telegram Bot API <https://core.telegram.org/bots/api>`_ written in Python 3.6 with `asyncio <https://docs.python.org/3/library/asyncio.html>`_ and `aiohttp <https://github.com/aio-libs/aiohttp>`_. It helps to make your bots more faster and simpler.
|
**aiogram** is a pretty simple and fully asynchronous library for `Telegram Bot API <https://core.telegram.org/bots/api>`_ written in Python 3.6 with `asyncio <https://docs.python.org/3/library/asyncio.html>`_ and `aiohttp <https://github.com/aio-libs/aiohttp>`_. It helps you to make your bots faster and simpler.
|
||||||
|
|
||||||
You can `read the docs here <http://aiogram.readthedocs.io/en/latest/>`_.
|
You can `read the docs here <http://aiogram.readthedocs.io/en/latest/>`_.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ try:
|
||||||
from .bot import Bot
|
from .bot import Bot
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
if e.name == 'aiohttp':
|
if e.name == 'aiohttp':
|
||||||
warnings.warn('Dependencies is not installed!', category=ImportWarning)
|
warnings.warn('Dependencies are not installed!',
|
||||||
|
category=ImportWarning)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
@ -19,8 +20,8 @@ else:
|
||||||
|
|
||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
|
|
||||||
VERSION = Version(1, 0, 5, stage=Stage.DEV, build=0)
|
VERSION = Version(1, 1, 1, stage=Stage.DEV, build=0)
|
||||||
API_VERSION = Version(3, 5)
|
API_VERSION = Version(3, 6)
|
||||||
|
|
||||||
__version__ = VERSION.version
|
__version__ = VERSION.version
|
||||||
__api_version__ = API_VERSION.version
|
__api_version__ = API_VERSION.version
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
from . import api
|
||||||
from .base import BaseBot
|
from .base import BaseBot
|
||||||
from .bot import Bot
|
from .bot import Bot
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'BaseBot',
|
'BaseBot',
|
||||||
'Bot'
|
'Bot',
|
||||||
|
'api'
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .. import types
|
from .. import types
|
||||||
from ..utils import exceptions
|
|
||||||
from ..utils import json
|
from ..utils import json
|
||||||
|
from ..utils import exceptions
|
||||||
from ..utils.helper import Helper, HelperMode, Item
|
from ..utils.helper import Helper, HelperMode, Item
|
||||||
|
|
||||||
# Main aiogram logger
|
# Main aiogram logger
|
||||||
|
|
@ -101,7 +100,7 @@ def _compose_data(params=None, files=None):
|
||||||
:param files:
|
:param files:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
data = aiohttp.formdata.FormData()
|
data = aiohttp.formdata.FormData(quote_fields=False)
|
||||||
|
|
||||||
if params:
|
if params:
|
||||||
for key, value in params.items():
|
for key, value in params.items():
|
||||||
|
|
@ -115,7 +114,7 @@ def _compose_data(params=None, files=None):
|
||||||
else:
|
else:
|
||||||
raise ValueError('Tuple must have exactly 2 elements: filename, fileobj')
|
raise ValueError('Tuple must have exactly 2 elements: filename, fileobj')
|
||||||
elif isinstance(f, types.InputFile):
|
elif isinstance(f, types.InputFile):
|
||||||
filename, fileobj = f.get_filename(), f.get_file()
|
filename, fileobj = f.filename, f.file
|
||||||
else:
|
else:
|
||||||
filename, fileobj = _guess_filename(f) or key, f
|
filename, fileobj = _guess_filename(f) or key, f
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from typing import Dict, List, Optional, Union
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from . import api
|
from . import api
|
||||||
from ..types import base
|
from ..types import ParseMode, base
|
||||||
from ..utils import json
|
from ..utils import json
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,8 +17,9 @@ class BaseBot:
|
||||||
def __init__(self, token: base.String,
|
def __init__(self, token: base.String,
|
||||||
loop: Optional[Union[asyncio.BaseEventLoop, asyncio.AbstractEventLoop]] = None,
|
loop: Optional[Union[asyncio.BaseEventLoop, asyncio.AbstractEventLoop]] = None,
|
||||||
connections_limit: Optional[base.Integer] = 10,
|
connections_limit: Optional[base.Integer] = 10,
|
||||||
proxy: str = None, proxy_auth: Optional[aiohttp.BasicAuth] = None,
|
proxy: Optional[base.String] = None, proxy_auth: Optional[aiohttp.BasicAuth] = None,
|
||||||
validate_token: Optional[bool] = True):
|
validate_token: Optional[base.Boolean] = True,
|
||||||
|
parse_mode=None):
|
||||||
"""
|
"""
|
||||||
Instructions how to get Bot token is found here: https://core.telegram.org/bots#3-how-do-i-create-a-bot
|
Instructions how to get Bot token is found here: https://core.telegram.org/bots#3-how-do-i-create-a-bot
|
||||||
|
|
||||||
|
|
@ -34,6 +35,8 @@ class BaseBot:
|
||||||
:type proxy_auth: Optional :obj:`aiohttp.BasicAuth`
|
:type proxy_auth: Optional :obj:`aiohttp.BasicAuth`
|
||||||
:param validate_token: Validate token.
|
:param validate_token: Validate token.
|
||||||
:type validate_token: :obj:`bool`
|
:type validate_token: :obj:`bool`
|
||||||
|
:param parse_mode: You can set default parse mode
|
||||||
|
:type parse_mode: :obj:`str`
|
||||||
:raise: when token is invalid throw an :obj:`aiogram.utils.exceptions.ValidationError`
|
:raise: when token is invalid throw an :obj:`aiogram.utils.exceptions.ValidationError`
|
||||||
"""
|
"""
|
||||||
# Authentication
|
# Authentication
|
||||||
|
|
@ -61,6 +64,8 @@ class BaseBot:
|
||||||
# Data stored in bot instance
|
# Data stored in bot instance
|
||||||
self._data = {}
|
self._data = {}
|
||||||
|
|
||||||
|
self.parse_mode = parse_mode
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
@ -74,7 +79,7 @@ class BaseBot:
|
||||||
if self.session and not self.session.closed:
|
if self.session and not self.session.closed:
|
||||||
self.session.close()
|
self.session.close()
|
||||||
|
|
||||||
def create_temp_session(self, limit: int = 1, force_close: bool = False) -> aiohttp.ClientSession:
|
def create_temp_session(self, limit: base.Integer = 1, force_close: base.Boolean = False) -> aiohttp.ClientSession:
|
||||||
"""
|
"""
|
||||||
Create temporary session
|
Create temporary session
|
||||||
|
|
||||||
|
|
@ -223,3 +228,23 @@ class BaseBot:
|
||||||
:return: value or default value
|
:return: value or default value
|
||||||
"""
|
"""
|
||||||
return self._data.get(key, default)
|
return self._data.get(key, default)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parse_mode(self):
|
||||||
|
return getattr(self, '_parse_mode', None)
|
||||||
|
|
||||||
|
@parse_mode.setter
|
||||||
|
def parse_mode(self, value):
|
||||||
|
if value is None:
|
||||||
|
setattr(self, '_parse_mode', None)
|
||||||
|
else:
|
||||||
|
if not isinstance(value, str):
|
||||||
|
raise TypeError(f"Parse mode must be an 'str' not {type(value)}")
|
||||||
|
value = value.lower()
|
||||||
|
if value not in ParseMode.all():
|
||||||
|
raise ValueError(f"Parse mode must be one of {ParseMode.all()}")
|
||||||
|
setattr(self, '_parse_mode', value)
|
||||||
|
|
||||||
|
@parse_mode.deleter
|
||||||
|
def parse_mode(self):
|
||||||
|
self.parse_mode = None
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ class Bot(BaseBot):
|
||||||
typing.Union[typing.List[base.String], None] = None) -> typing.List[types.Update]:
|
typing.Union[typing.List[base.String], None] = None) -> typing.List[types.Update]:
|
||||||
"""
|
"""
|
||||||
Use this method to receive incoming updates using long polling (wiki).
|
Use this method to receive incoming updates using long polling (wiki).
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
1. This method will not work if an outgoing webhook is set up.
|
1. This method will not work if an outgoing webhook is set up.
|
||||||
2. In order to avoid getting duplicate updates, recalculate offset after each server response.
|
2. In order to avoid getting duplicate updates, recalculate offset after each server response.
|
||||||
|
|
@ -132,7 +132,7 @@ class Bot(BaseBot):
|
||||||
async def get_webhook_info(self) -> types.WebhookInfo:
|
async def get_webhook_info(self) -> types.WebhookInfo:
|
||||||
"""
|
"""
|
||||||
Use this method to get current webhook status. Requires no parameters.
|
Use this method to get current webhook status. Requires no parameters.
|
||||||
|
|
||||||
If the bot is using getUpdates, will return an object with the url field empty.
|
If the bot is using getUpdates, will return an object with the url field empty.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#getwebhookinfo
|
Source: https://core.telegram.org/bots/api#getwebhookinfo
|
||||||
|
|
@ -180,7 +180,7 @@ class Bot(BaseBot):
|
||||||
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
|
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
|
||||||
:param text: Text of the message to be sent
|
:param text: Text of the message to be sent
|
||||||
:type text: :obj:`base.String`
|
:type text: :obj:`base.String`
|
||||||
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
|
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
|
||||||
fixed-width text or inline URLs in your bot's message.
|
fixed-width text or inline URLs in your bot's message.
|
||||||
:type parse_mode: :obj:`typing.Union[base.String, None]`
|
:type parse_mode: :obj:`typing.Union[base.String, None]`
|
||||||
:param disable_web_page_preview: Disables link previews for links in this message
|
:param disable_web_page_preview: Disables link previews for links in this message
|
||||||
|
|
@ -190,13 +190,16 @@ class Bot(BaseBot):
|
||||||
:param reply_to_message_id: If the message is a reply, ID of the original message
|
: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.Union[base.Integer, None]`
|
||||||
:param reply_markup: Additional interface options.
|
:param reply_markup: Additional interface options.
|
||||||
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||||
:return: On success, the sent Message is returned.
|
:return: On success, the sent Message is returned.
|
||||||
:rtype: :obj:`types.Message`
|
:rtype: :obj:`types.Message`
|
||||||
"""
|
"""
|
||||||
reply_markup = prepare_arg(reply_markup)
|
reply_markup = prepare_arg(reply_markup)
|
||||||
payload = generate_payload(**locals())
|
payload = generate_payload(**locals())
|
||||||
|
if self.parse_mode:
|
||||||
|
payload.setdefault('parse_mode', self.parse_mode)
|
||||||
|
|
||||||
result = await self.request(api.Methods.SEND_MESSAGE, payload)
|
result = await self.request(api.Methods.SEND_MESSAGE, payload)
|
||||||
|
|
||||||
return types.Message(**result)
|
return types.Message(**result)
|
||||||
|
|
@ -353,6 +356,7 @@ class Bot(BaseBot):
|
||||||
width: typing.Union[base.Integer, None] = None,
|
width: typing.Union[base.Integer, None] = None,
|
||||||
height: typing.Union[base.Integer, None] = None,
|
height: typing.Union[base.Integer, None] = None,
|
||||||
caption: typing.Union[base.String, None] = None,
|
caption: typing.Union[base.String, None] = None,
|
||||||
|
supports_streaming: typing.Union[base.Boolean, None] = None,
|
||||||
disable_notification: typing.Union[base.Boolean, None] = None,
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
reply_to_message_id: typing.Union[base.Integer, None] = None,
|
reply_to_message_id: typing.Union[base.Integer, None] = None,
|
||||||
reply_markup: typing.Union[types.InlineKeyboardMarkup,
|
reply_markup: typing.Union[types.InlineKeyboardMarkup,
|
||||||
|
|
@ -360,8 +364,8 @@ class Bot(BaseBot):
|
||||||
types.ReplyKeyboardRemove,
|
types.ReplyKeyboardRemove,
|
||||||
types.ForceReply, None] = None) -> types.Message:
|
types.ForceReply, None] = None) -> types.Message:
|
||||||
"""
|
"""
|
||||||
Use this method to send video files, Telegram clients support mp4 videos
|
Use this method to send video files, Telegram clients support mp4 videos
|
||||||
(other formats may be sent as Document).
|
(other formats may be sent as Document).
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#sendvideo
|
Source: https://core.telegram.org/bots/api#sendvideo
|
||||||
|
|
||||||
|
|
@ -377,12 +381,14 @@ class Bot(BaseBot):
|
||||||
:type height: :obj:`typing.Union[base.Integer, None]`
|
:type height: :obj:`typing.Union[base.Integer, None]`
|
||||||
:param caption: Video caption (may also be used when resending videos by file_id), 0-200 characters
|
:param caption: Video caption (may also be used when resending videos by file_id), 0-200 characters
|
||||||
:type caption: :obj:`typing.Union[base.String, None]`
|
:type caption: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param supports_streaming: Pass True, if the uploaded video is suitable for streaming
|
||||||
|
:type supports_streaming: :obj:`typing.Union[base.Boolean, None]`
|
||||||
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
: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.Union[base.Boolean, None]`
|
||||||
:param reply_to_message_id: If the message is a reply, ID of the original message
|
: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.Union[base.Integer, None]`
|
||||||
:param reply_markup: Additional interface options.
|
:param reply_markup: Additional interface options.
|
||||||
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||||
:return: On success, the sent Message is returned.
|
:return: On success, the sent Message is returned.
|
||||||
:rtype: :obj:`types.Message`
|
:rtype: :obj:`types.Message`
|
||||||
|
|
@ -904,8 +910,8 @@ class Bot(BaseBot):
|
||||||
|
|
||||||
async def export_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String]) -> base.String:
|
async def export_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String]) -> base.String:
|
||||||
"""
|
"""
|
||||||
Use this method to export an invite link to a supergroup or a channel.
|
Use this method to generate a new invite link for a chat; any previously generated link is revoked.
|
||||||
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/api#exportchatinvitelink
|
Source: https://core.telegram.org/bots/api#exportchatinvitelink
|
||||||
|
|
||||||
|
|
@ -1245,6 +1251,9 @@ class Bot(BaseBot):
|
||||||
"""
|
"""
|
||||||
reply_markup = prepare_arg(reply_markup)
|
reply_markup = prepare_arg(reply_markup)
|
||||||
payload = generate_payload(**locals())
|
payload = generate_payload(**locals())
|
||||||
|
if self.parse_mode:
|
||||||
|
payload.setdefault('parse_mode', self.parse_mode)
|
||||||
|
|
||||||
result = await self.request(api.Methods.EDIT_MESSAGE_TEXT, payload)
|
result = await self.request(api.Methods.EDIT_MESSAGE_TEXT, payload)
|
||||||
|
|
||||||
if isinstance(result, bool):
|
if isinstance(result, bool):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding:utf-8; -*-
|
|
||||||
__all__ = ['RethinkDBStorage']
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
|
@ -8,6 +5,7 @@ import rethinkdb as r
|
||||||
|
|
||||||
from ...dispatcher import BaseStorage
|
from ...dispatcher import BaseStorage
|
||||||
|
|
||||||
|
__all__ = ['RethinkDBStorage', 'ConnectionNotClosed']
|
||||||
|
|
||||||
r.set_loop_type('asyncio')
|
r.set_loop_type('asyncio')
|
||||||
|
|
||||||
|
|
@ -36,6 +34,7 @@ class RethinkDBStorage(BaseStorage):
|
||||||
await storage.close()
|
await storage.close()
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host='localhost', port=28015, db='aiogram', table='aiogram', auth_key=None,
|
def __init__(self, host='localhost', port=28015, db='aiogram', table='aiogram', auth_key=None,
|
||||||
user=None, password=None, timeout=20, ssl=None, loop=None):
|
user=None, password=None, timeout=20, ssl=None, loop=None):
|
||||||
self._host = host
|
self._host = host
|
||||||
|
|
@ -51,15 +50,17 @@ class RethinkDBStorage(BaseStorage):
|
||||||
self._connection: r.Connection = None
|
self._connection: r.Connection = None
|
||||||
self._loop = loop or asyncio.get_event_loop()
|
self._loop = loop or asyncio.get_event_loop()
|
||||||
self._lock = asyncio.Lock(loop=self._loop)
|
self._lock = asyncio.Lock(loop=self._loop)
|
||||||
|
|
||||||
async def connection(self):
|
async def connection(self):
|
||||||
"""
|
"""
|
||||||
Get or create connection.
|
Get or create connection.
|
||||||
"""
|
"""
|
||||||
async with self._lock: # thread-safe
|
async with self._lock: # thread-safe
|
||||||
if not self._connection:
|
if not self._connection:
|
||||||
self._connection = await r.connect(host=self._host, port=self._port, db=self._db, auth_key=self._auth_key, user=self._user,
|
self._connection = await r.connect(host=self._host, port=self._port, db=self._db,
|
||||||
password=self._password, timeout=self._timeout, ssl=self._ssl, io_loop=self._loop)
|
auth_key=self._auth_key, user=self._user,
|
||||||
|
password=self._password, timeout=self._timeout, ssl=self._ssl,
|
||||||
|
io_loop=self._loop)
|
||||||
return self._connection
|
return self._connection
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
|
|
@ -99,7 +100,8 @@ class RethinkDBStorage(BaseStorage):
|
||||||
else:
|
else:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'state': state}}).run(conn)
|
await r.table(self._table).insert({'id': chat, user: {'state': state}}).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):
|
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 = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
conn = await self.connection()
|
conn = await self.connection()
|
||||||
if await r.table(self._table).get(chat).run(conn):
|
if await r.table(self._table).get(chat).run(conn):
|
||||||
|
|
@ -107,7 +109,8 @@ class RethinkDBStorage(BaseStorage):
|
||||||
else:
|
else:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'data': data}}).run(conn)
|
await r.table(self._table).insert({'id': chat, user: {'data': data}}).run(conn)
|
||||||
|
|
||||||
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, data: typing.Dict = None,
|
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||||
|
data: typing.Dict = None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
conn = await self.connection()
|
conn = await self.connection()
|
||||||
|
|
@ -125,7 +128,8 @@ class RethinkDBStorage(BaseStorage):
|
||||||
conn = await self.connection()
|
conn = await self.connection()
|
||||||
return await r.table(self._table).get(chat)[user]['bucket'].default(default or {}).run(conn)
|
return await r.table(self._table).get(chat)[user]['bucket'].default(default or {}).run(conn)
|
||||||
|
|
||||||
async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, bucket: typing.Dict = None):
|
async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||||
|
bucket: typing.Dict = None):
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
conn = await self.connection()
|
conn = await self.connection()
|
||||||
if await r.table(self._table).get(chat).run(conn):
|
if await r.table(self._table).get(chat).run(conn):
|
||||||
|
|
@ -133,7 +137,8 @@ class RethinkDBStorage(BaseStorage):
|
||||||
else:
|
else:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'bucket': bucket}}).run(conn)
|
await r.table(self._table).insert({'id': chat, user: {'bucket': bucket}}).run(conn)
|
||||||
|
|
||||||
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, bucket: typing.Dict = None,
|
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
|
||||||
|
user: typing.Union[str, int, None] = None, bucket: typing.Dict = None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
conn = await self.connection()
|
conn = await self.connection()
|
||||||
|
|
|
||||||
|
|
@ -444,8 +444,6 @@ class Dispatcher:
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
if commands is None:
|
|
||||||
commands = []
|
|
||||||
if content_types is None:
|
if content_types is None:
|
||||||
content_types = ContentType.TEXT
|
content_types = ContentType.TEXT
|
||||||
if custom_filters is None:
|
if custom_filters is None:
|
||||||
|
|
@ -509,8 +507,6 @@ class Dispatcher:
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
if commands is None:
|
|
||||||
commands = []
|
|
||||||
if content_types is None:
|
if content_types is None:
|
||||||
content_types = ContentType.TEXT
|
content_types = ContentType.TEXT
|
||||||
if custom_filters is None:
|
if custom_filters is None:
|
||||||
|
|
@ -566,8 +562,6 @@ class Dispatcher:
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
if commands is None:
|
|
||||||
commands = []
|
|
||||||
if content_types is None:
|
if content_types is None:
|
||||||
content_types = ContentType.TEXT
|
content_types = ContentType.TEXT
|
||||||
if custom_filters is None:
|
if custom_filters is None:
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,32 @@ class RegexpFilter(Filter):
|
||||||
return bool(self.regexp.search(message.text))
|
return bool(self.regexp.search(message.text))
|
||||||
|
|
||||||
|
|
||||||
|
class RegexpCommandsFilter(AsyncFilter):
|
||||||
|
"""
|
||||||
|
Check commands by regexp in message
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, regexp_commands):
|
||||||
|
self.regexp_commands = [re.compile(command, flags=re.IGNORECASE | re.MULTILINE) for command in regexp_commands]
|
||||||
|
|
||||||
|
async def check(self, message):
|
||||||
|
if not message.is_command():
|
||||||
|
return False
|
||||||
|
|
||||||
|
command = message.text.split()[0][1:]
|
||||||
|
command, _, mention = command.partition('@')
|
||||||
|
|
||||||
|
if mention and mention != (await message.bot.me).username:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for command in self.regexp_commands:
|
||||||
|
search = command.search(message.text)
|
||||||
|
if search:
|
||||||
|
message.conf['regexp_command'] = search
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ContentTypeFilter(Filter):
|
class ContentTypeFilter(Filter):
|
||||||
"""
|
"""
|
||||||
Check message content type
|
Check message content type
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ class BaseStorage:
|
||||||
async def get_data(self, *,
|
async def get_data(self, *,
|
||||||
chat: typing.Union[str, int, None] = None,
|
chat: typing.Union[str, int, None] = None,
|
||||||
user: typing.Union[str, int, None] = None,
|
user: typing.Union[str, int, None] = None,
|
||||||
default: typing.Optional[str] = None) -> typing.Dict:
|
default: typing.Optional[typing.Dict] = None) -> typing.Dict:
|
||||||
"""
|
"""
|
||||||
Get state-data for user in chat. Return `default` if data is not presented in storage.
|
Get state-data for user in chat. Return `default` if data is not presented in storage.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,10 @@ from aiohttp import web
|
||||||
|
|
||||||
from .. import types
|
from .. import types
|
||||||
from ..bot import api
|
from ..bot import api
|
||||||
|
from ..types import ParseMode
|
||||||
from ..types.base import Boolean, Float, Integer, String
|
from ..types.base import Boolean, Float, Integer, String
|
||||||
from ..utils import context
|
from ..utils import context
|
||||||
|
from ..utils import helper, markdown
|
||||||
from ..utils import json
|
from ..utils import json
|
||||||
from ..utils.deprecated import warn_deprecated as warn
|
from ..utils.deprecated import warn_deprecated as warn
|
||||||
from ..utils.exceptions import TimeoutWarning
|
from ..utils.exceptions import TimeoutWarning
|
||||||
|
|
@ -319,8 +321,24 @@ class BaseResponse:
|
||||||
:param bot: Bot instance.
|
:param bot: Bot instance.
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
method_name = helper.HelperMode.apply(self.method, helper.HelperMode.snake_case)
|
||||||
|
method = getattr(bot, method_name, None)
|
||||||
|
if method:
|
||||||
|
return await method(**self.cleanup())
|
||||||
return await bot.request(self.method, self.cleanup())
|
return await bot.request(self.method, self.cleanup())
|
||||||
|
|
||||||
|
async def __call__(self, bot=None):
|
||||||
|
if bot is None:
|
||||||
|
from aiogram.dispatcher import ctx
|
||||||
|
bot = ctx.get_bot()
|
||||||
|
return await self.execute_response(bot)
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
return await self()
|
||||||
|
|
||||||
|
|
||||||
class ReplyToMixin:
|
class ReplyToMixin:
|
||||||
"""
|
"""
|
||||||
|
|
@ -337,8 +355,80 @@ class ReplyToMixin:
|
||||||
setattr(self, 'reply_to_message_id', message.message_id if isinstance(message, types.Message) else message)
|
setattr(self, 'reply_to_message_id', message.message_id if isinstance(message, types.Message) else message)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def to(self, target: typing.Union[types.Message, types.Chat, types.base.Integer, types.base.String]):
|
||||||
|
"""
|
||||||
|
Send to chat
|
||||||
|
|
||||||
class SendMessage(BaseResponse, ReplyToMixin):
|
:param target: message or chat or id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if isinstance(target, types.Message):
|
||||||
|
chat_id = target.chat.id
|
||||||
|
elif isinstance(target, types.Chat):
|
||||||
|
chat_id = target.id
|
||||||
|
elif isinstance(target, (int, str)):
|
||||||
|
chat_id = target
|
||||||
|
else:
|
||||||
|
raise TypeError(f"Bad type of target. ({type(target)})")
|
||||||
|
|
||||||
|
setattr(self, 'chat_id', chat_id)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class DisableNotificationMixin:
|
||||||
|
def without_notification(self):
|
||||||
|
"""
|
||||||
|
Disable notification
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
setattr(self, 'disable_notification', True)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class DisableWebPagePreviewMixin:
|
||||||
|
def no_web_page_preview(self):
|
||||||
|
"""
|
||||||
|
Disable web page preview
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
setattr(self, 'disable_web_page_preview', True)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class ParseModeMixin:
|
||||||
|
def as_html(self):
|
||||||
|
"""
|
||||||
|
Set parse_mode to HTML
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
setattr(self, 'parse_mode', ParseMode.HTML)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def as_markdown(self):
|
||||||
|
"""
|
||||||
|
Set parse_mode to Markdown
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
setattr(self, 'parse_mode', ParseMode.MARKDOWN)
|
||||||
|
return self
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _global_parse_mode():
|
||||||
|
"""
|
||||||
|
Detect global parse mode
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
bot = context.get_value('bot', None)
|
||||||
|
if bot is not None:
|
||||||
|
return bot.parse_mode
|
||||||
|
|
||||||
|
|
||||||
|
class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificationMixin, DisableWebPagePreviewMixin):
|
||||||
"""
|
"""
|
||||||
You can send message with webhook by using this instance of this object.
|
You can send message with webhook by using this instance of this object.
|
||||||
All arguments is equal with Bot.send_message method.
|
All arguments is equal with Bot.send_message method.
|
||||||
|
|
@ -350,8 +440,8 @@ class SendMessage(BaseResponse, ReplyToMixin):
|
||||||
|
|
||||||
method = api.Methods.SEND_MESSAGE
|
method = api.Methods.SEND_MESSAGE
|
||||||
|
|
||||||
def __init__(self, chat_id: Union[Integer, String],
|
def __init__(self, chat_id: Union[Integer, String] = None,
|
||||||
text: String,
|
text: String = None,
|
||||||
parse_mode: Optional[String] = None,
|
parse_mode: Optional[String] = None,
|
||||||
disable_web_page_preview: Optional[Boolean] = None,
|
disable_web_page_preview: Optional[Boolean] = None,
|
||||||
disable_notification: Optional[Boolean] = None,
|
disable_notification: Optional[Boolean] = None,
|
||||||
|
|
@ -372,6 +462,11 @@ class SendMessage(BaseResponse, ReplyToMixin):
|
||||||
- Additional interface options. A JSON-serialized object for an inline keyboard,
|
- Additional interface options. A JSON-serialized object for an inline keyboard,
|
||||||
custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.
|
custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.
|
||||||
"""
|
"""
|
||||||
|
if text is None:
|
||||||
|
text = ''
|
||||||
|
if parse_mode is None:
|
||||||
|
parse_mode = self._global_parse_mode()
|
||||||
|
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
self.text = text
|
self.text = text
|
||||||
self.parse_mode = parse_mode
|
self.parse_mode = parse_mode
|
||||||
|
|
@ -391,8 +486,32 @@ class SendMessage(BaseResponse, ReplyToMixin):
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def write(self, *text, sep=' '):
|
||||||
|
"""
|
||||||
|
Write text to response
|
||||||
|
|
||||||
class ForwardMessage(BaseResponse):
|
:param text:
|
||||||
|
:param sep:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.text += markdown.text(*text, sep)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def write_ln(self, *text, sep=' '):
|
||||||
|
"""
|
||||||
|
Write line
|
||||||
|
|
||||||
|
:param text:
|
||||||
|
:param sep:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if self.text and self.text[-1] != '\n':
|
||||||
|
self.text += '\n'
|
||||||
|
self.text += markdown.text(*text, sep) + '\n'
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for forward messages of any kind on to webhook.
|
Use that response type for forward messages of any kind on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -400,9 +519,9 @@ class ForwardMessage(BaseResponse):
|
||||||
|
|
||||||
method = api.Methods.FORWARD_MESSAGE
|
method = api.Methods.FORWARD_MESSAGE
|
||||||
|
|
||||||
def __init__(self, chat_id: Union[Integer, String],
|
def __init__(self, chat_id: Union[Integer, String] = None,
|
||||||
from_chat_id: Union[Integer, String],
|
from_chat_id: Union[Integer, String] = None,
|
||||||
message_id: Integer,
|
message_id: Integer = None,
|
||||||
disable_notification: Optional[Boolean] = None):
|
disable_notification: Optional[Boolean] = None):
|
||||||
"""
|
"""
|
||||||
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username of the
|
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username of the
|
||||||
|
|
@ -418,6 +537,17 @@ class ForwardMessage(BaseResponse):
|
||||||
self.message_id = message_id
|
self.message_id = message_id
|
||||||
self.disable_notification = disable_notification
|
self.disable_notification = disable_notification
|
||||||
|
|
||||||
|
def message(self, message: types.Message):
|
||||||
|
"""
|
||||||
|
Select target message
|
||||||
|
|
||||||
|
:param message:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
setattr(self, 'from_chat_id', message.chat.id)
|
||||||
|
setattr(self, 'message_id', message.message_id)
|
||||||
|
return self
|
||||||
|
|
||||||
def prepare(self) -> dict:
|
def prepare(self) -> dict:
|
||||||
return {
|
return {
|
||||||
'chat_id': self.chat_id,
|
'chat_id': self.chat_id,
|
||||||
|
|
@ -427,7 +557,7 @@ class ForwardMessage(BaseResponse):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendPhoto(BaseResponse, ReplyToMixin):
|
class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send photo on to webhook.
|
Use that response type for send photo on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -476,7 +606,7 @@ class SendPhoto(BaseResponse, ReplyToMixin):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendAudio(BaseResponse, ReplyToMixin):
|
class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send audio on to webhook.
|
Use that response type for send audio on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -538,7 +668,7 @@ class SendAudio(BaseResponse, ReplyToMixin):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendDocument(BaseResponse, ReplyToMixin):
|
class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send document on to webhook.
|
Use that response type for send document on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -588,7 +718,7 @@ class SendDocument(BaseResponse, ReplyToMixin):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendVideo(BaseResponse, ReplyToMixin):
|
class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send video on to webhook.
|
Use that response type for send video on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -651,7 +781,7 @@ class SendVideo(BaseResponse, ReplyToMixin):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendVoice(BaseResponse, ReplyToMixin):
|
class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send voice on to webhook.
|
Use that response type for send voice on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -705,7 +835,7 @@ class SendVoice(BaseResponse, ReplyToMixin):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendVideoNote(BaseResponse, ReplyToMixin):
|
class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send video note on to webhook.
|
Use that response type for send video note on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -758,7 +888,7 @@ class SendVideoNote(BaseResponse, ReplyToMixin):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendMediaGroup(BaseResponse):
|
class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use this method to send a group of photos or videos as an album.
|
Use this method to send a group of photos or videos as an album.
|
||||||
"""
|
"""
|
||||||
|
|
@ -839,7 +969,7 @@ class SendMediaGroup(BaseResponse):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class SendLocation(BaseResponse, ReplyToMixin):
|
class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send location on to webhook.
|
Use that response type for send location on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -884,7 +1014,7 @@ class SendLocation(BaseResponse, ReplyToMixin):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendVenue(BaseResponse, ReplyToMixin):
|
class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send venue on to webhook.
|
Use that response type for send venue on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -943,7 +1073,7 @@ class SendVenue(BaseResponse, ReplyToMixin):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendContact(BaseResponse, ReplyToMixin):
|
class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send contact on to webhook.
|
Use that response type for send contact on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -1268,7 +1398,7 @@ class SetChatDescription(BaseResponse):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PinChatMessage(BaseResponse):
|
class PinChatMessage(BaseResponse, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for pin chat message on to webhook.
|
Use that response type for pin chat message on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -1387,7 +1517,7 @@ class AnswerCallbackQuery(BaseResponse):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class EditMessageText(BaseResponse):
|
class EditMessageText(BaseResponse, ParseModeMixin, DisableWebPagePreviewMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for edit message text on to webhook.
|
Use that response type for edit message text on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -1419,6 +1549,9 @@ class EditMessageText(BaseResponse):
|
||||||
:param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for
|
:param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for
|
||||||
an inline keyboard.
|
an inline keyboard.
|
||||||
"""
|
"""
|
||||||
|
if parse_mode is None:
|
||||||
|
parse_mode = self._global_parse_mode()
|
||||||
|
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
self.message_id = message_id
|
self.message_id = message_id
|
||||||
self.inline_message_id = inline_message_id
|
self.inline_message_id = inline_message_id
|
||||||
|
|
@ -1541,7 +1674,7 @@ class DeleteMessage(BaseResponse):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendSticker(BaseResponse, ReplyToMixin):
|
class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send sticker on to webhook.
|
Use that response type for send sticker on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -1787,7 +1920,7 @@ class AnswerInlineQuery(BaseResponse):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendInvoice(BaseResponse, ReplyToMixin):
|
class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send invoice on to webhook.
|
Use that response type for send invoice on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
@ -1968,7 +2101,7 @@ class AnswerPreCheckoutQuery(BaseResponse):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SendGame(BaseResponse, ReplyToMixin):
|
class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
"""
|
"""
|
||||||
Use that response type for send game on to webhook.
|
Use that response type for send game on to webhook.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
from . import base
|
from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
|
from . import mixins
|
||||||
from .photo_size import PhotoSize
|
from .photo_size import PhotoSize
|
||||||
|
|
||||||
|
|
||||||
class Animation(base.TelegramObject):
|
class Animation(base.TelegramObject, mixins.Downloadable):
|
||||||
"""
|
"""
|
||||||
You can provide an animation for your game so that it looks stylish in chats
|
You can provide an animation for your game so that it looks stylish in chats
|
||||||
(check out Lumberjack for an example).
|
(check out Lumberjack for an example).
|
||||||
|
|
@ -17,11 +18,3 @@ class Animation(base.TelegramObject):
|
||||||
file_name: base.String = fields.Field()
|
file_name: base.String = fields.Field()
|
||||||
mime_type: base.String = fields.Field()
|
mime_type: base.String = fields.Field()
|
||||||
file_size: base.Integer = fields.Field()
|
file_size: base.Integer = fields.Field()
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return self.file_id
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, type(self)):
|
|
||||||
return other.file_id == self.file_id
|
|
||||||
return self.file_id == other
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
from . import base
|
from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
|
from . import mixins
|
||||||
|
|
||||||
|
|
||||||
class Audio(base.TelegramObject):
|
class Audio(base.TelegramObject, mixins.Downloadable):
|
||||||
"""
|
"""
|
||||||
This object represents an audio file to be treated as music by the Telegram clients.
|
This object represents an audio file to be treated as music by the Telegram clients.
|
||||||
|
|
||||||
|
|
@ -16,9 +17,9 @@ class Audio(base.TelegramObject):
|
||||||
file_size: base.Integer = fields.Field()
|
file_size: base.Integer = fields.Field()
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return self.file_id
|
return hash(self.file_id) + \
|
||||||
|
self.duration + \
|
||||||
def __eq__(self, other):
|
hash(self.performer) + \
|
||||||
if isinstance(other, type(self)):
|
hash(self.title) + \
|
||||||
return other.file_id == self.file_id
|
hash(self.mime_type) + \
|
||||||
return self.file_id == other
|
self.file_size
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,13 @@ from typing import TypeVar
|
||||||
|
|
||||||
from .fields import BaseField
|
from .fields import BaseField
|
||||||
from ..utils import json
|
from ..utils import json
|
||||||
from ..utils.context import get_value
|
|
||||||
|
__all__ = ('MetaTelegramObject', 'TelegramObject', 'InputFile', 'String', 'Integer', 'Float', 'Boolean')
|
||||||
|
|
||||||
PROPS_ATTR_NAME = '_props'
|
PROPS_ATTR_NAME = '_props'
|
||||||
VALUES_ATTR_NAME = '_values'
|
VALUES_ATTR_NAME = '_values'
|
||||||
ALIASES_ATTR_NAME = '_aliases'
|
ALIASES_ATTR_NAME = '_aliases'
|
||||||
|
|
||||||
__all__ = ('MetaTelegramObject', 'TelegramObject')
|
|
||||||
|
|
||||||
# Binding of builtin types
|
# Binding of builtin types
|
||||||
InputFile = TypeVar('InputFile', 'InputFile', io.BytesIO, io.FileIO, str)
|
InputFile = TypeVar('InputFile', 'InputFile', io.BytesIO, io.FileIO, str)
|
||||||
String = TypeVar('String', bound=str)
|
String = TypeVar('String', bound=str)
|
||||||
|
|
@ -187,15 +186,61 @@ class TelegramObject(metaclass=MetaTelegramObject):
|
||||||
return self.as_json()
|
return self.as_json()
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
|
"""
|
||||||
|
Item getter (by key)
|
||||||
|
|
||||||
|
:param item:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
if item in self.props:
|
if item in self.props:
|
||||||
return self.props[item].get_value(self)
|
return self.props[item].get_value(self)
|
||||||
raise KeyError(item)
|
raise KeyError(item)
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
|
"""
|
||||||
|
Item setter (by key)
|
||||||
|
|
||||||
|
:param key:
|
||||||
|
:param value:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
if key in self.props:
|
if key in self.props:
|
||||||
return self.props[key].set_value(self, value, self.conf.get('parent', None))
|
return self.props[key].set_value(self, value, self.conf.get('parent', None))
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
|
|
||||||
def __contains__(self, item):
|
def __contains__(self, item):
|
||||||
|
"""
|
||||||
|
Check key contains in that object
|
||||||
|
|
||||||
|
:param item:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
self.clean()
|
self.clean()
|
||||||
return item in self.values
|
return item in self.values
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""
|
||||||
|
Iterate over items
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
for item in self.to_python().items():
|
||||||
|
yield item
|
||||||
|
|
||||||
|
def iter_keys(self):
|
||||||
|
"""
|
||||||
|
Iterate over keys
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
for key, _ in self:
|
||||||
|
yield key
|
||||||
|
|
||||||
|
def iter_values(self):
|
||||||
|
"""
|
||||||
|
Iterate over values
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
for _, value in self:
|
||||||
|
yield value
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import typing
|
||||||
|
|
||||||
from . import base
|
from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
from .message import Message
|
from .message import Message
|
||||||
|
|
@ -26,10 +28,31 @@ class CallbackQuery(base.TelegramObject):
|
||||||
data: base.String = fields.Field()
|
data: base.String = fields.Field()
|
||||||
game_short_name: base.String = fields.Field()
|
game_short_name: base.String = fields.Field()
|
||||||
|
|
||||||
def __hash__(self):
|
async def answer(self, text: typing.Union[base.String, None] = None,
|
||||||
return self.id
|
show_alert: typing.Union[base.Boolean, None] = None,
|
||||||
|
url: typing.Union[base.String, None] = None,
|
||||||
|
cache_time: typing.Union[base.Integer, None] = None):
|
||||||
|
"""
|
||||||
|
Use this method to send answers to callback queries sent from inline keyboards.
|
||||||
|
The answer will be displayed to the user as a notification at the top of the chat screen or as an alert.
|
||||||
|
|
||||||
def __eq__(self, other):
|
Alternatively, the user can be redirected to the specified Game URL.
|
||||||
if isinstance(other, type(self)):
|
For this option to work, you must first create a game for your bot via @Botfather and accept the terms.
|
||||||
return other.id == self.id
|
Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter.
|
||||||
return self.id == other
|
|
||||||
|
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]`
|
||||||
|
: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]`
|
||||||
|
:param url: URL that will be opened by the user's client.
|
||||||
|
:type url: :obj:`typing.Union[base.String, None]`
|
||||||
|
: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]`
|
||||||
|
: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)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import typing
|
||||||
|
|
||||||
from . import base
|
from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
|
|
@ -39,7 +40,7 @@ class Chat(base.TelegramObject):
|
||||||
@property
|
@property
|
||||||
def mention(self):
|
def mention(self):
|
||||||
"""
|
"""
|
||||||
Get mention if dialog have username or full name if this is Private dialog otherwise None
|
Get mention if a Chat has a username, or get full name if this is a Private Chat, otherwise None is returned
|
||||||
"""
|
"""
|
||||||
if self.username:
|
if self.username:
|
||||||
return '@' + self.username
|
return '@' + self.username
|
||||||
|
|
@ -50,7 +51,7 @@ class Chat(base.TelegramObject):
|
||||||
@property
|
@property
|
||||||
def user_url(self):
|
def user_url(self):
|
||||||
if self.type != ChatType.PRIVATE:
|
if self.type != ChatType.PRIVATE:
|
||||||
raise TypeError('This property available only in private chats.')
|
raise TypeError('`user_url` property is only available in private chats!')
|
||||||
|
|
||||||
return f"tg://user?id={self.id}"
|
return f"tg://user?id={self.id}"
|
||||||
|
|
||||||
|
|
@ -62,45 +63,303 @@ class Chat(base.TelegramObject):
|
||||||
return markdown.link(name, self.user_url)
|
return markdown.link(name, self.user_url)
|
||||||
|
|
||||||
async def set_photo(self, photo):
|
async def set_photo(self, photo):
|
||||||
|
"""
|
||||||
|
Use this method to set a new profile photo for the chat. Photos can't be changed for private chats.
|
||||||
|
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.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#setchatphoto
|
||||||
|
|
||||||
|
:param photo: New chat photo, uploaded using multipart/form-data
|
||||||
|
:type photo: :obj:`base.InputFile`
|
||||||
|
:return: Returns True on success.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
return await self.bot.set_chat_photo(self.id, photo)
|
return await self.bot.set_chat_photo(self.id, photo)
|
||||||
|
|
||||||
async def delete_photo(self):
|
async def delete_photo(self):
|
||||||
|
"""
|
||||||
|
Use this method to delete a chat photo. Photos can't be changed for private chats.
|
||||||
|
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.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#deletechatphoto
|
||||||
|
|
||||||
|
:return: Returns True on success.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
return await self.bot.delete_chat_photo(self.id)
|
return await self.bot.delete_chat_photo(self.id)
|
||||||
|
|
||||||
async def set_title(self, title):
|
async def set_title(self, title):
|
||||||
|
"""
|
||||||
|
Use this method to change the title of a chat. Titles can't be changed for private chats.
|
||||||
|
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.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#setchattitle
|
||||||
|
|
||||||
|
:param title: New chat title, 1-255 characters
|
||||||
|
:type title: :obj:`base.String`
|
||||||
|
:return: Returns True on success.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
return await self.bot.set_chat_title(self.id, title)
|
return await self.bot.set_chat_title(self.id, title)
|
||||||
|
|
||||||
async def set_description(self, description):
|
async def set_description(self, description):
|
||||||
|
"""
|
||||||
|
Use this method to change the description of 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.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#setchatdescription
|
||||||
|
|
||||||
|
:param description: New chat description, 0-255 characters
|
||||||
|
:type description: :obj:`typing.Union[base.String, None]`
|
||||||
|
:return: Returns True on success.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
return await self.bot.delete_chat_description(self.id, description)
|
return await self.bot.delete_chat_description(self.id, description)
|
||||||
|
|
||||||
|
async def kick(self, user_id: base.Integer,
|
||||||
|
until_date: typing.Union[base.Integer, None] = None):
|
||||||
|
"""
|
||||||
|
Use this method to kick a user from a group, a supergroup or a channel.
|
||||||
|
In the case of supergroups and channels, the user will not be able to return to the group
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
|
return await self.bot.kick_chat_member(self.id, user_id=user_id, until_date=until_date)
|
||||||
|
|
||||||
|
async def unban(self, user_id: base.Integer):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#unbanchatmember
|
||||||
|
|
||||||
|
:param user_id: Unique identifier of the target user
|
||||||
|
:type user_id: :obj:`base.Integer`
|
||||||
|
:return: Returns True on success.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
|
return await self.bot.unban_chat_member(self.id, user_id=user_id)
|
||||||
|
|
||||||
|
async def restrict(self, user_id: base.Integer,
|
||||||
|
until_date: typing.Union[base.Integer, None] = None,
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
Pass True for all boolean parameters to lift restrictions from a user.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#restrictchatmember
|
||||||
|
|
||||||
|
:param user_id: Unique identifier of the target user
|
||||||
|
:type user_id: :obj:`base.Integer`
|
||||||
|
:param until_date: Date when restrictions will be lifted for the user, unix time.
|
||||||
|
:type until_date: :obj:`typing.Union[base.Integer, None]`
|
||||||
|
:param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues
|
||||||
|
:type can_send_messages: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
: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]`
|
||||||
|
: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]`
|
||||||
|
: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]`
|
||||||
|
:return: Returns True on success.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
|
return self.bot.restrict_chat_member(self.id, user_id=user_id, until_date=until_date,
|
||||||
|
can_send_messages=can_send_messages,
|
||||||
|
can_send_media_messages=can_send_media_messages,
|
||||||
|
can_send_other_messages=can_send_other_messages,
|
||||||
|
can_add_web_page_previews=can_add_web_page_previews)
|
||||||
|
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
Pass False for all boolean parameters to demote a user.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#promotechatmember
|
||||||
|
|
||||||
|
:param user_id: Unique identifier of the target user
|
||||||
|
:type user_id: :obj:`base.Integer`
|
||||||
|
: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]`
|
||||||
|
: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]`
|
||||||
|
: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]`
|
||||||
|
: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]`
|
||||||
|
: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]`
|
||||||
|
: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]`
|
||||||
|
:param can_pin_messages: Pass True, if the administrator can pin messages, supergroups only
|
||||||
|
:type can_pin_messages: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
: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]`
|
||||||
|
:return: Returns True on success.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
|
return self.bot.promote_chat_member(self.id,
|
||||||
|
can_change_info=can_change_info,
|
||||||
|
can_post_messages=can_post_messages,
|
||||||
|
can_edit_messages=can_edit_messages,
|
||||||
|
can_delete_messages=can_delete_messages,
|
||||||
|
can_invite_users=can_invite_users,
|
||||||
|
can_restrict_members=can_restrict_members,
|
||||||
|
can_pin_messages=can_pin_messages,
|
||||||
|
can_promote_members=can_promote_members)
|
||||||
|
|
||||||
async def pin_message(self, message_id: int, disable_notification: bool = False):
|
async def pin_message(self, message_id: int, disable_notification: bool = False):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
return await self.bot.pin_chat_message(self.id, message_id, disable_notification)
|
return await self.bot.pin_chat_message(self.id, message_id, disable_notification)
|
||||||
|
|
||||||
async def unpin_message(self):
|
async def unpin_message(self):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#unpinchatmessage
|
||||||
|
|
||||||
|
: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(self.id)
|
||||||
|
|
||||||
async def leave(self):
|
async def leave(self):
|
||||||
|
"""
|
||||||
|
Use this method for your bot to leave a group, supergroup or channel.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#leavechat
|
||||||
|
|
||||||
|
:return: Returns True on success.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
return await self.bot.leave_chat(self.id)
|
return await self.bot.leave_chat(self.id)
|
||||||
|
|
||||||
async def get_administrators(self):
|
async def get_administrators(self):
|
||||||
|
"""
|
||||||
|
Use this method to get a list of administrators in a chat.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#getchatadministrators
|
||||||
|
|
||||||
|
:return: On success, returns an Array of ChatMember objects that contains information about all
|
||||||
|
chat administrators except other bots.
|
||||||
|
If the chat is a group or a supergroup and no administrators were appointed,
|
||||||
|
only the creator will be returned.
|
||||||
|
:rtype: :obj:`typing.List[types.ChatMember]`
|
||||||
|
"""
|
||||||
return await self.bot.get_chat_administrators(self.id)
|
return await self.bot.get_chat_administrators(self.id)
|
||||||
|
|
||||||
async def get_members_count(self):
|
async def get_members_count(self):
|
||||||
|
"""
|
||||||
|
Use this method to get the number of members in a chat.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#getchatmemberscount
|
||||||
|
|
||||||
|
: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_members_count(self.id)
|
||||||
|
|
||||||
async def get_member(self, user_id):
|
async def get_member(self, user_id):
|
||||||
|
"""
|
||||||
|
Use this method to get information about a member of a chat.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#getchatmember
|
||||||
|
|
||||||
|
:param user_id: Unique identifier of the target user
|
||||||
|
:type user_id: :obj:`base.Integer`
|
||||||
|
:return: Returns a ChatMember object on success.
|
||||||
|
:rtype: :obj:`types.ChatMember`
|
||||||
|
"""
|
||||||
return await self.bot.get_chat_member(self.id, user_id)
|
return await self.bot.get_chat_member(self.id, user_id)
|
||||||
|
|
||||||
async def do(self, action):
|
async def do(self, action):
|
||||||
|
"""
|
||||||
|
Use this method when you need to tell the user that something is happening on the bot's side.
|
||||||
|
The status is set for 5 seconds or less
|
||||||
|
(when a message arrives from your bot, Telegram clients clear its typing status).
|
||||||
|
|
||||||
|
We only recommend using this method when a response from the bot will take
|
||||||
|
a noticeable amount of time to arrive.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendchataction
|
||||||
|
|
||||||
|
:param action: Type of action to broadcast.
|
||||||
|
:type action: :obj:`base.String`
|
||||||
|
:return: Returns True on success.
|
||||||
|
:rtype: :obj:`base.Boolean`
|
||||||
|
"""
|
||||||
return await self.bot.send_chat_action(self.id, action)
|
return await self.bot.send_chat_action(self.id, action)
|
||||||
|
|
||||||
def __hash__(self):
|
async def export_invite_link(self):
|
||||||
return self.id
|
"""
|
||||||
|
Use this method to export an invite link to 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.
|
||||||
|
|
||||||
def __eq__(self, other):
|
Source: https://core.telegram.org/bots/api#exportchatinvitelink
|
||||||
if isinstance(other, type(self)):
|
|
||||||
return other.id == self.id
|
:return: Returns exported invite link as String on success.
|
||||||
return self.id == other
|
:rtype: :obj:`base.String`
|
||||||
|
"""
|
||||||
|
if self.invite_link:
|
||||||
|
return self.invite_link
|
||||||
|
return await self.bot.export_chat_invite_link(self.id)
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
|
@ -125,9 +384,11 @@ class ChatType(helper.Helper):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check(obj, chat_types) -> bool:
|
def _check(obj, chat_types) -> bool:
|
||||||
if not hasattr(obj, 'chat'):
|
if hasattr(obj, 'chat'):
|
||||||
|
obj = obj.chat
|
||||||
|
if not hasattr(obj, 'type'):
|
||||||
return False
|
return False
|
||||||
return obj.chat.type in chat_types
|
return obj.type in chat_types
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_private(cls, obj) -> bool:
|
def is_private(cls, obj) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,11 @@ class ChatMember(base.TelegramObject):
|
||||||
can_send_other_messages: base.Boolean = fields.Field()
|
can_send_other_messages: base.Boolean = fields.Field()
|
||||||
can_add_web_page_previews: base.Boolean = fields.Field()
|
can_add_web_page_previews: base.Boolean = fields.Field()
|
||||||
|
|
||||||
def __hash__(self):
|
def is_admin(self):
|
||||||
return self.user.id
|
return ChatMemberStatus.is_admin(self.status)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def is_member(self):
|
||||||
if isinstance(other, type(self)):
|
return ChatMemberStatus.is_member(self.status)
|
||||||
return other.user.id == self.user.id
|
|
||||||
return self.user.id == other
|
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.user.id
|
return self.user.id
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
|
||||||
from . import base
|
from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
|
|
||||||
|
|
@ -10,3 +13,63 @@ class ChatPhoto(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
small_file_id: base.String = fields.Field()
|
small_file_id: base.String = fields.Field()
|
||||||
big_file_id: base.String = fields.Field()
|
big_file_id: base.String = fields.Field()
|
||||||
|
|
||||||
|
async def download_small(self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True):
|
||||||
|
"""
|
||||||
|
Download file
|
||||||
|
|
||||||
|
:param destination: filename or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO`
|
||||||
|
:param timeout: Integer
|
||||||
|
:param chunk_size: Integer
|
||||||
|
:param seek: Boolean - go to start of file when downloading is finished.
|
||||||
|
:param make_dirs: Make dirs if not exist
|
||||||
|
:return: destination
|
||||||
|
"""
|
||||||
|
file = await self.get_small_file()
|
||||||
|
|
||||||
|
is_path = True
|
||||||
|
if destination is None:
|
||||||
|
destination = file.file_path
|
||||||
|
elif isinstance(destination, (str, pathlib.Path)) and os.path.isdir(destination):
|
||||||
|
os.path.join(destination, file.file_path)
|
||||||
|
else:
|
||||||
|
is_path = False
|
||||||
|
|
||||||
|
if is_path and make_dirs:
|
||||||
|
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
||||||
|
|
||||||
|
return await self.bot.download_file(file_path=file.file_path, destination=destination, timeout=timeout,
|
||||||
|
chunk_size=chunk_size, seek=seek)
|
||||||
|
|
||||||
|
async def download_big(self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True):
|
||||||
|
"""
|
||||||
|
Download file
|
||||||
|
|
||||||
|
:param destination: filename or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO`
|
||||||
|
:param timeout: Integer
|
||||||
|
:param chunk_size: Integer
|
||||||
|
:param seek: Boolean - go to start of file when downloading is finished.
|
||||||
|
:param make_dirs: Make dirs if not exist
|
||||||
|
:return: destination
|
||||||
|
"""
|
||||||
|
file = await self.get_big_file()
|
||||||
|
|
||||||
|
is_path = True
|
||||||
|
if destination is None:
|
||||||
|
destination = file.file_path
|
||||||
|
elif isinstance(destination, (str, pathlib.Path)) and os.path.isdir(destination):
|
||||||
|
os.path.join(destination, file.file_path)
|
||||||
|
else:
|
||||||
|
is_path = False
|
||||||
|
|
||||||
|
if is_path and make_dirs:
|
||||||
|
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
||||||
|
|
||||||
|
return await self.bot.download_file(file_path=file.file_path, destination=destination, timeout=timeout,
|
||||||
|
chunk_size=chunk_size, seek=seek)
|
||||||
|
|
||||||
|
async def get_small_file(self):
|
||||||
|
return await self.bot.get_file(self.small_file_id)
|
||||||
|
|
||||||
|
async def get_big_file(self):
|
||||||
|
return await self.bot.get_file(self.big_file_id)
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,10 @@ class Contact(base.TelegramObject):
|
||||||
first_name: base.String = fields.Field()
|
first_name: base.String = fields.Field()
|
||||||
last_name: base.String = fields.Field()
|
last_name: base.String = fields.Field()
|
||||||
user_id: base.Integer = fields.Field()
|
user_id: base.Integer = fields.Field()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self):
|
||||||
|
name = self.first_name
|
||||||
|
if self.last_name is not None:
|
||||||
|
name += ' ' + self.last_name
|
||||||
|
return name
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
from . import base
|
from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
|
from . import mixins
|
||||||
from .photo_size import PhotoSize
|
from .photo_size import PhotoSize
|
||||||
|
|
||||||
|
|
||||||
class Document(base.TelegramObject):
|
class Document(base.TelegramObject, mixins.Downloadable):
|
||||||
"""
|
"""
|
||||||
This object represents a general file (as opposed to photos, voice messages and audio files).
|
This object represents a general file (as opposed to photos, voice messages and audio files).
|
||||||
|
|
||||||
|
|
@ -14,11 +15,3 @@ class Document(base.TelegramObject):
|
||||||
file_name: base.String = fields.Field()
|
file_name: base.String = fields.Field()
|
||||||
mime_type: base.String = fields.Field()
|
mime_type: base.String = fields.Field()
|
||||||
file_size: base.Integer = fields.Field()
|
file_size: base.Integer = fields.Field()
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return self.file_id
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, type(self)):
|
|
||||||
return other.file_id == self.file_id
|
|
||||||
return self.file_id == other
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ class BaseField(metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
:param base: class for child element
|
:param base: class for child element
|
||||||
:param default: default value
|
:param default: default value
|
||||||
:param alias: alias name (for e.g. field named 'from' must be has name 'from_user'
|
:param alias: alias name (for e.g. field 'from' has to be named 'from_user'
|
||||||
('from' is builtin Python keyword)
|
as 'from' is a builtin Python keyword
|
||||||
"""
|
"""
|
||||||
self.base_object = base
|
self.base_object = base
|
||||||
self.default = default
|
self.default = default
|
||||||
|
|
@ -34,7 +34,7 @@ class BaseField(metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
def get_value(self, instance):
|
def get_value(self, instance):
|
||||||
"""
|
"""
|
||||||
Get value for current object instance
|
Get value for the current object instance
|
||||||
|
|
||||||
:param instance:
|
:param instance:
|
||||||
:return:
|
:return:
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
from . import base
|
from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
|
from . import mixins
|
||||||
|
|
||||||
|
|
||||||
class File(base.TelegramObject):
|
class File(base.TelegramObject, mixins.Downloadable):
|
||||||
"""
|
"""
|
||||||
This object represents a file ready to be downloaded.
|
This object represents a file ready to be downloaded.
|
||||||
|
|
||||||
|
|
@ -18,23 +19,3 @@ class File(base.TelegramObject):
|
||||||
file_id: base.String = fields.Field()
|
file_id: base.String = fields.Field()
|
||||||
file_size: base.Integer = fields.Field()
|
file_size: base.Integer = fields.Field()
|
||||||
file_path: base.String = fields.Field()
|
file_path: base.String = fields.Field()
|
||||||
|
|
||||||
async def download(self, destination=None, timeout=30, chunk_size=65536, seek=True):
|
|
||||||
"""
|
|
||||||
Download file by file_path to destination
|
|
||||||
|
|
||||||
:param destination: filename or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO`
|
|
||||||
:param timeout: Integer
|
|
||||||
:param chunk_size: Integer
|
|
||||||
:param seek: Boolean - go to start of file when downloading is finished.
|
|
||||||
:return: destination
|
|
||||||
"""
|
|
||||||
return await self.bot.download_file(self.file_path, destination, timeout, chunk_size, seek)
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return self.file_id
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, type(self)):
|
|
||||||
return other.file_id == self.file_id
|
|
||||||
return self.file_id == other
|
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,21 @@ class InlineKeyboardMarkup(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
btn_array = []
|
btn_array = []
|
||||||
for button in args:
|
for button in args:
|
||||||
btn_array.append(button.to_json())
|
btn_array.append(button)
|
||||||
self.inline_keyboard.append(btn_array)
|
self.inline_keyboard.append(btn_array)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def insert(self, button):
|
||||||
|
"""
|
||||||
|
Insert button to last row
|
||||||
|
|
||||||
|
:param button:
|
||||||
|
"""
|
||||||
|
if self.inline_keyboard and len(self.inline_keyboard[-1] < self.row_width):
|
||||||
|
self.inline_keyboard[-1].append(button)
|
||||||
|
else:
|
||||||
|
self.add(button)
|
||||||
|
|
||||||
|
|
||||||
class InlineKeyboardButton(base.TelegramObject):
|
class InlineKeyboardButton(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,3 @@ class InlineQuery(base.TelegramObject):
|
||||||
location: Location = fields.Field(base=Location)
|
location: Location = fields.Field(base=Location)
|
||||||
query: base.String = fields.Field()
|
query: base.String = fields.Field()
|
||||||
offset: base.String = fields.Field()
|
offset: base.String = fields.Field()
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return self.id
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, type(self)):
|
|
||||||
return other.id == self.id
|
|
||||||
return self.id == other
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
|
||||||
import time
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
from . import base
|
from . import base
|
||||||
from ..bot import api
|
from ..bot import api
|
||||||
|
|
@ -36,11 +32,11 @@ class InputFile(base.TelegramObject):
|
||||||
self._path = path_or_bytesio
|
self._path = path_or_bytesio
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = os.path.split(path_or_bytesio)[-1]
|
filename = os.path.split(path_or_bytesio)[-1]
|
||||||
else:
|
elif isinstance(path_or_bytesio, io.IOBase):
|
||||||
# As io.BytesIO
|
|
||||||
assert isinstance(path_or_bytesio, io.IOBase)
|
|
||||||
self._path = None
|
self._path = None
|
||||||
self._file = path_or_bytesio
|
self._file = path_or_bytesio
|
||||||
|
else:
|
||||||
|
raise TypeError('Not supported file type.')
|
||||||
|
|
||||||
self._filename = filename
|
self._filename = filename
|
||||||
|
|
||||||
|
|
@ -48,14 +44,17 @@ class InputFile(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
Close file descriptor
|
Close file descriptor
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, '_file'):
|
|
||||||
return
|
|
||||||
self._file.close()
|
self._file.close()
|
||||||
del self._file
|
|
||||||
|
|
||||||
if self.conf.get('downloaded') and self.conf.get('temp'):
|
@property
|
||||||
log.debug(f"Unlink file '{self._path}'")
|
def filename(self):
|
||||||
os.unlink(self._path)
|
if self._filename is None:
|
||||||
|
self._filename = api._guess_filename(self._file)
|
||||||
|
return self._filename
|
||||||
|
|
||||||
|
@filename.setter
|
||||||
|
def filename(self, value):
|
||||||
|
self._filename = value
|
||||||
|
|
||||||
def get_filename(self) -> str:
|
def get_filename(self) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
@ -63,9 +62,11 @@ class InputFile(base.TelegramObject):
|
||||||
|
|
||||||
:return: name
|
:return: name
|
||||||
"""
|
"""
|
||||||
if self._filename is None:
|
return self.filename
|
||||||
self._filename = api._guess_filename(self._file)
|
|
||||||
return self._filename
|
@property
|
||||||
|
def file(self):
|
||||||
|
return self._file
|
||||||
|
|
||||||
def get_file(self):
|
def get_file(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -73,74 +74,7 @@ class InputFile(base.TelegramObject):
|
||||||
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
return self._file
|
return self.file
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def from_url(cls, url, filename=None, temp_file=False, chunk_size=65536):
|
|
||||||
"""
|
|
||||||
Download file from URL
|
|
||||||
|
|
||||||
Manually is not required action. You can send urls instead!
|
|
||||||
|
|
||||||
:param url: target URL
|
|
||||||
:param filename: optional. set custom file name
|
|
||||||
:param temp_file: use temporary file
|
|
||||||
:param chunk_size:
|
|
||||||
|
|
||||||
:return: InputFile
|
|
||||||
"""
|
|
||||||
conf = {
|
|
||||||
'downloaded': True,
|
|
||||||
'url': url
|
|
||||||
}
|
|
||||||
|
|
||||||
# Let's do magic with the filename
|
|
||||||
if filename:
|
|
||||||
filename_prefix, _, ext = filename.rpartition('.')
|
|
||||||
file_suffix = '.' + ext if ext else ''
|
|
||||||
else:
|
|
||||||
filename_prefix, _, ext = url.rpartition('/')[-1].rpartition('.')
|
|
||||||
file_suffix = '.' + ext if ext else ''
|
|
||||||
filename = filename_prefix + file_suffix
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
start = time.time()
|
|
||||||
async with session.get(url) as response:
|
|
||||||
if temp_file:
|
|
||||||
# Create temp file
|
|
||||||
fd, path = tempfile.mkstemp(suffix=file_suffix, prefix=filename_prefix + '_')
|
|
||||||
file = conf['temp'] = path
|
|
||||||
|
|
||||||
# Save file in temp directory
|
|
||||||
with open(fd, 'wb') as f:
|
|
||||||
await cls._process_stream(response, f, chunk_size=chunk_size)
|
|
||||||
else:
|
|
||||||
# Save file in memory
|
|
||||||
file = await cls._process_stream(response, io.BytesIO(), chunk_size=chunk_size)
|
|
||||||
|
|
||||||
log.debug(f"File successful downloaded at {round(time.time() - start, 2)} seconds from '{url}'")
|
|
||||||
return cls(file, filename, conf=conf)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def _process_stream(cls, response, writer, chunk_size=65536):
|
|
||||||
"""
|
|
||||||
Transfer data
|
|
||||||
|
|
||||||
:param response:
|
|
||||||
:param writer:
|
|
||||||
:param chunk_size:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
chunk = await response.content.read(chunk_size)
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
writer.write(chunk)
|
|
||||||
|
|
||||||
if writer.seekable():
|
|
||||||
writer.seek(0)
|
|
||||||
|
|
||||||
return writer
|
|
||||||
|
|
||||||
def to_python(self):
|
def to_python(self):
|
||||||
raise TypeError('Object of this type is not exportable!')
|
raise TypeError('Object of this type is not exportable!')
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,15 @@ class InputMedia(base.TelegramObject):
|
||||||
type: base.String = fields.Field(default='photo')
|
type: base.String = fields.Field(default='photo')
|
||||||
media: base.String = fields.Field()
|
media: base.String = fields.Field()
|
||||||
caption: base.String = fields.Field()
|
caption: base.String = fields.Field()
|
||||||
|
parse_mode: base.Boolean = fields.Field()
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(InputMedia, self).__init__(*args, **kwargs)
|
||||||
|
try:
|
||||||
|
if self.parse_mode is None and self.bot.parse_mode:
|
||||||
|
self.parse_mode = self.bot.parse_mode
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def file(self):
|
def file(self):
|
||||||
|
|
@ -30,13 +39,16 @@ class InputMedia(base.TelegramObject):
|
||||||
@file.setter
|
@file.setter
|
||||||
def file(self, file: io.IOBase):
|
def file(self, file: io.IOBase):
|
||||||
setattr(self, '_file', file)
|
setattr(self, '_file', file)
|
||||||
self.media = ATTACHMENT_PREFIX + secrets.token_urlsafe(16)
|
attachment_key = self.attachment_key = secrets.token_urlsafe(16)
|
||||||
|
self.media = ATTACHMENT_PREFIX + attachment_key
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attachment_key(self):
|
def attachment_key(self):
|
||||||
if self.media.startswith(ATTACHMENT_PREFIX):
|
return self.conf.get('attachment_key', None)
|
||||||
return self.media[len(ATTACHMENT_PREFIX):]
|
|
||||||
return None
|
@attachment_key.setter
|
||||||
|
def attachment_key(self, value):
|
||||||
|
self.conf['attachment_key'] = value
|
||||||
|
|
||||||
|
|
||||||
class InputMediaPhoto(InputMedia):
|
class InputMediaPhoto(InputMedia):
|
||||||
|
|
@ -46,8 +58,9 @@ class InputMediaPhoto(InputMedia):
|
||||||
https://core.telegram.org/bots/api#inputmediaphoto
|
https://core.telegram.org/bots/api#inputmediaphoto
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, media: base.InputFile, caption: base.String = None):
|
def __init__(self, media: base.InputFile, caption: base.String = None, parse_mode: base.Boolean = None, **kwargs):
|
||||||
super(InputMediaPhoto, self).__init__(type='photo', media=media, caption=caption)
|
super(InputMediaPhoto, self).__init__(type='photo', media=media, caption=caption, parse_mode=parse_mode,
|
||||||
|
conf=kwargs)
|
||||||
|
|
||||||
if isinstance(media, (io.IOBase, InputFile)):
|
if isinstance(media, (io.IOBase, InputFile)):
|
||||||
self.file = media
|
self.file = media
|
||||||
|
|
@ -62,11 +75,16 @@ class InputMediaVideo(InputMedia):
|
||||||
width: base.Integer = fields.Field()
|
width: base.Integer = fields.Field()
|
||||||
height: base.Integer = fields.Field()
|
height: base.Integer = fields.Field()
|
||||||
duration: base.Integer = fields.Field()
|
duration: base.Integer = fields.Field()
|
||||||
|
supports_streaming: base.Boolean = fields.Field()
|
||||||
|
|
||||||
def __init__(self, media: base.InputFile, caption: base.String = None,
|
def __init__(self, media: base.InputFile, 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.Boolean = None,
|
||||||
|
supports_streaming: base.Boolean = None, **kwargs):
|
||||||
super(InputMediaVideo, self).__init__(type='video', media=media, caption=caption,
|
super(InputMediaVideo, self).__init__(type='video', media=media, caption=caption,
|
||||||
width=width, height=height, duration=duration)
|
width=width, height=height, duration=duration,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
supports_streaming=supports_streaming, conf=kwargs)
|
||||||
|
|
||||||
if isinstance(media, (io.IOBase, InputFile)):
|
if isinstance(media, (io.IOBase, InputFile)):
|
||||||
self.file = media
|
self.file = media
|
||||||
|
|
@ -82,7 +100,7 @@ class MediaGroup(base.TelegramObject):
|
||||||
self.media = []
|
self.media = []
|
||||||
|
|
||||||
if medias:
|
if medias:
|
||||||
self.attach_many(medias)
|
self.attach_many(*medias)
|
||||||
|
|
||||||
def attach_many(self, *medias: typing.Union[InputMedia, typing.Dict]):
|
def attach_many(self, *medias: typing.Union[InputMedia, typing.Dict]):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ from .video import Video
|
||||||
from .video_note import VideoNote
|
from .video_note import VideoNote
|
||||||
from .voice import Voice
|
from .voice import Voice
|
||||||
from ..utils import helper
|
from ..utils import helper
|
||||||
from ..utils.payload import generate_payload
|
|
||||||
|
|
||||||
|
|
||||||
class Message(base.TelegramObject):
|
class Message(base.TelegramObject):
|
||||||
|
|
@ -71,6 +70,7 @@ class Message(base.TelegramObject):
|
||||||
pinned_message: 'Message' = fields.Field(base='Message')
|
pinned_message: 'Message' = fields.Field(base='Message')
|
||||||
invoice: Invoice = fields.Field(base=Invoice)
|
invoice: Invoice = fields.Field(base=Invoice)
|
||||||
successful_payment: SuccessfulPayment = fields.Field(base=SuccessfulPayment)
|
successful_payment: SuccessfulPayment = fields.Field(base=SuccessfulPayment)
|
||||||
|
connected_website: base.String = fields.Field()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
|
|
@ -101,6 +101,8 @@ class Message(base.TelegramObject):
|
||||||
return ContentType.VENUE[0]
|
return ContentType.VENUE[0]
|
||||||
if self.new_chat_members:
|
if self.new_chat_members:
|
||||||
return ContentType.NEW_CHAT_MEMBERS[0]
|
return ContentType.NEW_CHAT_MEMBERS[0]
|
||||||
|
if self.left_chat_member:
|
||||||
|
return ContentType.LEFT_CHAT_MEMBER[0]
|
||||||
if self.invoice:
|
if self.invoice:
|
||||||
return ContentType.INVOICE[0]
|
return ContentType.INVOICE[0]
|
||||||
if self.successful_payment:
|
if self.successful_payment:
|
||||||
|
|
@ -177,7 +179,7 @@ class Message(base.TelegramObject):
|
||||||
return text
|
return text
|
||||||
|
|
||||||
async def reply(self, text, parse_mode=None, disable_web_page_preview=None,
|
async def reply(self, text, parse_mode=None, disable_web_page_preview=None,
|
||||||
disable_notification=None, reply_markup=None) -> 'Message':
|
disable_notification=None, reply_markup=None, reply=False) -> 'Message':
|
||||||
"""
|
"""
|
||||||
Reply to this message
|
Reply to this message
|
||||||
|
|
||||||
|
|
@ -186,10 +188,399 @@ class Message(base.TelegramObject):
|
||||||
:param disable_web_page_preview: bool
|
:param disable_web_page_preview: bool
|
||||||
:param disable_notification: bool
|
:param disable_notification: bool
|
||||||
:param reply_markup:
|
:param reply_markup:
|
||||||
:return: :class:`aoigram.types.Message`
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: :class:`aiogram.types.Message`
|
||||||
"""
|
"""
|
||||||
return await self.bot.send_message(self.chat.id, text, parse_mode, disable_web_page_preview,
|
return await self.bot.send_message(chat_id=self.chat.id, text=text,
|
||||||
disable_notification, self.message_id, reply_markup)
|
parse_mode=parse_mode,
|
||||||
|
disable_web_page_preview=disable_web_page_preview,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def reply_photo(self, photo: typing.Union[base.InputFile, base.String],
|
||||||
|
caption: typing.Union[base.String, None] = None,
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply_markup=None, reply=True) -> 'Message':
|
||||||
|
"""
|
||||||
|
Use this method to send photos.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendphoto
|
||||||
|
|
||||||
|
:param photo: Photo to send.
|
||||||
|
:type photo: :obj:`typing.Union[base.InputFile, base.String]`
|
||||||
|
:param caption: Photo caption (may also be used when resending photos by file_id), 0-200 characters
|
||||||
|
:type caption: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||||
|
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
:param reply_markup: Additional interface options.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||||
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: On success, the sent Message is returned.
|
||||||
|
:rtype: :obj:`types.Message`
|
||||||
|
"""
|
||||||
|
return await self.bot.send_photo(chat_id=self.chat.id, photo=photo, caption=caption,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def reply_audio(self, audio: typing.Union[base.InputFile, base.String],
|
||||||
|
caption: typing.Union[base.String, None] = None,
|
||||||
|
duration: typing.Union[base.Integer, None] = None,
|
||||||
|
performer: typing.Union[base.String, None] = None,
|
||||||
|
title: typing.Union[base.String, None] = None,
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply_markup=None,
|
||||||
|
reply=True) -> 'Message':
|
||||||
|
"""
|
||||||
|
Use this method to send audio files, if you want Telegram clients to display them in the music player.
|
||||||
|
Your audio must be in the .mp3 format.
|
||||||
|
|
||||||
|
For sending voice messages, use the sendVoice method instead.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendaudio
|
||||||
|
|
||||||
|
:param audio: Audio file to send.
|
||||||
|
:type audio: :obj:`typing.Union[base.InputFile, base.String]`
|
||||||
|
:param caption: Audio caption, 0-200 characters
|
||||||
|
:type caption: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param duration: Duration of the audio in seconds
|
||||||
|
:type duration: :obj:`typing.Union[base.Integer, None]`
|
||||||
|
:param performer: Performer
|
||||||
|
:type performer: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param title: Track name
|
||||||
|
:type title: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||||
|
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
:param reply_markup: Additional interface options.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||||
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: On success, the sent Message is returned.
|
||||||
|
:rtype: :obj:`types.Message`
|
||||||
|
"""
|
||||||
|
return await self.bot.send_audio(chat_id=self.chat.id,
|
||||||
|
audio=audio,
|
||||||
|
caption=caption,
|
||||||
|
duration=duration,
|
||||||
|
performer=performer,
|
||||||
|
title=title,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def reply_document(self, document: typing.Union[base.InputFile, base.String],
|
||||||
|
caption: typing.Union[base.String, None] = None,
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply_markup=None,
|
||||||
|
reply=True) -> 'Message':
|
||||||
|
"""
|
||||||
|
Use this method to send general files.
|
||||||
|
|
||||||
|
Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#senddocument
|
||||||
|
|
||||||
|
:param document: File to send.
|
||||||
|
:type document: :obj:`typing.Union[base.InputFile, base.String]`
|
||||||
|
:param caption: Document caption (may also be used when resending documents by file_id), 0-200 characters
|
||||||
|
:type caption: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||||
|
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
:param reply_markup: Additional interface options.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply], None]`
|
||||||
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: On success, the sent Message is returned.
|
||||||
|
:rtype: :obj:`types.Message`
|
||||||
|
"""
|
||||||
|
return await self.bot.send_document(chat_id=self.chat.id,
|
||||||
|
document=document,
|
||||||
|
caption=caption,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def reply_video(self, video: typing.Union[base.InputFile, base.String],
|
||||||
|
duration: typing.Union[base.Integer, None] = None,
|
||||||
|
width: typing.Union[base.Integer, None] = None,
|
||||||
|
height: typing.Union[base.Integer, None] = None,
|
||||||
|
caption: typing.Union[base.String, None] = None,
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply_markup=None,
|
||||||
|
reply=True) -> 'Message':
|
||||||
|
"""
|
||||||
|
Use this method to send video files, Telegram clients support mp4 videos
|
||||||
|
(other formats may be sent as Document).
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendvideo
|
||||||
|
|
||||||
|
:param video: Video to send.
|
||||||
|
:type video: :obj:`typing.Union[base.InputFile, base.String]`
|
||||||
|
:param duration: Duration of sent video in seconds
|
||||||
|
:type duration: :obj:`typing.Union[base.Integer, None]`
|
||||||
|
:param width: Video width
|
||||||
|
:type width: :obj:`typing.Union[base.Integer, None]`
|
||||||
|
:param height: Video height
|
||||||
|
:type height: :obj:`typing.Union[base.Integer, None]`
|
||||||
|
:param caption: Video caption (may also be used when resending videos by file_id), 0-200 characters
|
||||||
|
:type caption: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||||
|
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
:param reply_markup: Additional interface options.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||||
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: On success, the sent Message is returned.
|
||||||
|
:rtype: :obj:`types.Message`
|
||||||
|
"""
|
||||||
|
return await self.bot.send_video(chat_id=self.chat.id,
|
||||||
|
video=video,
|
||||||
|
duration=duration,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
caption=caption,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def reply_voice(self, voice: typing.Union[base.InputFile, base.String],
|
||||||
|
caption: typing.Union[base.String, None] = None,
|
||||||
|
duration: typing.Union[base.Integer, None] = None,
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply_markup=None,
|
||||||
|
reply=True) -> 'Message':
|
||||||
|
"""
|
||||||
|
Use this method to send audio files, if you want Telegram clients to display the file
|
||||||
|
as a playable voice message.
|
||||||
|
|
||||||
|
For this to work, your audio must be in an .ogg file encoded with OPUS
|
||||||
|
(other formats may be sent as Audio or Document).
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendvoice
|
||||||
|
|
||||||
|
:param voice: Audio file to send.
|
||||||
|
:type voice: :obj:`typing.Union[base.InputFile, base.String]`
|
||||||
|
:param caption: Voice message caption, 0-200 characters
|
||||||
|
:type caption: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param duration: Duration of the voice message in seconds
|
||||||
|
:type duration: :obj:`typing.Union[base.Integer, None]`
|
||||||
|
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||||
|
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
:param reply_markup: Additional interface options.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||||
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: On success, the sent Message is returned.
|
||||||
|
:rtype: :obj:`types.Message`
|
||||||
|
"""
|
||||||
|
return await self.bot.send_voice(chat_id=self.chat.id,
|
||||||
|
voice=voice,
|
||||||
|
caption=caption,
|
||||||
|
duration=duration,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def reply_video_note(self, video_note: typing.Union[base.InputFile, base.String],
|
||||||
|
duration: typing.Union[base.Integer, None] = None,
|
||||||
|
length: typing.Union[base.Integer, None] = None,
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply_markup=None,
|
||||||
|
reply=True) -> 'Message':
|
||||||
|
"""
|
||||||
|
As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long.
|
||||||
|
Use this method to send video messages.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendvideonote
|
||||||
|
|
||||||
|
:param video_note: Video note to send.
|
||||||
|
:type video_note: :obj:`typing.Union[base.InputFile, base.String]`
|
||||||
|
:param duration: Duration of sent video in seconds
|
||||||
|
:type duration: :obj:`typing.Union[base.Integer, None]`
|
||||||
|
:param length: Video width and height
|
||||||
|
:type length: :obj:`typing.Union[base.Integer, None]`
|
||||||
|
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||||
|
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
:param reply_markup: Additional interface options.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||||
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: On success, the sent Message is returned.
|
||||||
|
:rtype: :obj:`types.Message`
|
||||||
|
"""
|
||||||
|
return await self.bot.send_video_note(chat_id=self.chat.id,
|
||||||
|
video_note=video_note,
|
||||||
|
duration=duration,
|
||||||
|
length=length,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def reply_media_group(self, media: typing.Union['MediaGroup', typing.List],
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply=True) -> typing.List['Message']:
|
||||||
|
"""
|
||||||
|
Use this method to send a group of photos or videos as an album.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendmediagroup
|
||||||
|
|
||||||
|
: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]`
|
||||||
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: On success, an array of the sent Messages is returned.
|
||||||
|
:rtype: typing.List[types.Message]
|
||||||
|
"""
|
||||||
|
return await self.bot.send_media_group(self.chat.id,
|
||||||
|
media=media,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None)
|
||||||
|
|
||||||
|
async def reply_location(self, latitude: base.Float,
|
||||||
|
longitude: base.Float, live_period: typing.Union[base.Integer, None] = None,
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply_markup=None,
|
||||||
|
reply=True) -> 'Message':
|
||||||
|
"""
|
||||||
|
Use this method to send point on the map.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendlocation
|
||||||
|
|
||||||
|
:param latitude: Latitude of the location
|
||||||
|
:type latitude: :obj:`base.Float`
|
||||||
|
:param longitude: Longitude of the location
|
||||||
|
:type longitude: :obj:`base.Float`
|
||||||
|
:param live_period: Period in seconds for which the location will be updated
|
||||||
|
:type live_period: :obj:`typing.Union[base.Integer, None]`
|
||||||
|
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||||
|
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
:param reply_markup: Additional interface options.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||||
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: On success, the sent Message is returned.
|
||||||
|
:rtype: :obj:`types.Message`
|
||||||
|
"""
|
||||||
|
return await self.bot.send_location(chat_id=self.chat.id,
|
||||||
|
latitude=latitude,
|
||||||
|
longitude=longitude,
|
||||||
|
live_period=live_period,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def edit_live_location(self, latitude: base.Float, longitude: base.Float,
|
||||||
|
reply_markup=None) -> 'Message' or base.Boolean:
|
||||||
|
"""
|
||||||
|
Use this method to edit live location messages sent by the bot or via the bot (for inline bots).
|
||||||
|
A location can be edited until its live_period expires or editing is explicitly disabled by a call
|
||||||
|
to stopMessageLiveLocation.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#editmessagelivelocation
|
||||||
|
|
||||||
|
:param latitude: Latitude of new location
|
||||||
|
:type latitude: :obj:`base.Float`
|
||||||
|
:param longitude: Longitude of new location
|
||||||
|
:type longitude: :obj:`base.Float`
|
||||||
|
:param reply_markup: A JSON-serialized object for a new inline keyboard.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
|
||||||
|
:return: On success, if the edited message was sent by the bot, the edited Message is returned,
|
||||||
|
otherwise True is returned.
|
||||||
|
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
|
||||||
|
"""
|
||||||
|
return await self.bot.edit_message_live_location(latitude=latitude, longitude=longitude,
|
||||||
|
chat_id=self.chat.id, message_id=self.message_id,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def stop_live_location(self, reply_markup=None) -> 'Message' or base.Boolean:
|
||||||
|
"""
|
||||||
|
Use this method to stop updating a live location message sent by the bot or via the bot
|
||||||
|
(for inline bots) before live_period expires.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#stopmessagelivelocation
|
||||||
|
|
||||||
|
:param reply_markup: A JSON-serialized object for a new inline keyboard.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
|
||||||
|
:return: On success, if the message was sent by the bot, the sent Message is returned,
|
||||||
|
otherwise True is returned.
|
||||||
|
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
|
||||||
|
"""
|
||||||
|
return await self.bot.stop_message_live_location(chat_id=self.chat.id, message_id=self.message_id,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def send_venue(self, latitude: base.Float, longitude: base.Float, title: base.String, address: base.String,
|
||||||
|
foursquare_id: typing.Union[base.String, None] = None,
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply_markup=None,
|
||||||
|
reply=True) -> 'Message':
|
||||||
|
"""
|
||||||
|
Use this method to send information about a venue.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendvenue
|
||||||
|
|
||||||
|
:param latitude: Latitude of the venue
|
||||||
|
:type latitude: :obj:`base.Float`
|
||||||
|
:param longitude: Longitude of the venue
|
||||||
|
:type longitude: :obj:`base.Float`
|
||||||
|
:param title: Name of the venue
|
||||||
|
:type title: :obj:`base.String`
|
||||||
|
:param address: Address of the venue
|
||||||
|
:type address: :obj:`base.String`
|
||||||
|
:param foursquare_id: Foursquare identifier of the venue
|
||||||
|
:type foursquare_id: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||||
|
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
:param reply_markup: Additional interface options.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||||
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: On success, the sent Message is returned.
|
||||||
|
:rtype: :obj:`types.Message`
|
||||||
|
"""
|
||||||
|
return await self.bot.send_venue(chat_id=self.chat.id,
|
||||||
|
latitude=latitude,
|
||||||
|
longitude=longitude,
|
||||||
|
title=title,
|
||||||
|
address=address,
|
||||||
|
foursquare_id=foursquare_id,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def send_contact(self, phone_number: base.String,
|
||||||
|
first_name: base.String, last_name: typing.Union[base.String, None] = None,
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply_markup=None,
|
||||||
|
reply=True) -> 'Message':
|
||||||
|
"""
|
||||||
|
Use this method to send phone contacts.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendcontact
|
||||||
|
|
||||||
|
:param phone_number: Contact's phone number
|
||||||
|
:type phone_number: :obj:`base.String`
|
||||||
|
:param first_name: Contact's first name
|
||||||
|
:type first_name: :obj:`base.String`
|
||||||
|
:param last_name: Contact's last name
|
||||||
|
:type last_name: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||||
|
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
:param reply_markup: Additional interface options.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||||
|
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||||
|
:param reply: fill 'reply_to_message_id'
|
||||||
|
:return: On success, the sent Message is returned.
|
||||||
|
:rtype: :obj:`types.Message`
|
||||||
|
"""
|
||||||
|
return await self.bot.send_contact(chat_id=self.chat.id,
|
||||||
|
phone_number=phone_number,
|
||||||
|
first_name=first_name, last_name=last_name,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
async def forward(self, chat_id, disable_notification=None) -> 'Message':
|
async def forward(self, chat_id, disable_notification=None) -> 'Message':
|
||||||
"""
|
"""
|
||||||
|
|
@ -204,12 +595,30 @@ class Message(base.TelegramObject):
|
||||||
async def edit_text(self, text: base.String,
|
async def edit_text(self, text: base.String,
|
||||||
parse_mode: typing.Union[base.String, None] = None,
|
parse_mode: typing.Union[base.String, None] = None,
|
||||||
disable_web_page_preview: typing.Union[base.Boolean, None] = None,
|
disable_web_page_preview: typing.Union[base.Boolean, None] = None,
|
||||||
reply_markup: typing.Union['types.InlineKeyboardMarkup',
|
reply_markup=None):
|
||||||
None] = None):
|
"""
|
||||||
payload = generate_payload(**locals())
|
Use this method to edit text and game messages sent by the bot or via the bot (for inline bots).
|
||||||
payload['message_id'] = self.message_id
|
|
||||||
payload['chat_id'] = self.chat.id
|
Source: https://core.telegram.org/bots/api#editmessagetext
|
||||||
return await self.bot.edit_message_text(**payload)
|
|
||||||
|
:param text: New text of the message
|
||||||
|
:type text: :obj:`base.String`
|
||||||
|
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
|
||||||
|
fixed-width text or inline URLs in your bot's message.
|
||||||
|
:type parse_mode: :obj:`typing.Union[base.String, None]`
|
||||||
|
:param disable_web_page_preview: Disables link previews for links in this message
|
||||||
|
:type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]`
|
||||||
|
:param reply_markup: A JSON-serialized object for an inline keyboard.
|
||||||
|
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
|
||||||
|
:return: On success, if edited message is sent by the bot,
|
||||||
|
the edited Message is returned, otherwise True is returned.
|
||||||
|
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
|
||||||
|
"""
|
||||||
|
return await self.bot.edit_message_text(text=text,
|
||||||
|
chat_id=self.chat.id, message_id=self.message_id,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
disable_web_page_preview=disable_web_page_preview,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
async def delete(self):
|
async def delete(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -220,16 +629,14 @@ class Message(base.TelegramObject):
|
||||||
return await self.bot.delete_message(self.chat.id, self.message_id)
|
return await self.bot.delete_message(self.chat.id, self.message_id)
|
||||||
|
|
||||||
async def pin(self, disable_notification: bool = False):
|
async def pin(self, disable_notification: bool = False):
|
||||||
|
"""
|
||||||
|
Pin message
|
||||||
|
|
||||||
|
:param disable_notification:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
return await self.chat.pin_message(self.message_id, disable_notification)
|
return await self.chat.pin_message(self.message_id, disable_notification)
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return self.message_id
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, type(self)):
|
|
||||||
return other.message_id == self.message_id
|
|
||||||
return self.message_id == other
|
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.message_id
|
return self.message_id
|
||||||
|
|
||||||
|
|
@ -251,6 +658,7 @@ class ContentType(helper.Helper):
|
||||||
:key: LOCATION
|
:key: LOCATION
|
||||||
:key: VENUE
|
:key: VENUE
|
||||||
:key: NEW_CHAT_MEMBERS
|
:key: NEW_CHAT_MEMBERS
|
||||||
|
:key: LEFT_CHAT_MEMBER
|
||||||
:key: INVOICE
|
:key: INVOICE
|
||||||
:key: SUCCESSFUL_PAYMENT
|
:key: SUCCESSFUL_PAYMENT
|
||||||
"""
|
"""
|
||||||
|
|
@ -269,6 +677,7 @@ class ContentType(helper.Helper):
|
||||||
LOCATION = helper.ListItem() # location
|
LOCATION = helper.ListItem() # location
|
||||||
VENUE = helper.ListItem() # venue
|
VENUE = helper.ListItem() # venue
|
||||||
NEW_CHAT_MEMBERS = helper.ListItem() # new_chat_member
|
NEW_CHAT_MEMBERS = helper.ListItem() # new_chat_member
|
||||||
|
LEFT_CHAT_MEMBER = helper.ListItem() # left_chat_member
|
||||||
INVOICE = helper.ListItem() # invoice
|
INVOICE = helper.ListItem() # invoice
|
||||||
SUCCESSFUL_PAYMENT = helper.ListItem() # successful_payment
|
SUCCESSFUL_PAYMENT = helper.ListItem() # successful_payment
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ class MessageEntity(base.TelegramObject):
|
||||||
|
|
||||||
def _apply(self, text, func):
|
def _apply(self, text, func):
|
||||||
return text[:self.offset] + \
|
return text[:self.offset] + \
|
||||||
func(text[self.offset:self.offset + self.length]) + \
|
func(text[self.offset:self.offset + self.length]) + \
|
||||||
text[self.offset + self.length:]
|
text[self.offset + self.length:]
|
||||||
|
|
||||||
def apply_md(self, text):
|
def apply_md(self, text):
|
||||||
"""
|
"""
|
||||||
|
|
@ -40,6 +40,8 @@ class MessageEntity(base.TelegramObject):
|
||||||
return self._apply(text, lambda url: markdown.link(url, url))
|
return self._apply(text, lambda url: markdown.link(url, url))
|
||||||
elif self.type == MessageEntityType.TEXT_LINK:
|
elif self.type == MessageEntityType.TEXT_LINK:
|
||||||
return self._apply(text, lambda url: markdown.link(url, self.url))
|
return self._apply(text, lambda url: markdown.link(url, self.url))
|
||||||
|
if self.type == MessageEntityType.TEXT_MENTION and self.user:
|
||||||
|
return self._apply(text, lambda name: self.user.get_mention(name, as_html=False))
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def apply_html(self, text):
|
def apply_html(self, text):
|
||||||
|
|
@ -61,6 +63,8 @@ class MessageEntity(base.TelegramObject):
|
||||||
return self._apply(text, lambda url: markdown.hlink(url, url))
|
return self._apply(text, lambda url: markdown.hlink(url, url))
|
||||||
elif self.type == MessageEntityType.TEXT_LINK:
|
elif self.type == MessageEntityType.TEXT_LINK:
|
||||||
return self._apply(text, lambda url: markdown.hlink(url, self.url))
|
return self._apply(text, lambda url: markdown.hlink(url, self.url))
|
||||||
|
if self.type == MessageEntityType.TEXT_MENTION and self.user:
|
||||||
|
return self._apply(text, lambda name: self.user.get_mention(name, as_html=True))
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
46
aiogram/types/mixins.py
Normal file
46
aiogram/types/mixins.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
class Downloadable:
|
||||||
|
"""
|
||||||
|
Mixin for files
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def download(self, destination=None, timeout=30, chunk_size=65536, seek=True, make_dirs=True):
|
||||||
|
"""
|
||||||
|
Download file
|
||||||
|
|
||||||
|
:param destination: filename or instance of :class:`io.IOBase`. For e. g. :class:`io.BytesIO`
|
||||||
|
:param timeout: Integer
|
||||||
|
:param chunk_size: Integer
|
||||||
|
:param seek: Boolean - go to start of file when downloading is finished.
|
||||||
|
:param make_dirs: Make dirs if not exist
|
||||||
|
:return: destination
|
||||||
|
"""
|
||||||
|
file = await self.get_file()
|
||||||
|
|
||||||
|
is_path = True
|
||||||
|
if destination is None:
|
||||||
|
destination = file.file_path
|
||||||
|
elif isinstance(destination, (str, pathlib.Path)) and os.path.isdir(destination):
|
||||||
|
os.path.join(destination, file.file_path)
|
||||||
|
else:
|
||||||
|
is_path = False
|
||||||
|
|
||||||
|
if is_path and make_dirs:
|
||||||
|
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
||||||
|
|
||||||
|
return await self.bot.download_file(file_path=file.file_path, destination=destination, timeout=timeout,
|
||||||
|
chunk_size=chunk_size, seek=seek)
|
||||||
|
|
||||||
|
async def get_file(self):
|
||||||
|
"""
|
||||||
|
Get file information
|
||||||
|
|
||||||
|
:return: :obj:`aiogram.types.File`
|
||||||
|
"""
|
||||||
|
if hasattr(self, 'file_path'):
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
return await self.bot.get_file(self.file_id)
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
from . import base
|
from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
|
from . import mixins
|
||||||
|
|
||||||
|
|
||||||
class PhotoSize(base.TelegramObject):
|
class PhotoSize(base.TelegramObject, mixins.Downloadable):
|
||||||
"""
|
"""
|
||||||
This object represents one size of a photo or a file / sticker thumbnail.
|
This object represents one size of a photo or a file / sticker thumbnail.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,32 +33,45 @@ class ReplyKeyboardMarkup(base.TelegramObject):
|
||||||
self.conf['row_width'] = value
|
self.conf['row_width'] = value
|
||||||
|
|
||||||
def add(self, *args):
|
def add(self, *args):
|
||||||
i = 1
|
"""
|
||||||
|
Add buttons
|
||||||
|
|
||||||
|
:param args:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
row = []
|
row = []
|
||||||
for button in args:
|
for index, button in enumerate(args):
|
||||||
if isinstance(button, str):
|
row.append(button)
|
||||||
row.append({'text': button})
|
if index % self.row_width == 0:
|
||||||
elif isinstance(button, bytes):
|
|
||||||
row.append({'text': button.decode('utf-8')})
|
|
||||||
else:
|
|
||||||
row.append(button.to_json())
|
|
||||||
if i % self.row_width == 0:
|
|
||||||
self.keyboard.append(row)
|
self.keyboard.append(row)
|
||||||
row = []
|
row = []
|
||||||
i += 1
|
|
||||||
if len(row) > 0:
|
if len(row) > 0:
|
||||||
self.keyboard.append(row)
|
self.keyboard.append(row)
|
||||||
|
|
||||||
def row(self, *args):
|
def row(self, *args):
|
||||||
|
"""
|
||||||
|
Add row
|
||||||
|
|
||||||
|
:param args:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
btn_array = []
|
btn_array = []
|
||||||
for button in args:
|
for button in args:
|
||||||
if isinstance(button, str):
|
btn_array.append(button)
|
||||||
btn_array.append({'text': button})
|
|
||||||
else:
|
|
||||||
btn_array.append(button.to_json())
|
|
||||||
self.keyboard.append(btn_array)
|
self.keyboard.append(btn_array)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def insert(self, button):
|
||||||
|
"""
|
||||||
|
Insert button to last row
|
||||||
|
|
||||||
|
:param button:
|
||||||
|
"""
|
||||||
|
if self.keyboard and len(self.keyboard[-1]) < self.row_width:
|
||||||
|
self.keyboard[-1].append(button)
|
||||||
|
else:
|
||||||
|
self.add(button)
|
||||||
|
|
||||||
|
|
||||||
class KeyboardButton(base.TelegramObject):
|
class KeyboardButton(base.TelegramObject):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,6 @@ class Update(base.TelegramObject):
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return self.update_id
|
return self.update_id
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, type(self)):
|
|
||||||
return other.update_id == self.update_id
|
|
||||||
return self.update_id == other
|
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.update_id
|
return self.update_id
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,9 +64,12 @@ class User(base.TelegramObject):
|
||||||
def url(self):
|
def url(self):
|
||||||
return f"tg://user?id={self.id}"
|
return f"tg://user?id={self.id}"
|
||||||
|
|
||||||
def get_mention(self, name=None, as_html=False):
|
def get_mention(self, name=None, as_html=None):
|
||||||
|
if as_html is None and self.bot.parse_mode and self.bot.parse_mode.lower() == 'html':
|
||||||
|
as_html = True
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
name = self.mention
|
name = self.full_name
|
||||||
if as_html:
|
if as_html:
|
||||||
return markdown.hlink(name, self.url)
|
return markdown.hlink(name, self.url)
|
||||||
return markdown.link(name, self.url)
|
return markdown.link(name, self.url)
|
||||||
|
|
@ -75,12 +78,10 @@ class User(base.TelegramObject):
|
||||||
return await self.bot.get_user_profile_photos(self.id, offset, limit)
|
return await self.bot.get_user_profile_photos(self.id, offset, limit)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return self.id
|
return self.id + \
|
||||||
|
hash(self.is_bot) + \
|
||||||
def __eq__(self, other):
|
hash(self.full_name) + \
|
||||||
if isinstance(other, type(self)):
|
(hash(self.username) if self.username else 0)
|
||||||
return other.id == self.id
|
|
||||||
return self.id == other
|
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Need setup task factory:
|
You need to setup task factory:
|
||||||
>>> from aiogram.utils import context
|
>>> from aiogram.utils import context
|
||||||
>>> loop = asyncio.get_event_loop()
|
>>> loop = asyncio.get_event_loop()
|
||||||
>>> loop.set_task_factory(context.task_factory)
|
>>> loop.set_task_factory(context.task_factory)
|
||||||
|
|
@ -46,10 +46,10 @@ def get_current_state() -> typing.Dict:
|
||||||
:rtype: :obj:`dict`
|
:rtype: :obj:`dict`
|
||||||
"""
|
"""
|
||||||
task = asyncio.Task.current_task()
|
task = asyncio.Task.current_task()
|
||||||
context = getattr(task, 'context', None)
|
context_ = getattr(task, 'context', None)
|
||||||
if context is None:
|
if context_ is None:
|
||||||
context = task.context = {}
|
context_ = task.context = {}
|
||||||
return context
|
return context_
|
||||||
|
|
||||||
|
|
||||||
def get_value(key, default=None):
|
def get_value(key, default=None):
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,8 @@ def start_pooling(*args, **kwargs):
|
||||||
return start_polling(*args, **kwargs)
|
return start_polling(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def start_polling(dispatcher, *, loop=None, skip_updates=False, on_startup=None, on_shutdown=None):
|
def start_polling(dispatcher, *, loop=None, skip_updates=False,
|
||||||
|
on_startup=None, on_shutdown=None):
|
||||||
log.warning('Start bot with long-polling.')
|
log.warning('Start bot with long-polling.')
|
||||||
if loop is None:
|
if loop is None:
|
||||||
loop = dispatcher.loop
|
loop = dispatcher.loop
|
||||||
|
|
@ -59,7 +60,7 @@ def start_polling(dispatcher, *, loop=None, skip_updates=False, on_startup=None,
|
||||||
loop.set_task_factory(context.task_factory)
|
loop.set_task_factory(context.task_factory)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(_startup(dispatcher, skip_updates=skip_updates, callback=on_startup))
|
loop.run_until_complete(_startup(dispatcher, skip_updates, on_startup))
|
||||||
loop.create_task(dispatcher.start_polling(reset_webhook=True))
|
loop.create_task(dispatcher.start_polling(reset_webhook=True))
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
|
@ -69,8 +70,8 @@ def start_polling(dispatcher, *, loop=None, skip_updates=False, on_startup=None,
|
||||||
log.warning("Goodbye!")
|
log.warning("Goodbye!")
|
||||||
|
|
||||||
|
|
||||||
def start_webhook(dispatcher, webhook_path, *, loop=None, skip_updates=None, on_startup=None, on_shutdown=None,
|
def start_webhook(dispatcher, webhook_path, *, loop=None, skip_updates=None,
|
||||||
check_ip=False, **kwargs):
|
on_startup=None, on_shutdown=None, check_ip=False, **kwargs):
|
||||||
log.warning('Start bot with webhook.')
|
log.warning('Start bot with webhook.')
|
||||||
if loop is None:
|
if loop is None:
|
||||||
loop = dispatcher.loop
|
loop = dispatcher.loop
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,8 @@ class Item:
|
||||||
"""
|
"""
|
||||||
Helper item
|
Helper item
|
||||||
|
|
||||||
If value is not configured it will be generated automatically based on variable name
|
If a value is not provided,
|
||||||
|
it will be automatically generated based on a variable's name
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, value=None):
|
def __init__(self, value=None):
|
||||||
|
|
@ -156,7 +157,7 @@ class Item:
|
||||||
|
|
||||||
class ListItem(Item):
|
class ListItem(Item):
|
||||||
"""
|
"""
|
||||||
This item always is list
|
This item is always a list
|
||||||
|
|
||||||
You can use &, | and + operators for that.
|
You can use &, | and + operators for that.
|
||||||
"""
|
"""
|
||||||
|
|
@ -179,7 +180,7 @@ class ItemsList(list):
|
||||||
"""
|
"""
|
||||||
Patch for default list
|
Patch for default list
|
||||||
|
|
||||||
This class provide +, &, |, +=, &=, |= operators for extending the list
|
This class provides +, &, |, +=, &=, |= operators for extending the list
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *seq):
|
def __init__(self, *seq):
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ HTML_QUOTES_MAP = {
|
||||||
'"': '"'
|
'"': '"'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_HQS = HTML_QUOTES_MAP.keys() # HQS for HTML QUOTES SYMBOLS
|
||||||
|
|
||||||
|
|
||||||
def _join(*content, sep=' '):
|
def _join(*content, sep=' '):
|
||||||
return sep.join(map(str, content))
|
return sep.join(map(str, content))
|
||||||
|
|
@ -38,21 +40,22 @@ def quote_html(content):
|
||||||
"""
|
"""
|
||||||
Quote HTML symbols
|
Quote HTML symbols
|
||||||
|
|
||||||
All <, > and & symbols that are not a part of a tag or an HTML entity
|
All <, >, & and " symbols that are not a part of a tag or
|
||||||
must be replaced with the corresponding HTML entities (< with <, > with > and & with &).
|
an HTML entity must be replaced with the corresponding HTML entities
|
||||||
|
(< with < > with > & with & and " with ").
|
||||||
|
|
||||||
:param content: str
|
:param content: str
|
||||||
:return: str
|
:return: str
|
||||||
"""
|
"""
|
||||||
new_content = ''
|
new_content = ''
|
||||||
for symbol in content:
|
for symbol in content:
|
||||||
new_content += HTML_QUOTES_MAP[symbol] if symbol in '<>&"' else symbol
|
new_content += HTML_QUOTES_MAP[symbol] if symbol in _HQS else symbol
|
||||||
return new_content
|
return new_content
|
||||||
|
|
||||||
|
|
||||||
def text(*content, sep=' '):
|
def text(*content, sep=' '):
|
||||||
"""
|
"""
|
||||||
Join all elements with separator
|
Join all elements with a separator
|
||||||
|
|
||||||
:param content:
|
:param content:
|
||||||
:param sep:
|
:param sep:
|
||||||
|
|
@ -168,7 +171,7 @@ def hlink(title, url):
|
||||||
:param url:
|
:param url:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
return "<a href=\"{0}\">{1}</a>".format(url, quote_html(title))
|
return '<a href="{0}">{1}</a>'.format(url, quote_html(title))
|
||||||
|
|
||||||
|
|
||||||
def escape_md(*content, sep=' '):
|
def escape_md(*content, sep=' '):
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ def generate_payload(exclude=None, **kwargs):
|
||||||
exclude = []
|
exclude = []
|
||||||
return {key: value for key, value in kwargs.items() if
|
return {key: value for key, value in kwargs.items() if
|
||||||
key not in exclude + DEFAULT_FILTER
|
key not in exclude + DEFAULT_FILTER
|
||||||
and value
|
and value is not None
|
||||||
and not key.startswith('_')}
|
and not key.startswith('_')}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ from .helper import Helper, HelperMode, Item
|
||||||
|
|
||||||
|
|
||||||
class Version:
|
class Version:
|
||||||
def __init__(self, major=0, minor=0, maintenance=0, stage='final', build=0):
|
def __init__(self, major=0, minor=0,
|
||||||
|
maintenance=0, stage='final', build=0):
|
||||||
self.__raw_version = None
|
self.__raw_version = None
|
||||||
self.__version = None
|
self.__version = None
|
||||||
|
|
||||||
|
|
@ -86,7 +87,8 @@ class Version:
|
||||||
if git_changeset:
|
if git_changeset:
|
||||||
sub = '.dev{0}'.format(git_changeset)
|
sub = '.dev{0}'.format(git_changeset)
|
||||||
elif version[3] != Stage.FINAL:
|
elif version[3] != Stage.FINAL:
|
||||||
mapping = {Stage.ALPHA: 'a', Stage.BETA: 'b', Stage.RC: 'rc', Stage.DEV: 'dev'}
|
mapping = {Stage.ALPHA: 'a', Stage.BETA: 'b',
|
||||||
|
Stage.RC: 'rc', Stage.DEV: 'dev'}
|
||||||
sub = mapping[version[3]] + str(version[4])
|
sub = mapping[version[3]] + str(version[4])
|
||||||
|
|
||||||
return str(main + sub)
|
return str(main + sub)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
ujson
|
|
||||||
emoji
|
ujson>=1.35
|
||||||
pytest
|
emoji>=0.4.5
|
||||||
pytest-asyncio
|
pytest>=3.3.0
|
||||||
uvloop
|
pytest-asyncio>=0.8.0
|
||||||
aioredis
|
uvloop>=0.9.1
|
||||||
rethinkdb
|
aioredis>=1.0.0
|
||||||
|
wheel>=0.30.0
|
||||||
|
rethinkdb>=2.3.0
|
||||||
|
sphinx>=1.6.6
|
||||||
|
sphinx-rtd-theme>=0.2.4
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
BaseBot
|
BaseBot
|
||||||
=======
|
=======
|
||||||
|
|
||||||
This class is base of bot. In BaseBot implemented only methods for interactions with Telegram Bot API.
|
This class is the base class for bot. BaseBot implements only methods for interaction with Telegram Bot API.
|
||||||
|
|
||||||
.. autoclass:: aiogram.bot.base.BaseBot
|
.. autoclass:: aiogram.bot.base.BaseBot
|
||||||
:members:
|
:members:
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
Bot object
|
Bot object
|
||||||
==========
|
==========
|
||||||
|
|
||||||
That is extended (and recommended for usage) bot class based on BaseBot class.
|
This is extended (and recommended for use) bot class based on BaseBot class.
|
||||||
You can use instance of that bot in :obj:`aiogram.dispatcher.Dispatcher`
|
You can use an instance of this bot in :obj:`aiogram.dispatcher.Dispatcher`
|
||||||
|
|
||||||
.. autoclass:: aiogram.bot.bot.Bot
|
.. autoclass:: aiogram.bot.bot.Bot
|
||||||
:members:
|
:members:
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,10 @@ Redis storage
|
||||||
.. automodule:: aiogram.contrib.fsm_storage.redis
|
.. automodule:: aiogram.contrib.fsm_storage.redis
|
||||||
:members:
|
:members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
RethinkDB storage
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. automodule:: aiogram.contrib.fsm_storage.rethinkdb
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
Filters
|
Filters
|
||||||
-------
|
-------
|
||||||
|
|
||||||
In this module stored builtin filters for dispatcher.
|
This module stores builtin filters for dispatcher.
|
||||||
|
|
||||||
.. automodule:: aiogram.dispatcher.filters
|
.. automodule:: aiogram.dispatcher.filters
|
||||||
:members:
|
:members:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
Storages
|
Storages
|
||||||
--------
|
--------
|
||||||
|
|
||||||
In this module stored base of storage's for finite-state machine.
|
This module stores storage base for finite-state machine.
|
||||||
|
|
||||||
.. automodule:: aiogram.dispatcher.storage
|
.. automodule:: aiogram.dispatcher.storage
|
||||||
:members:
|
:members:
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ Welcome to aiogram's documentation!
|
||||||
:alt: MIT License
|
:alt: MIT License
|
||||||
|
|
||||||
|
|
||||||
**aiogram** is are pretty simple and fully asynchronously library for `Telegram Bot API <https://core.telegram.org/bots/api>`_ written in Python 3.6 with `asyncio <https://docs.python.org/3/library/asyncio.html>`_ and `aiohttp <https://github.com/aio-libs/aiohttp>`_. It helps to make your bots more faster and simpler.
|
**aiogram** is a pretty simple and fully asynchronous library for `Telegram Bot API <https://core.telegram.org/bots/api>`_ written in Python 3.6 with `asyncio <https://docs.python.org/3/library/asyncio.html>`_ and `aiohttp <https://github.com/aio-libs/aiohttp>`_. It helps you to make your bots faster and simpler.
|
||||||
|
|
||||||
|
|
||||||
Official aiogram resources
|
Official aiogram resources
|
||||||
|
|
@ -45,17 +45,17 @@ Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- Asynchronous
|
- Asynchronous
|
||||||
- Be awesome
|
- Awesome
|
||||||
- Make things faster
|
- Makes things faster
|
||||||
- Have `FSM <https://en.wikipedia.org/wiki/Finite-state_machine>`_
|
- Has `FSM <https://en.wikipedia.org/wiki/Finite-state_machine>`_
|
||||||
- Can reply into webhook
|
- Can reply into webhook. (In other words `make requests in response to updates <https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates>`_)
|
||||||
|
|
||||||
|
|
||||||
Contribute
|
Contribute
|
||||||
----------
|
----------
|
||||||
|
|
||||||
- `Issue Tracker <https://bitbucket.org/illemius/aiogram/issues>`_
|
- `Issue Tracker <https://github.com/aiogram/aiogram/issues>`_
|
||||||
- `Source Code <https://bitbucket.org/illemius/aiogram.git>`_
|
- `Source Code <https://github.com/aiogram/aiogram.git>`_
|
||||||
|
|
||||||
|
|
||||||
Contents
|
Contents
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
Installation Guide
|
Installation Guide
|
||||||
==================
|
==================
|
||||||
|
|
||||||
From PIP
|
Using PIP
|
||||||
--------
|
---------
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ pip install -U aiogram
|
$ pip install -U aiogram
|
||||||
|
|
@ -11,5 +11,34 @@ From sources
|
||||||
------------
|
------------
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ git clone https://bitbucket.org/illemius/aiogram.git
|
$ git clone https://github.com/aiogram/aiogram.git
|
||||||
|
$ cd aiogram
|
||||||
$ python setup.py install
|
$ python setup.py install
|
||||||
|
|
||||||
|
|
||||||
|
Recommendations
|
||||||
|
---------------
|
||||||
|
You can speedup your bots by following next instructions:
|
||||||
|
|
||||||
|
- Use `uvloop <https://github.com/MagicStack/uvloop>`_ instead of default asyncio loop.
|
||||||
|
|
||||||
|
*uvloop* is a fast, drop-in replacement of the built-in asyncio event loop. uvloop is implemented in Cython and uses libuv under the hood.
|
||||||
|
|
||||||
|
**Installation:**
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ pip install uvloop
|
||||||
|
|
||||||
|
|
||||||
|
- Use `ujson <https://github.com/esnme/ultrajson>`_ instead of default json module.
|
||||||
|
|
||||||
|
*UltraJSON* is an ultra fast JSON encoder and decoder written in pure C with bindings for Python 2.5+ and 3.
|
||||||
|
|
||||||
|
**Installation:**
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ pip install ujson
|
||||||
|
|
||||||
|
In addition, you don't need do nothing, *aiogram* is automatically starts using that if is found in your environment.
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@ Quick start
|
||||||
Simple template
|
Simple template
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
By first step you need import all modules
|
At first you have to import all necessary modules
|
||||||
|
|
||||||
.. code-block:: python3
|
.. code-block:: python3
|
||||||
|
|
||||||
from aiogram import Bot
|
from aiogram import Bot, types
|
||||||
from aiogram.dispatcher import Dispatcher
|
from aiogram.dispatcher import Dispatcher
|
||||||
from aiogram.utils import executor
|
from aiogram.utils import executor
|
||||||
|
|
||||||
In next step you you can initialize bot and dispatcher instances.
|
Then you have to initialize bot and dispatcher instances.
|
||||||
Bot token you can get from `@BotFather <https://t.me/BotFather>`_
|
Bot token you can get from `@BotFather <https://t.me/BotFather>`_
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ Bot token you can get from `@BotFather <https://t.me/BotFather>`_
|
||||||
bot = Bot(token='BOT TOKEN HERE')
|
bot = Bot(token='BOT TOKEN HERE')
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
And next: all bots is needed command for starting interaction with bot. Register first command handler:
|
Next step: interaction with bots starts with one command. Register your first command handler:
|
||||||
|
|
||||||
.. code-block:: python3
|
.. code-block:: python3
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ And next: all bots is needed command for starting interaction with bot. Registe
|
||||||
async def send_welcome(message: types.Message):
|
async def send_welcome(message: types.Message):
|
||||||
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
|
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
|
||||||
|
|
||||||
And last step - run long polling.
|
Last step: run long polling.
|
||||||
|
|
||||||
.. code-block:: python3
|
.. code-block:: python3
|
||||||
|
|
||||||
|
|
@ -41,12 +41,16 @@ Summary
|
||||||
|
|
||||||
.. code-block:: python3
|
.. code-block:: python3
|
||||||
|
|
||||||
from aiogram import Bot
|
from aiogram import Bot, types
|
||||||
from aiogram.dispatcher import Dispatcher
|
from aiogram.dispatcher import Dispatcher
|
||||||
from aiogram.utils import executor
|
from aiogram.utils import executor
|
||||||
|
|
||||||
bot = Bot(token='BOT TOKEN HERE')
|
bot = Bot(token='BOT TOKEN HERE')
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
|
@dp.message_handler(commands=['start', 'help'])
|
||||||
|
async def send_welcome(message: types.Message):
|
||||||
|
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
executor.start_polling(dp)
|
executor.start_polling(dp)
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,6 @@ Data types
|
||||||
|
|
||||||
Bases
|
Bases
|
||||||
-----
|
-----
|
||||||
:class:`aiogram.types.base.Serializable`
|
|
||||||
|
|
||||||
:class:`aiogram.types.base.Deserializable`
|
|
||||||
|
|
||||||
.. automodule:: aiogram.types.base
|
.. automodule:: aiogram.types.base
|
||||||
:members:
|
:members:
|
||||||
|
|
@ -210,6 +207,16 @@ ResponseParameters
|
||||||
:members:
|
:members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
InputMedia
|
||||||
|
----------
|
||||||
|
:class:`aiogram.types.InputMediaPhoto`
|
||||||
|
:class:`aiogram.types.InputMediaVideo`
|
||||||
|
:class:`aiogram.types.MediaGroup`
|
||||||
|
|
||||||
|
.. automodule:: aiogram.types.input_media
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
Sticker
|
Sticker
|
||||||
-------
|
-------
|
||||||
:class:`aiogram.types.Sticker`
|
:class:`aiogram.types.Sticker`
|
||||||
|
|
@ -299,3 +306,13 @@ Games
|
||||||
.. automodule:: aiogram.types.game_high_score
|
.. automodule:: aiogram.types.game_high_score
|
||||||
:members:
|
:members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
InputFile interface
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
:class:`aiogram.types.InputFile`
|
||||||
|
|
||||||
|
.. automodule:: aiogram.types.input_file
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ dp = Dispatcher(bot)
|
||||||
|
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=['start'])
|
||||||
async def send_welcome(message: types.Message):
|
async def send_welcome(message: types.Message):
|
||||||
# So... By first i want to send something like that:
|
# So... At first I want to send something like this:
|
||||||
await message.reply("Do you want to see many pussies? Are you ready?")
|
await message.reply("Do you want to see many pussies? Are you ready?")
|
||||||
|
|
||||||
# And wait few seconds...
|
# And wait few seconds...
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
# Good bots always must be send chat actions. Or not.
|
# Good bots should send chat actions. Or not.
|
||||||
await ChatActions.upload_photo()
|
await ChatActions.upload_photo()
|
||||||
|
|
||||||
# Create media group
|
# Create media group
|
||||||
|
|
|
||||||
16
examples/regexp_commands_filter_example.py
Normal file
16
examples/regexp_commands_filter_example.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
from aiogram import Bot, types
|
||||||
|
from aiogram.dispatcher import Dispatcher, filters
|
||||||
|
from aiogram.utils import executor
|
||||||
|
|
||||||
|
bot = Bot(token='TOKEN')
|
||||||
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(filters.RegexpCommandsFilter(regexp_commands=['item_([0-9]*)']))
|
||||||
|
async def send_welcome(message: types.Message):
|
||||||
|
regexp_command = message.conf['regexp_command']
|
||||||
|
await message.reply("You have requested an item with number: {}".format(regexp_command.group(1)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
executor.start_polling(dp)
|
||||||
|
|
@ -1,11 +1,2 @@
|
||||||
aiohttp>=2.1.0
|
aiohttp>=2.3.5
|
||||||
appdirs>=1.4.3
|
Babel>=2.5.1
|
||||||
async-timeout>=1.2.1
|
|
||||||
Babel>=2.4.0
|
|
||||||
chardet>=3.0.3
|
|
||||||
multidict>=2.1.6
|
|
||||||
packaging>=16.8
|
|
||||||
pyparsing>=2.2.0
|
|
||||||
pytz>=2017.2
|
|
||||||
six>=1.10.0
|
|
||||||
yarl>=0.10.2
|
|
||||||
|
|
|
||||||
32
setup.py
32
setup.py
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
|
from pip.req import parse_requirements
|
||||||
from setuptools import PackageFinder
|
from setuptools import PackageFinder
|
||||||
|
|
||||||
from aiogram import VERSION
|
from aiogram import Stage, VERSION
|
||||||
|
|
||||||
|
|
||||||
def get_description():
|
def get_description():
|
||||||
|
|
@ -14,7 +15,7 @@ def get_description():
|
||||||
:return: description
|
:return: description
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
with open('README.rst', encoding='utf-8') as f:
|
with open('README.rst', 'r', encoding='utf-8') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -25,34 +26,35 @@ def get_requirements():
|
||||||
:return: requirements
|
:return: requirements
|
||||||
:rtype: list
|
:rtype: list
|
||||||
"""
|
"""
|
||||||
requirements = []
|
filename = 'requirements.txt'
|
||||||
with open('requirements.txt', 'r') as file:
|
if VERSION.stage == Stage.DEV:
|
||||||
for line in file.readlines():
|
filename = 'dev_' + filename
|
||||||
line = line.strip()
|
|
||||||
if not line or line.startswith('#'):
|
|
||||||
continue
|
|
||||||
requirements.append(line)
|
|
||||||
|
|
||||||
return requirements
|
install_reqs = parse_requirements(filename, session='hack')
|
||||||
|
return [str(ir.req) for ir in install_reqs]
|
||||||
|
|
||||||
|
|
||||||
|
install_requires = get_requirements()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='aiogram',
|
name='aiogram',
|
||||||
version=VERSION.version,
|
version=VERSION.version,
|
||||||
packages=PackageFinder.find(exclude=('tests', 'examples', 'docs',)),
|
packages=PackageFinder.find(exclude=('tests', 'tests.*', 'examples.*', 'docs',)),
|
||||||
url='https://github.com/aiogram/aiogram',
|
url='https://github.com/aiogram/aiogram',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
author='Alex Root Junior',
|
author='Alex Root Junior',
|
||||||
author_email='jroot.junior@gmail.com',
|
author_email='jroot.junior@gmail.com',
|
||||||
description='Is are pretty simple and fully asynchronously library for Telegram Bot API',
|
description='Is a pretty simple and fully asynchronous library for Telegram Bot API',
|
||||||
long_description=get_description(),
|
long_description=get_description(),
|
||||||
classifiers=[
|
classifiers=[
|
||||||
VERSION.pypi_development_status, # Automated change classifier by build stage
|
VERSION.pypi_development_status, # Automated change classifier by build stage
|
||||||
'Programming Language :: Python :: 3.6',
|
|
||||||
'Environment :: Console',
|
'Environment :: Console',
|
||||||
'Framework :: AsyncIO',
|
'Framework :: AsyncIO',
|
||||||
'Topic :: Software Development :: Libraries :: Application Frameworks',
|
'Intended Audience :: Developers',
|
||||||
|
'Intended Audience :: System Administrators',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
|
'Topic :: Software Development :: Libraries :: Application Frameworks',
|
||||||
],
|
],
|
||||||
install_requires=get_requirements()
|
install_requires=install_requires
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ def test_export():
|
||||||
def test_id():
|
def test_id():
|
||||||
assert isinstance(chat.id, int)
|
assert isinstance(chat.id, int)
|
||||||
assert chat.id == CHAT['id']
|
assert chat.id == CHAT['id']
|
||||||
assert hash(chat) == CHAT['id']
|
# assert hash(chat) == CHAT['id']
|
||||||
|
|
||||||
|
|
||||||
def test_name():
|
def test_name():
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from .dataset import GAME
|
||||||
|
|
||||||
game = types.Game(**GAME)
|
game = types.Game(**GAME)
|
||||||
|
|
||||||
|
|
||||||
def test_export():
|
def test_export():
|
||||||
exported = game.to_python()
|
exported = game.to_python()
|
||||||
assert isinstance(exported, dict)
|
assert isinstance(exported, dict)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ def test_export():
|
||||||
|
|
||||||
|
|
||||||
def test_message_id():
|
def test_message_id():
|
||||||
assert hash(message) == MESSAGE['message_id']
|
# assert hash(message) == MESSAGE['message_id']
|
||||||
assert message.message_id == MESSAGE['message_id']
|
assert message.message_id == MESSAGE['message_id']
|
||||||
assert message['message_id'] == MESSAGE['message_id']
|
assert message['message_id'] == MESSAGE['message_id']
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ def test_export():
|
||||||
|
|
||||||
def test_update_id():
|
def test_update_id():
|
||||||
assert isinstance(update.update_id, int)
|
assert isinstance(update.update_id, int)
|
||||||
assert hash(update) == UPDATE['update_id']
|
# assert hash(update) == UPDATE['update_id']
|
||||||
assert update.update_id == UPDATE['update_id']
|
assert update.update_id == UPDATE['update_id']
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ def test_export():
|
||||||
def test_id():
|
def test_id():
|
||||||
assert isinstance(user.id, int)
|
assert isinstance(user.id, int)
|
||||||
assert user.id == USER['id']
|
assert user.id == USER['id']
|
||||||
assert hash(user) == USER['id']
|
# assert hash(user) == USER['id']
|
||||||
|
|
||||||
|
|
||||||
def test_bot():
|
def test_bot():
|
||||||
|
|
@ -40,7 +40,7 @@ def test_full_name():
|
||||||
|
|
||||||
def test_mention():
|
def test_mention():
|
||||||
assert user.mention == f"@{USER['username']}"
|
assert user.mention == f"@{USER['username']}"
|
||||||
assert user.get_mention('foo') == f"[foo](tg://user?id={USER['id']})"
|
assert user.get_mention('foo', as_html=False) == f"[foo](tg://user?id={USER['id']})"
|
||||||
assert user.get_mention('foo', as_html=True) == f"<a href=\"tg://user?id={USER['id']}\">foo</a>"
|
assert user.get_mention('foo', as_html=True) == f"<a href=\"tg://user?id={USER['id']}\">foo</a>"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue