Asynchronize the world

This commit is contained in:
thec0sm0s
2020-09-30 13:12:53 +05:30
parent 256d6cae81
commit bc15de5119
9 changed files with 74 additions and 70 deletions

View File

@@ -1,5 +1,5 @@
import cachetools
import requests
import aiohttp
import typing
import json
import os
@@ -56,23 +56,23 @@ class DiscordOAuth2HttpClient(abc.ABC):
@staticmethod
@abc.abstractmethod
def save_authorization_token(token: dict):
async def save_authorization_token(token: dict):
raise NotImplementedError
@staticmethod
@abc.abstractmethod
def get_authorization_token() -> dict:
async def get_authorization_token() -> dict:
raise NotImplementedError
def _fetch_token(self, state):
discord = self._make_session(state=state)
return discord.fetch_token(
async def _fetch_token(self, state):
discord = await self._make_session(state=state)
return await discord.fetch_token(
configs.DISCORD_TOKEN_URL,
client_secret=self.__client_secret,
authorization_response=request.url
)
def _make_session(self, token: str = None, state: str = None, scope: list = None) -> OAuth2Session:
async def _make_session(self, token: str = None, state: str = None, scope: list = None) -> OAuth2Session:
"""A low level method used for creating OAuth2 session.
Parameters
@@ -93,7 +93,7 @@ class DiscordOAuth2HttpClient(abc.ABC):
"""
return OAuth2Session(
client_id=self.client_id,
token=token or self.get_authorization_token(),
token=token or await self.get_authorization_token(),
state=state or session.get("DISCORD_OAUTH2_STATE"),
scope=scope,
redirect_uri=self.redirect_uri,
@@ -104,7 +104,7 @@ class DiscordOAuth2HttpClient(abc.ABC):
auto_refresh_url=configs.DISCORD_TOKEN_URL,
token_updater=self.save_authorization_token)
def request(self, route: str, method="GET", data=None, oauth=True, **kwargs) -> typing.Union[dict, str]:
async def request(self, route: str, method="GET", data=None, oauth=True, **kwargs) -> typing.Union[dict, str]:
"""Sends HTTP request to provided route or discord endpoint.
Note
@@ -137,20 +137,22 @@ class DiscordOAuth2HttpClient(abc.ABC):
"""
route = configs.DISCORD_API_BASE_URL + route
response = self._make_session(
).request(method, route, data, **kwargs) if oauth else requests.request(method, route, data=data, **kwargs)
discord = await self._make_session()
async with (discord.request(
method, route, data, **kwargs
) if oauth else aiohttp.request(method, route, data=data, **kwargs)) as response:
if response.status_code == 401:
raise exceptions.Unauthorized
if response.status_code == 429:
raise exceptions.RateLimited(response)
if response.status == 401:
raise exceptions.Unauthorized
if response.status == 429:
raise exceptions.RateLimited(response)
try:
return response.json()
except json.JSONDecodeError:
return response.text
try:
return await response.json()
except json.JSONDecodeError:
return await response.text()
def bot_request(self, route: str, method="GET", **kwargs) -> typing.Union[dict, str]:
async def bot_request(self, route: str, method="GET", **kwargs) -> typing.Union[dict, str]:
"""Make HTTP request to specified endpoint with bot token as authorization headers.
Parameters
@@ -175,4 +177,4 @@ class DiscordOAuth2HttpClient(abc.ABC):
"""
headers = {"Authorization": f"Bot {self.__bot_token}"}
return self.request(route, method=method, oauth=False, headers=headers, **kwargs)
return await self.request(route, method=method, oauth=False, headers=headers, **kwargs)

View File

@@ -57,7 +57,7 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
def __get_state():
return session.pop("DISCORD_OAUTH2_STATE", str())
def create_session(
async def create_session(
self, scope: list = None, *, data: dict = None, prompt: bool = True,
permissions: typing.Union[discord.Permissions, int] = 0, **params
):
@@ -95,7 +95,7 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
state = jwt.encode(data or dict(), current_app.config["SECRET_KEY"]).decode(encoding="utf-8")
discord_session = self._make_session(scope=scope, state=state)
discord_session = await self._make_session(scope=scope, state=state)
authorization_url, state = discord_session.authorization_url(configs.DISCORD_AUTHORIZATION_BASE_URL)
self.__save_state(state)
@@ -117,7 +117,7 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
return redirect(authorization_url)
@staticmethod
def save_authorization_token(token: dict):
async def save_authorization_token(token: dict):
"""A staticmethod which saves a dict containing Discord OAuth2 token and other secrets to the user's cookies.
Meaning by default, it uses client side session handling.
@@ -128,7 +128,7 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
session["DISCORD_OAUTH2_TOKEN"] = token
@staticmethod
def get_authorization_token() -> dict:
async def get_authorization_token() -> dict:
"""A static method which returns a dict containing Discord OAuth2 token and other secrets which was saved
previously by `:py:meth:`quart_discord.DiscordOAuth2Session.save_authorization_token` from user's cookies.
@@ -151,8 +151,8 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
raise exceptions.HttpException(error)
state = self.__get_state()
token = self._fetch_token(state)
self.save_authorization_token(token)
token = await self._fetch_token(state)
await self.save_authorization_token(token)
return jwt.decode(state, current_app.config["SECRET_KEY"])
@@ -172,13 +172,12 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
except KeyError:
pass
@property
def authorized(self):
async def authorized(self):
"""A boolean indicating whether current session has authorization token or not."""
return self._make_session().authorized
return (await self._make_session()).authorized
@staticmethod
def fetch_user() -> models.User:
async def fetch_user() -> models.User:
"""This method returns user object from the internal cache if it exists otherwise makes an API call to do so.
Returns
@@ -186,10 +185,10 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
quart_discord.models.User
"""
return models.User.get_from_cache() or models.User.fetch_from_api()
return models.User.get_from_cache() or await models.User.fetch_from_api()
@staticmethod
def fetch_connections() -> list:
async def fetch_connections() -> list:
"""This method returns list of user connection objects from internal cache if it exists otherwise
makes an API call to do so.
@@ -206,10 +205,10 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
except AttributeError:
pass
return models.UserConnection.fetch_from_api()
return await models.UserConnection.fetch_from_api()
@staticmethod
def fetch_guilds() -> list:
async def fetch_guilds() -> list:
"""This method returns list of guild objects from internal cache if it exists otherwise makes an API
call to do so.
@@ -226,4 +225,4 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
except AttributeError:
pass
return models.Guild.fetch_from_api()
return await models.Guild.fetch_from_api()

View File

@@ -22,20 +22,20 @@ class DiscordModelsBase(metaclass=DiscordModelsMeta):
self._payload = payload
@staticmethod
def _request(*args, **kwargs):
async def _request(*args, **kwargs):
"""A shorthand to :py:func:quart_discord.request`. It uses Quart current_app local proxy to get the
Quart-Discord client.
"""
return current_app.discord.request(*args, **kwargs)
return await current_app.discord.request(*args, **kwargs)
@staticmethod
def _bot_request(*args, **kwargs):
async def _bot_request(*args, **kwargs):
"""A shorthand to :py:func:quart_discord.bot_request`."""
return current_app.discord.bot_request(*args, **kwargs)
return await current_app.discord.bot_request(*args, **kwargs)
@classmethod
def fetch_from_api(cls):
async def fetch_from_api(cls):
"""A class method which returns an instance or list of instances of this model by implicitly making an
API call to Discord.
@@ -48,7 +48,7 @@ class DiscordModelsBase(metaclass=DiscordModelsMeta):
"""
request_method = cls._bot_request if cls.BOT else cls._request
payload = request_method(cls.ROUTE)
payload = await request_method(cls.ROUTE)
if cls.MANY:
return [cls(_) for _ in payload]
return cls(payload)

View File

@@ -57,7 +57,7 @@ class UserConnection(DiscordModelsBase):
return bool(self.visibility)
@classmethod
def fetch_from_api(cls, cache=True):
async def fetch_from_api(cls, cache=True):
"""A class method which returns an instance or list of instances of this model by implicitly making an
API call to Discord. If an instance of :py:class:`quart_discord.User` exists in the users internal cache
who are attached to these connections then, the cached property :py:attr:`quart_discord.User.connections`
@@ -74,7 +74,7 @@ class UserConnection(DiscordModelsBase):
List of instances of :py:class:`quart_discord.UserConnection` to which this user belongs.
"""
connections = super().fetch_from_api()
connections = await super().fetch_from_api()
if cache:
user = current_app.discord.users_cache.get(current_app.discord.user_id)

View File

@@ -66,7 +66,7 @@ class Guild(DiscordModelsBase):
return configs.DISCORD_GUILD_ICON_BASE_URL.format(guild_id=self.id, icon_hash=self.icon_hash)
@classmethod
def fetch_from_api(cls, cache=True):
async def fetch_from_api(cls, cache=True):
"""A class method which returns an instance or list of instances of this model by implicitly making an
API call to Discord. If an instance of :py:class:`quart_discord.User` exists in the users internal cache
who belongs to these guilds then, the cached property :py:attr:`quart_discord.User.guilds` is updated.
@@ -82,7 +82,7 @@ class Guild(DiscordModelsBase):
List of instances of :py:class:`quart_discord.Guild` to which this user belongs.
"""
guilds = super().fetch_from_api()
guilds = await super().fetch_from_api()
if cache:
user = current_app.discord.users_cache.get(current_app.discord.user_id)

View File

@@ -125,7 +125,7 @@ class User(DiscordModelsBase):
return False
@classmethod
def fetch_from_api(cls, guilds=False, connections=False):
async def fetch_from_api(cls, guilds=False, connections=False):
"""A class method which returns an instance of this model by implicitly making an
API call to Discord. The user returned from API will always be cached and update in internal cache.
@@ -144,14 +144,14 @@ class User(DiscordModelsBase):
An instance of this model itself.
[cls, ...]
List of instances of this model when many of these models exist."""
self = super().fetch_from_api()
self = await super().fetch_from_api()
current_app.discord.users_cache.update({self.id: self})
session["DISCORD_USER_ID"] = self.id
if guilds:
self.fetch_guilds()
await self.fetch_guilds()
if connections:
self.fetch_connections()
await self.fetch_connections()
return self
@@ -169,7 +169,7 @@ class User(DiscordModelsBase):
"""
return current_app.discord.users_cache.get(current_app.discord.user_id)
def add_to_guild(self, guild_id) -> dict:
async def add_to_guild(self, guild_id) -> dict:
"""Method to add user to the guild, provided OAuth2 session has already been created with ``guilds.join`` scope.
Parameters
@@ -189,12 +189,12 @@ class User(DiscordModelsBase):
"""
try:
data = {"access_token": current_app.discord.get_authorization_token()["access_token"]}
data = {"access_token": await current_app.discord.get_authorization_token()["access_token"]}
except KeyError:
raise exceptions.Unauthorized
return self._bot_request(f"/guilds/{guild_id}/members/{self.id}", method="PUT", json=data) or dict()
return await self._bot_request(f"/guilds/{guild_id}/members/{self.id}", method="PUT", json=data) or dict()
def fetch_guilds(self) -> list:
async def fetch_guilds(self) -> list:
"""A method which makes an API call to Discord to get user's guilds. It prepares the internal guilds cache
and returns list of all guilds the user is member of.
@@ -204,10 +204,10 @@ class User(DiscordModelsBase):
List of :py:class:`quart_discord.Guilds` instances.
"""
self._guilds = {guild.id: guild for guild in Guild.fetch_from_api(cache=False)}
self._guilds = {guild.id: guild for guild in await Guild.fetch_from_api(cache=False)}
return self.guilds
def fetch_connections(self) -> list:
async def fetch_connections(self) -> list:
"""A method which makes an API call to Discord to get user's connections. It prepares the internal connection
cache and returns list of all connection instances.
@@ -217,7 +217,7 @@ class User(DiscordModelsBase):
A list of :py:class:`quart_discord.UserConnection` instances.
"""
self.connections = UserConnection.fetch_from_api(cache=False)
self.connections = await UserConnection.fetch_from_api(cache=False)
return self.connections

View File

@@ -44,7 +44,7 @@ def requires_authorization(view):
@functools.wraps(view)
async def wrapper(*args, **kwargs):
if not current_app.discord.authorized:
if not await current_app.discord.authorized():
raise exceptions.Unauthorized
return await view(*args, **kwargs)

View File

@@ -1,8 +1,9 @@
Quart
pyjwt
requests
aiohttp
oauthlib
discord.py
cachetools
setuptools
requests_oauthlib
git+https://github.com/thec0sm0s/requests-oauthlib.git@async

View File

@@ -21,7 +21,7 @@ HYPERLINK = '<a href="{}">{}</a>'
@app.route("/")
async def index():
if not discord.authorized:
if not await discord.authorized():
return f"""
{HYPERLINK.format(url_for(".login"), "Login")} <br />
{HYPERLINK.format(url_for(".login_with_data"), "Login with custom data")} <br />
@@ -39,22 +39,24 @@ async def index():
@app.route("/login/")
async def login():
return discord.create_session()
return await discord.create_session()
@app.route("/login-data/")
async def login_with_data():
return discord.create_session(data=dict(redirect="/me/", coupon="15off", number=15, zero=0, status=False))
return await discord.create_session(data=dict(redirect="/me/", coupon="15off", number=15, zero=0, status=False))
@app.route("/invite-bot/")
async def invite_bot():
return discord.create_session(scope=["bot"], permissions=8, guild_id=464488012328468480, disable_guild_select=True)
return await discord.create_session(
scope=["bot"], permissions=8, guild_id=464488012328468480, disable_guild_select=True
)
@app.route("/invite-oauth/")
async def invite_oauth():
return discord.create_session(scope=["bot", "identify"], permissions=8)
return await discord.create_session(scope=["bot", "identify"], permissions=8)
@app.route("/callback/")
@@ -66,7 +68,7 @@ async def callback():
@app.route("/me/")
async def me():
user = discord.fetch_user()
user = await discord.fetch_user()
return f"""
<html>
<head>
@@ -84,20 +86,20 @@ async def me():
@app.route("/me/guilds/")
async def user_guilds():
guilds = discord.fetch_guilds()
guilds = await discord.fetch_guilds()
return "<br />".join([f"[ADMIN] {g.name}" if g.permissions.administrator else g.name for g in guilds])
@app.route("/add_to/<int:guild_id>/")
async def add_to_guild(guild_id):
user = discord.fetch_user()
return user.add_to_guild(guild_id)
user = await discord.fetch_user()
return await user.add_to_guild(guild_id)
@app.route("/me/connections/")
async def my_connections():
user = discord.fetch_user()
connections = discord.fetch_connections()
user = await discord.fetch_user()
connections = await discord.fetch_connections()
return f"""
<html>
<head>