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:
Suren Khorenyan 2018-03-15 20:46:34 +03:00
commit 19f67a2e99
24 changed files with 245 additions and 89 deletions

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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):

View file

@ -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',

View file

@ -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

View 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

View file

@ -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)

View file

@ -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)

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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):
""" """

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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)

View file

@ -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 \

View file

@ -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():
""" """