mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-11 09:55:21 +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())
|
||||
|
||||
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)
|
||||
|
||||
__version__ = VERSION.version
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import aiohttp
|
|||
from . import api
|
||||
from ..types import ParseMode, base
|
||||
from ..utils import json
|
||||
from ..utils.auth_widget import check_token
|
||||
|
||||
|
||||
class BaseBot:
|
||||
|
|
@ -67,17 +68,17 @@ class BaseBot:
|
|||
self.parse_mode = parse_mode
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
asyncio.ensure_future(self.close())
|
||||
|
||||
def close(self):
|
||||
async def close(self):
|
||||
"""
|
||||
Close all client sessions
|
||||
"""
|
||||
if self.session and not self.session.closed:
|
||||
await self.session.close()
|
||||
for session in self._temp_sessions:
|
||||
if not session.closed:
|
||||
session.close()
|
||||
if self.session and not self.session.closed:
|
||||
self.session.close()
|
||||
await session.close()
|
||||
|
||||
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
|
||||
def parse_mode(self):
|
||||
self.parse_mode = None
|
||||
|
||||
def check_auth_widget(self, data):
|
||||
return check_token(data, self.__token)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
import typing
|
||||
import weakref
|
||||
|
||||
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,
|
||||
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._port = port
|
||||
self._db = db
|
||||
|
|
@ -47,77 +48,113 @@ class RethinkDBStorage(BaseStorage):
|
|||
self._timeout = timeout
|
||||
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._lock = asyncio.Lock(loop=self._loop)
|
||||
|
||||
async def connection(self):
|
||||
async def get_connection(self):
|
||||
"""
|
||||
Get or create connection.
|
||||
"""
|
||||
async with self._lock: # thread-safe
|
||||
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,
|
||||
password=self._password, timeout=self._timeout, ssl=self._ssl,
|
||||
io_loop=self._loop)
|
||||
return self._connection
|
||||
try:
|
||||
while True:
|
||||
conn: r.Connection = self._queue.get_nowait()
|
||||
if conn.is_open():
|
||||
break
|
||||
try:
|
||||
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):
|
||||
"""
|
||||
Close connection.
|
||||
Close all connections.
|
||||
"""
|
||||
if self._connection and self._connection.is_open():
|
||||
await self._connection.close()
|
||||
self._connection = None
|
||||
while True:
|
||||
try:
|
||||
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):
|
||||
"""
|
||||
Checks if connection is closed.
|
||||
"""
|
||||
if self._connection:
|
||||
if len(self._outstanding_connections) != 0 and self._queue.qsize() != 0:
|
||||
raise ConnectionNotClosed
|
||||
return True
|
||||
|
||||
async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||
conn = await self.connection()
|
||||
return await r.table(self._table).get(chat)[user]['state'].default(default or '').run(conn)
|
||||
conn = await self.get_connection()
|
||||
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,
|
||||
default: typing.Optional[str] = None) -> typing.Dict:
|
||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||
conn = await self.connection()
|
||||
return await r.table(self._table).get(chat)[user]['data'].default(default or {}).run(conn)
|
||||
conn = await self.get_connection()
|
||||
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,
|
||||
state: typing.Optional[typing.AnyStr] = None):
|
||||
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):
|
||||
await r.table(self._table).get(chat).update({user: {'state': state}}).run(conn)
|
||||
else:
|
||||
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,
|
||||
data: typing.Dict = None):
|
||||
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):
|
||||
await r.table(self._table).get(chat).update({user: {'data': r.literal(data)}}).run(conn)
|
||||
else:
|
||||
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,
|
||||
data: typing.Dict = None,
|
||||
**kwargs):
|
||||
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):
|
||||
await r.table(self._table).get(chat).update({user: {'data': data}}).run(conn)
|
||||
else:
|
||||
await r.table(self._table).insert({'id': chat, user: {'data': data}}).run(conn)
|
||||
await self.put_connection(conn)
|
||||
|
||||
def has_bucket(self):
|
||||
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,
|
||||
default: typing.Optional[dict] = None) -> typing.Dict:
|
||||
chat, user = map(str, self.check_address(chat=chat, user=user))
|
||||
conn = await self.connection()
|
||||
return await r.table(self._table).get(chat)[user]['bucket'].default(default or {}).run(conn)
|
||||
conn = await self.get_connection()
|
||||
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,
|
||||
bucket: typing.Dict = None):
|
||||
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):
|
||||
await r.table(self._table).get(chat).update({user: {'bucket': r.literal(bucket)}}).run(conn)
|
||||
else:
|
||||
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,
|
||||
user: typing.Union[str, int, None] = None, bucket: typing.Dict = None,
|
||||
**kwargs):
|
||||
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):
|
||||
await r.table(self._table).get(chat).update({user: {'bucket': bucket}}).run(conn)
|
||||
else:
|
||||
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
|
||||
|
||||
: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 = []
|
||||
|
||||
items = (await r.table(self._table).run(conn)).items
|
||||
|
|
@ -164,11 +205,14 @@ class RethinkDBStorage(BaseStorage):
|
|||
user = int(key)
|
||||
result.append((chat, user))
|
||||
|
||||
await self.put_connection(conn)
|
||||
|
||||
return result
|
||||
|
||||
async def reset_all(self):
|
||||
"""
|
||||
Reset states in DB
|
||||
"""
|
||||
conn = await self.connection()
|
||||
conn = await self.get_connection()
|
||||
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):
|
||||
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)
|
||||
|
||||
async def set_data(self, data: typing.Dict = None):
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from . import base
|
|||
from . import fields
|
||||
from .animation import Animation
|
||||
from .audio import Audio
|
||||
from .auth_widget_data import AuthWidgetData
|
||||
from .callback_game import CallbackGame
|
||||
from .callback_query import CallbackQuery
|
||||
from .chat import Chat, ChatActions, ChatType
|
||||
|
|
@ -56,6 +57,7 @@ __all__ = (
|
|||
'AllowedUpdates',
|
||||
'Animation',
|
||||
'Audio',
|
||||
'AuthWidgetData',
|
||||
'CallbackGame',
|
||||
'CallbackQuery',
|
||||
'Chat',
|
||||
|
|
|
|||
|
|
@ -15,11 +15,3 @@ class Audio(base.TelegramObject, mixins.Downloadable):
|
|||
title: base.String = fields.Field()
|
||||
mime_type: base.String = 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:
|
||||
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`"""
|
||||
await self.bot.answer_callback_query(callback_query_id=self.id, text=text,
|
||||
show_alert=show_alert, url=url, cache_time=cache_time)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.id)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ class Chat(base.TelegramObject):
|
|||
sticker_set_name: base.String = fields.Field()
|
||||
can_set_sticker_set: base.Boolean = fields.Field()
|
||||
|
||||
def __hash__(self):
|
||||
return self.id
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
if self.type == ChatType.PRIVATE:
|
||||
|
|
|
|||
|
|
@ -73,3 +73,6 @@ class ChatPhoto(base.TelegramObject):
|
|||
|
||||
async def get_big_file(self):
|
||||
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:
|
||||
name += ' ' + self.last_name
|
||||
return name
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.phone_number)
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ class Message(base.TelegramObject):
|
|||
return text
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -630,6 +630,30 @@ class Message(base.TelegramObject):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
Pin message
|
||||
|
|
|
|||
|
|
@ -44,3 +44,6 @@ class Downloadable:
|
|||
return self
|
||||
else:
|
||||
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 fields
|
||||
from . import mixins
|
||||
from .mask_position import MaskPosition
|
||||
from .photo_size import PhotoSize
|
||||
|
||||
|
||||
class Sticker(base.TelegramObject):
|
||||
class Sticker(base.TelegramObject, mixins.Downloadable):
|
||||
"""
|
||||
This object represents a sticker.
|
||||
|
||||
|
|
@ -18,11 +19,3 @@ class Sticker(base.TelegramObject):
|
|||
set_name: base.String = fields.Field()
|
||||
mask_position: MaskPosition = fields.Field(base=MaskPosition)
|
||||
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)
|
||||
|
||||
def __hash__(self):
|
||||
return self.id + \
|
||||
hash(self.is_bot) + \
|
||||
hash(self.full_name) + \
|
||||
(hash(self.username) if self.username else 0)
|
||||
return self.id
|
||||
|
||||
def __int__(self):
|
||||
return self.id
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from . import base
|
||||
from . import fields
|
||||
from . import mixins
|
||||
from .photo_size import PhotoSize
|
||||
|
||||
|
||||
class Video(base.TelegramObject):
|
||||
class Video(base.TelegramObject, mixins.Downloadable):
|
||||
"""
|
||||
This object represents a video file.
|
||||
|
||||
|
|
@ -16,11 +17,3 @@ class Video(base.TelegramObject):
|
|||
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
||||
mime_type: base.String = 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 fields
|
||||
from . import mixins
|
||||
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).
|
||||
|
||||
|
|
@ -14,11 +15,3 @@ class VideoNote(base.TelegramObject):
|
|||
duration: base.Integer = fields.Field()
|
||||
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
||||
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 fields
|
||||
from . import mixins
|
||||
|
||||
|
||||
class Voice(base.TelegramObject):
|
||||
class Voice(base.TelegramObject, mixins.Downloadable):
|
||||
"""
|
||||
This object represents a voice note.
|
||||
|
||||
|
|
@ -12,11 +13,3 @@ class Voice(base.TelegramObject):
|
|||
duration: base.Integer = fields.Field()
|
||||
mime_type: base.String = 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.wait_closed()
|
||||
|
||||
await dispatcher.bot.close()
|
||||
|
||||
|
||||
async def _wh_shutdown(app):
|
||||
callback = app.get('_shutdown_callback', None)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/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:
|
||||
python adwanced_executor_example.py \
|
||||
|
|
|
|||
7
setup.py
7
setup.py
|
|
@ -1,12 +1,19 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
from distutils.core import setup
|
||||
from warnings import warn
|
||||
|
||||
from pip.req import parse_requirements
|
||||
from setuptools import PackageFinder
|
||||
|
||||
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():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue