mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-11 18:01:04 +00:00
Merge remote-tracking branch 'upstream/dev-1.x' into dev-1.x
# Conflicts: # aiogram/types/user.py - update hashing in user type
This commit is contained in:
commit
19f67a2e99
24 changed files with 245 additions and 89 deletions
|
|
@ -20,7 +20,7 @@ else:
|
||||||
|
|
||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
|
|
||||||
VERSION = Version(1, 1, 1, stage=Stage.DEV, build=0)
|
VERSION = Version(1, 2, 1, stage=Stage.DEV, build=0)
|
||||||
API_VERSION = Version(3, 6)
|
API_VERSION = Version(3, 6)
|
||||||
|
|
||||||
__version__ = VERSION.version
|
__version__ = VERSION.version
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import aiohttp
|
||||||
from . import api
|
from . import api
|
||||||
from ..types import ParseMode, base
|
from ..types import ParseMode, base
|
||||||
from ..utils import json
|
from ..utils import json
|
||||||
|
from ..utils.auth_widget import check_token
|
||||||
|
|
||||||
|
|
||||||
class BaseBot:
|
class BaseBot:
|
||||||
|
|
@ -67,17 +68,17 @@ class BaseBot:
|
||||||
self.parse_mode = parse_mode
|
self.parse_mode = parse_mode
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.close()
|
asyncio.ensure_future(self.close())
|
||||||
|
|
||||||
def close(self):
|
async def close(self):
|
||||||
"""
|
"""
|
||||||
Close all client sessions
|
Close all client sessions
|
||||||
"""
|
"""
|
||||||
|
if self.session and not self.session.closed:
|
||||||
|
await self.session.close()
|
||||||
for session in self._temp_sessions:
|
for session in self._temp_sessions:
|
||||||
if not session.closed:
|
if not session.closed:
|
||||||
session.close()
|
await session.close()
|
||||||
if self.session and not self.session.closed:
|
|
||||||
self.session.close()
|
|
||||||
|
|
||||||
def create_temp_session(self, limit: base.Integer = 1, force_close: base.Boolean = False) -> aiohttp.ClientSession:
|
def create_temp_session(self, limit: base.Integer = 1, force_close: base.Boolean = False) -> aiohttp.ClientSession:
|
||||||
"""
|
"""
|
||||||
|
|
@ -248,3 +249,6 @@ class BaseBot:
|
||||||
@parse_mode.deleter
|
@parse_mode.deleter
|
||||||
def parse_mode(self):
|
def parse_mode(self):
|
||||||
self.parse_mode = None
|
self.parse_mode = None
|
||||||
|
|
||||||
|
def check_auth_widget(self, data):
|
||||||
|
return check_token(data, self.__token)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import typing
|
import typing
|
||||||
|
import weakref
|
||||||
|
|
||||||
import rethinkdb as r
|
import rethinkdb as r
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ class RethinkDBStorage(BaseStorage):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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, max_conn=10, loop=None):
|
||||||
self._host = host
|
self._host = host
|
||||||
self._port = port
|
self._port = port
|
||||||
self._db = db
|
self._db = db
|
||||||
|
|
@ -47,77 +48,113 @@ class RethinkDBStorage(BaseStorage):
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
self._ssl = ssl or {}
|
self._ssl = ssl or {}
|
||||||
|
|
||||||
self._connection: r.Connection = None
|
self._queue = asyncio.Queue(max_conn)
|
||||||
|
self._outstanding_connections = weakref.WeakSet()
|
||||||
self._loop = loop or asyncio.get_event_loop()
|
self._loop = loop or asyncio.get_event_loop()
|
||||||
self._lock = asyncio.Lock(loop=self._loop)
|
|
||||||
|
|
||||||
async def connection(self):
|
async def get_connection(self):
|
||||||
"""
|
"""
|
||||||
Get or create connection.
|
Get or create connection.
|
||||||
"""
|
"""
|
||||||
async with self._lock: # thread-safe
|
try:
|
||||||
if not self._connection:
|
while True:
|
||||||
self._connection = await r.connect(host=self._host, port=self._port, db=self._db,
|
conn: r.Connection = self._queue.get_nowait()
|
||||||
auth_key=self._auth_key, user=self._user,
|
if conn.is_open():
|
||||||
password=self._password, timeout=self._timeout, ssl=self._ssl,
|
break
|
||||||
io_loop=self._loop)
|
try:
|
||||||
return self._connection
|
await conn.close()
|
||||||
|
except r.ReqlError:
|
||||||
|
raise ConnectionNotClosed('Exception was caught while closing connection')
|
||||||
|
except asyncio.QueueEmpty:
|
||||||
|
if len(self._outstanding_connections) < self._queue.maxsize:
|
||||||
|
conn = await r.connect(host=self._host, port=self._port, db=self._db,
|
||||||
|
auth_key=self._auth_key, user=self._user, password=self._password,
|
||||||
|
timeout=self._timeout, ssl=self._ssl)
|
||||||
|
else:
|
||||||
|
conn = await self._queue.get()
|
||||||
|
|
||||||
|
self._outstanding_connections.add(conn)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
async def put_connection(self, conn):
|
||||||
|
"""
|
||||||
|
Return connection to pool.
|
||||||
|
"""
|
||||||
|
self._queue.put_nowait(conn)
|
||||||
|
self._outstanding_connections.remove(conn)
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
"""
|
"""
|
||||||
Close connection.
|
Close all connections.
|
||||||
"""
|
"""
|
||||||
if self._connection and self._connection.is_open():
|
while True:
|
||||||
await self._connection.close()
|
try:
|
||||||
self._connection = None
|
conn: r.Connection = self._queue.get_nowait()
|
||||||
|
except asyncio.QueueEmpty:
|
||||||
|
break
|
||||||
|
|
||||||
|
self._outstanding_connections.add(conn)
|
||||||
|
|
||||||
|
for conn in self._outstanding_connections:
|
||||||
|
try:
|
||||||
|
await conn.close()
|
||||||
|
except r.ReqlError:
|
||||||
|
raise ConnectionNotClosed('Exception was caught while closing connection')
|
||||||
|
|
||||||
def wait_closed(self):
|
def wait_closed(self):
|
||||||
"""
|
"""
|
||||||
Checks if connection is closed.
|
Checks if connection is closed.
|
||||||
"""
|
"""
|
||||||
if self._connection:
|
if len(self._outstanding_connections) != 0 and self._queue.qsize() != 0:
|
||||||
raise ConnectionNotClosed
|
raise ConnectionNotClosed
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
conn = await self.connection()
|
conn = await self.get_connection()
|
||||||
return await r.table(self._table).get(chat)[user]['state'].default(default or '').run(conn)
|
result = await r.table(self._table).get(chat)[user]['state'].default(default or '').run(conn)
|
||||||
|
await self.put_connection(conn)
|
||||||
|
return result
|
||||||
|
|
||||||
async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||||
default: typing.Optional[str] = None) -> typing.Dict:
|
default: typing.Optional[str] = None) -> typing.Dict:
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
conn = await self.connection()
|
conn = await self.get_connection()
|
||||||
return await r.table(self._table).get(chat)[user]['data'].default(default or {}).run(conn)
|
result = await r.table(self._table).get(chat)[user]['data'].default(default or {}).run(conn)
|
||||||
|
await self.put_connection(conn)
|
||||||
|
return result
|
||||||
|
|
||||||
async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||||
state: typing.Optional[typing.AnyStr] = None):
|
state: typing.Optional[typing.AnyStr] = None):
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
conn = await self.connection()
|
conn = await self.get_connection()
|
||||||
if await r.table(self._table).get(chat).run(conn):
|
if await r.table(self._table).get(chat).run(conn):
|
||||||
await r.table(self._table).get(chat).update({user: {'state': state}}).run(conn)
|
await r.table(self._table).get(chat).update({user: {'state': state}}).run(conn)
|
||||||
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)
|
||||||
|
await self.put_connection(conn)
|
||||||
|
|
||||||
async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||||
data: typing.Dict = 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.get_connection()
|
||||||
if await r.table(self._table).get(chat).run(conn):
|
if await r.table(self._table).get(chat).run(conn):
|
||||||
await r.table(self._table).get(chat).update({user: {'data': r.literal(data)}}).run(conn)
|
await r.table(self._table).get(chat).update({user: {'data': r.literal(data)}}).run(conn)
|
||||||
else:
|
else:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'data': data}}).run(conn)
|
await r.table(self._table).insert({'id': chat, user: {'data': data}}).run(conn)
|
||||||
|
await self.put_connection(conn)
|
||||||
|
|
||||||
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||||
data: typing.Dict = 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.get_connection()
|
||||||
if await r.table(self._table).get(chat).run(conn):
|
if await r.table(self._table).get(chat).run(conn):
|
||||||
await r.table(self._table).get(chat).update({user: {'data': data}}).run(conn)
|
await r.table(self._table).get(chat).update({user: {'data': data}}).run(conn)
|
||||||
else:
|
else:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'data': data}}).run(conn)
|
await r.table(self._table).insert({'id': chat, user: {'data': data}}).run(conn)
|
||||||
|
await self.put_connection(conn)
|
||||||
|
|
||||||
def has_bucket(self):
|
def has_bucket(self):
|
||||||
return True
|
return True
|
||||||
|
|
@ -125,35 +162,39 @@ class RethinkDBStorage(BaseStorage):
|
||||||
async def get_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def get_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||||
default: typing.Optional[dict] = None) -> typing.Dict:
|
default: typing.Optional[dict] = None) -> typing.Dict:
|
||||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||||
conn = await self.connection()
|
conn = await self.get_connection()
|
||||||
return await r.table(self._table).get(chat)[user]['bucket'].default(default or {}).run(conn)
|
result = await r.table(self._table).get(chat)[user]['bucket'].default(default or {}).run(conn)
|
||||||
|
await self.put_connection(conn)
|
||||||
|
return result
|
||||||
|
|
||||||
async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||||
bucket: typing.Dict = 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.get_connection()
|
||||||
if await r.table(self._table).get(chat).run(conn):
|
if await r.table(self._table).get(chat).run(conn):
|
||||||
await r.table(self._table).get(chat).update({user: {'bucket': r.literal(bucket)}}).run(conn)
|
await r.table(self._table).get(chat).update({user: {'bucket': r.literal(bucket)}}).run(conn)
|
||||||
else:
|
else:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'bucket': bucket}}).run(conn)
|
await r.table(self._table).insert({'id': chat, user: {'bucket': bucket}}).run(conn)
|
||||||
|
await self.put_connection(conn)
|
||||||
|
|
||||||
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
|
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
|
||||||
user: typing.Union[str, int, None] = None, bucket: typing.Dict = None,
|
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.get_connection()
|
||||||
if await r.table(self._table).get(chat).run(conn):
|
if await r.table(self._table).get(chat).run(conn):
|
||||||
await r.table(self._table).get(chat).update({user: {'bucket': bucket}}).run(conn)
|
await r.table(self._table).get(chat).update({user: {'bucket': bucket}}).run(conn)
|
||||||
else:
|
else:
|
||||||
await r.table(self._table).insert({'id': chat, user: {'bucket': bucket}}).run(conn)
|
await r.table(self._table).insert({'id': chat, user: {'bucket': bucket}}).run(conn)
|
||||||
|
await self.put_connection(conn)
|
||||||
|
|
||||||
async def get_states_list(self) -> typing.List[typing.Tuple[int]]:
|
async def get_states_list(self) -> typing.List[typing.Tuple[int, int]]:
|
||||||
"""
|
"""
|
||||||
Get list of all stored chat's and user's
|
Get list of all stored chat's and user's
|
||||||
|
|
||||||
:return: list of tuples where first element is chat id and second is user id
|
:return: list of tuples where first element is chat id and second is user id
|
||||||
"""
|
"""
|
||||||
conn = await self.connection()
|
conn = await self.get_connection()
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
items = (await r.table(self._table).run(conn)).items
|
items = (await r.table(self._table).run(conn)).items
|
||||||
|
|
@ -164,11 +205,14 @@ class RethinkDBStorage(BaseStorage):
|
||||||
user = int(key)
|
user = int(key)
|
||||||
result.append((chat, user))
|
result.append((chat, user))
|
||||||
|
|
||||||
|
await self.put_connection(conn)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def reset_all(self):
|
async def reset_all(self):
|
||||||
"""
|
"""
|
||||||
Reset states in DB
|
Reset states in DB
|
||||||
"""
|
"""
|
||||||
conn = await self.connection()
|
conn = await self.get_connection()
|
||||||
await r.table(self._table).delete().run(conn)
|
await r.table(self._table).delete().run(conn)
|
||||||
|
await self.put_connection(conn)
|
||||||
|
|
|
||||||
|
|
@ -286,7 +286,7 @@ class FSMContext:
|
||||||
async def update_data(self, data: typing.Dict = None, **kwargs):
|
async def update_data(self, data: typing.Dict = None, **kwargs):
|
||||||
await self.storage.update_data(chat=self.chat, user=self.user, data=data, **kwargs)
|
await self.storage.update_data(chat=self.chat, user=self.user, data=data, **kwargs)
|
||||||
|
|
||||||
async def set_state(self, state: typing.Union[typing.AnyStr, None]):
|
async def set_state(self, state: typing.Union[typing.AnyStr, None] = None):
|
||||||
await self.storage.set_state(chat=self.chat, user=self.user, state=state)
|
await self.storage.set_state(chat=self.chat, user=self.user, state=state)
|
||||||
|
|
||||||
async def set_data(self, data: typing.Dict = None):
|
async def set_data(self, data: typing.Dict = None):
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
from .animation import Animation
|
from .animation import Animation
|
||||||
from .audio import Audio
|
from .audio import Audio
|
||||||
|
from .auth_widget_data import AuthWidgetData
|
||||||
from .callback_game import CallbackGame
|
from .callback_game import CallbackGame
|
||||||
from .callback_query import CallbackQuery
|
from .callback_query import CallbackQuery
|
||||||
from .chat import Chat, ChatActions, ChatType
|
from .chat import Chat, ChatActions, ChatType
|
||||||
|
|
@ -56,6 +57,7 @@ __all__ = (
|
||||||
'AllowedUpdates',
|
'AllowedUpdates',
|
||||||
'Animation',
|
'Animation',
|
||||||
'Audio',
|
'Audio',
|
||||||
|
'AuthWidgetData',
|
||||||
'CallbackGame',
|
'CallbackGame',
|
||||||
'CallbackQuery',
|
'CallbackQuery',
|
||||||
'Chat',
|
'Chat',
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,3 @@ class Audio(base.TelegramObject, mixins.Downloadable):
|
||||||
title: base.String = fields.Field()
|
title: 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 hash(self.file_id) + \
|
|
||||||
self.duration + \
|
|
||||||
hash(self.performer) + \
|
|
||||||
hash(self.title) + \
|
|
||||||
hash(self.mime_type) + \
|
|
||||||
self.file_size
|
|
||||||
|
|
|
||||||
47
aiogram/types/auth_widget_data.py
Normal file
47
aiogram/types/auth_widget_data.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from . import base
|
||||||
|
from . import fields
|
||||||
|
|
||||||
|
|
||||||
|
class AuthWidgetData(base.TelegramObject):
|
||||||
|
id: base.Integer = fields.Field()
|
||||||
|
first_name: base.String = fields.Field()
|
||||||
|
last_name: base.String = fields.Field()
|
||||||
|
username: base.String = fields.Field()
|
||||||
|
photo_url: base.String = fields.Field()
|
||||||
|
auth_date: base.String = fields.DateTimeField()
|
||||||
|
hash: base.String = fields.Field()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, request: web.Request) -> 'AuthWidgetData':
|
||||||
|
"""
|
||||||
|
Parse request as Telegram auth widget data.
|
||||||
|
|
||||||
|
:param request:
|
||||||
|
:return: :obj:`AuthWidgetData`
|
||||||
|
:raise :obj:`aiohttp.web.HTTPBadRequest`
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
query = dict(request.query)
|
||||||
|
query['id'] = int(query['id'])
|
||||||
|
query['auth_date'] = int(query['auth_date'])
|
||||||
|
widget = AuthWidgetData(**query)
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
raise web.HTTPBadRequest(text='Invalid auth data')
|
||||||
|
else:
|
||||||
|
return widget
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
return self.bot.check_auth_widget(self.to_python())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self):
|
||||||
|
result = self.first_name
|
||||||
|
if self.last_name:
|
||||||
|
result += ' '
|
||||||
|
result += self.last_name
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return self.id
|
||||||
|
|
@ -244,3 +244,28 @@ class TelegramObject(metaclass=MetaTelegramObject):
|
||||||
"""
|
"""
|
||||||
for _, value in self:
|
for _, value in self:
|
||||||
yield value
|
yield value
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
def _hash(obj):
|
||||||
|
buf = 0
|
||||||
|
if isinstance(obj, list):
|
||||||
|
for item in obj:
|
||||||
|
buf += _hash(item)
|
||||||
|
elif isinstance(obj, dict):
|
||||||
|
for dict_key, dict_value in obj.items():
|
||||||
|
buf += hash(dict_key) + _hash(dict_value)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
buf += hash(obj)
|
||||||
|
except TypeError: # Skip unhashable objects
|
||||||
|
pass
|
||||||
|
return buf
|
||||||
|
|
||||||
|
result = 0
|
||||||
|
for key, value in sorted(self.values.items()):
|
||||||
|
result += hash(key) + _hash(value)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return isinstance(other, self.__class__) and hash(other) == hash(self)
|
||||||
|
|
|
||||||
|
|
@ -56,3 +56,6 @@ class CallbackQuery(base.TelegramObject):
|
||||||
:rtype: :obj:`base.Boolean`"""
|
:rtype: :obj:`base.Boolean`"""
|
||||||
await self.bot.answer_callback_query(callback_query_id=self.id, text=text,
|
await self.bot.answer_callback_query(callback_query_id=self.id, text=text,
|
||||||
show_alert=show_alert, url=url, cache_time=cache_time)
|
show_alert=show_alert, url=url, cache_time=cache_time)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.id)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ class Chat(base.TelegramObject):
|
||||||
sticker_set_name: base.String = fields.Field()
|
sticker_set_name: base.String = fields.Field()
|
||||||
can_set_sticker_set: base.Boolean = fields.Field()
|
can_set_sticker_set: base.Boolean = fields.Field()
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return self.id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_name(self):
|
def full_name(self):
|
||||||
if self.type == ChatType.PRIVATE:
|
if self.type == ChatType.PRIVATE:
|
||||||
|
|
|
||||||
|
|
@ -73,3 +73,6 @@ class ChatPhoto(base.TelegramObject):
|
||||||
|
|
||||||
async def get_big_file(self):
|
async def get_big_file(self):
|
||||||
return await self.bot.get_file(self.big_file_id)
|
return await self.bot.get_file(self.big_file_id)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.small_file_id) + hash(self.big_file_id)
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,6 @@ class Contact(base.TelegramObject):
|
||||||
if self.last_name is not None:
|
if self.last_name is not None:
|
||||||
name += ' ' + self.last_name
|
name += ' ' + self.last_name
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.phone_number)
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,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, reply=False) -> 'Message':
|
disable_notification=None, reply_markup=None, reply=True) -> 'Message':
|
||||||
"""
|
"""
|
||||||
Reply to this message
|
Reply to this message
|
||||||
|
|
||||||
|
|
@ -630,6 +630,30 @@ 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 reply_sticker(self, sticker: typing.Union[base.InputFile, base.String],
|
||||||
|
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||||
|
reply_markup=None, reply=True) -> 'Message':
|
||||||
|
"""
|
||||||
|
Use this method to send .webp stickers.
|
||||||
|
|
||||||
|
Source: https://core.telegram.org/bots/api#sendsticker
|
||||||
|
|
||||||
|
:param sticker: Sticker to send.
|
||||||
|
:type sticker: :obj:`typing.Union[base.InputFile, base.String]`
|
||||||
|
: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_sticker(chat_id=self.chat.id, sticker=sticker,
|
||||||
|
disable_notification=disable_notification,
|
||||||
|
reply_to_message_id=self.message_id if reply else None,
|
||||||
|
reply_markup=reply_markup)
|
||||||
|
|
||||||
async def pin(self, disable_notification: bool = False):
|
async def pin(self, disable_notification: bool = False):
|
||||||
"""
|
"""
|
||||||
Pin message
|
Pin message
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -44,3 +44,6 @@ class Downloadable:
|
||||||
return self
|
return self
|
||||||
else:
|
else:
|
||||||
return await self.bot.get_file(self.file_id)
|
return await self.bot.get_file(self.file_id)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.file_id)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
from . import base
|
from . import base
|
||||||
from . import fields
|
from . import fields
|
||||||
|
from . import mixins
|
||||||
from .mask_position import MaskPosition
|
from .mask_position import MaskPosition
|
||||||
from .photo_size import PhotoSize
|
from .photo_size import PhotoSize
|
||||||
|
|
||||||
|
|
||||||
class Sticker(base.TelegramObject):
|
class Sticker(base.TelegramObject, mixins.Downloadable):
|
||||||
"""
|
"""
|
||||||
This object represents a sticker.
|
This object represents a sticker.
|
||||||
|
|
||||||
|
|
@ -18,11 +19,3 @@ class Sticker(base.TelegramObject):
|
||||||
set_name: base.String = fields.Field()
|
set_name: base.String = fields.Field()
|
||||||
mask_position: MaskPosition = fields.Field(base=MaskPosition)
|
mask_position: MaskPosition = fields.Field(base=MaskPosition)
|
||||||
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
|
|
||||||
|
|
|
||||||
|
|
@ -79,10 +79,7 @@ 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) + \
|
|
||||||
hash(self.full_name) + \
|
|
||||||
(hash(self.username) if self.username else 0)
|
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
|
|
||||||
|
|
@ -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 Video(base.TelegramObject):
|
class Video(base.TelegramObject, mixins.Downloadable):
|
||||||
"""
|
"""
|
||||||
This object represents a video file.
|
This object represents a video file.
|
||||||
|
|
||||||
|
|
@ -16,11 +17,3 @@ class Video(base.TelegramObject):
|
||||||
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
||||||
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,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 VideoNote(base.TelegramObject):
|
class VideoNote(base.TelegramObject, mixins.Downloadable):
|
||||||
"""
|
"""
|
||||||
This object represents a video message (available in Telegram apps as of v.4.0).
|
This object represents a video message (available in Telegram apps as of v.4.0).
|
||||||
|
|
||||||
|
|
@ -14,11 +15,3 @@ class VideoNote(base.TelegramObject):
|
||||||
duration: base.Integer = fields.Field()
|
duration: base.Integer = fields.Field()
|
||||||
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
||||||
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 Voice(base.TelegramObject):
|
class Voice(base.TelegramObject, mixins.Downloadable):
|
||||||
"""
|
"""
|
||||||
This object represents a voice note.
|
This object represents a voice note.
|
||||||
|
|
||||||
|
|
@ -12,11 +13,3 @@ class Voice(base.TelegramObject):
|
||||||
duration: base.Integer = fields.Field()
|
duration: base.Integer = 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
|
|
||||||
|
|
|
||||||
25
aiogram/utils/auth_widget.py
Normal file
25
aiogram/utils/auth_widget.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import collections
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
|
||||||
|
|
||||||
|
def check_token(data, token):
|
||||||
|
"""
|
||||||
|
Validate auth token
|
||||||
|
https://core.telegram.org/widgets/login#checking-authorization
|
||||||
|
|
||||||
|
Source: https://gist.github.com/xen/e4bea72487d34caa28c762776cf655a3
|
||||||
|
|
||||||
|
:param data:
|
||||||
|
:param token:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
secret = hashlib.sha256()
|
||||||
|
secret.update(token.encode('utf-8'))
|
||||||
|
sorted_params = collections.OrderedDict(sorted(data.items()))
|
||||||
|
param_hash = sorted_params.pop('hash', '') or ''
|
||||||
|
msg = "\n".join(["{}={}".format(k, v) for k, v in sorted_params.items()])
|
||||||
|
|
||||||
|
if param_hash == hmac.new(secret.digest(), msg.encode('utf-8'), digestmod=hashlib.sha256).hexdigest():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
@ -39,6 +39,8 @@ async def _shutdown(dispatcher: Dispatcher, callback=None):
|
||||||
await dispatcher.storage.close()
|
await dispatcher.storage.close()
|
||||||
await dispatcher.storage.wait_closed()
|
await dispatcher.storage.wait_closed()
|
||||||
|
|
||||||
|
await dispatcher.bot.close()
|
||||||
|
|
||||||
|
|
||||||
async def _wh_shutdown(app):
|
async def _wh_shutdown(app):
|
||||||
callback = app.get('_shutdown_callback', None)
|
callback = app.get('_shutdown_callback', None)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
II this example used ArgumentParser for configuring Your bot.
|
In this example used ArgumentParser for configuring Your bot.
|
||||||
|
|
||||||
Provided to start bot with webhook:
|
Provided to start bot with webhook:
|
||||||
python adwanced_executor_example.py \
|
python adwanced_executor_example.py \
|
||||||
|
|
|
||||||
7
setup.py
7
setup.py
|
|
@ -1,12 +1,19 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
from pip.req import parse_requirements
|
from pip.req import parse_requirements
|
||||||
from setuptools import PackageFinder
|
from setuptools import PackageFinder
|
||||||
|
|
||||||
from aiogram import Stage, VERSION
|
from aiogram import Stage, VERSION
|
||||||
|
|
||||||
|
MINIMAL_PY_VERSION = (3, 6)
|
||||||
|
|
||||||
|
if sys.version_info < MINIMAL_PY_VERSION:
|
||||||
|
warn('aiogram works only with Python {}+'.format('.'.join(map(str, MINIMAL_PY_VERSION)), RuntimeWarning))
|
||||||
|
|
||||||
|
|
||||||
def get_description():
|
def get_description():
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue