mirror of
https://github.com/yawaflua/Flask-Discord.git
synced 2025-12-09 20:09:30 +02:00
Asynchronize the world
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
Quart
|
||||
pyjwt
|
||||
requests
|
||||
aiohttp
|
||||
oauthlib
|
||||
discord.py
|
||||
cachetools
|
||||
setuptools
|
||||
requests_oauthlib
|
||||
git+https://github.com/thec0sm0s/requests-oauthlib.git@async
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user