mirror of
https://github.com/yawaflua/Flask-Discord.git
synced 2026-02-04 18:24:15 +02:00
Merge remote-tracking branch 'quart-discord/master' into quart-discord
# Conflicts: # README.md # docs/introduction.rst # quart_discord/__init__.py # quart_discord/_http.py
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -57,7 +57,7 @@ coverage.xml
|
|||||||
local_settings.py
|
local_settings.py
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
|
||||||
# Flask stuff:
|
# Quart stuff:
|
||||||
instance/
|
instance/
|
||||||
.webassets-cache
|
.webassets-cache
|
||||||
|
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -1,27 +1,24 @@
|
|||||||
# Flask-Discord
|
# Quart-Discord
|
||||||
[](https://pypi.org/project/Flask-Discord/) [](https://flask-discord.readthedocs.io/en/latest/) [](https://discord.gg/7CrQEyP)
|
[](https://pypi.org/project/Quart-Discord/) [](https://quart-discord.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
Discord OAuth2 extension for Flask.
|
Discord OAuth2 extension for Quart.
|
||||||
|
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
To install current latest release you can use following command:
|
To install current latest release you can use following command:
|
||||||
```sh
|
```sh
|
||||||
python3 -m pip install Flask-Discord
|
python3 -m pip install Quart-Discord
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Basic Example
|
### Basic Example
|
||||||
```python
|
```python
|
||||||
import os
|
from quart import Quart, redirect, url_for
|
||||||
|
from quart_discord import DiscordOAuth2Session, requires_authorization, Unauthorized
|
||||||
|
|
||||||
from flask import Flask, redirect, url_for
|
app = Quart(__name__)
|
||||||
from flask_discord import DiscordOAuth2Session, requires_authorization, Unauthorized
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app.secret_key = b"random bytes representing quart secret key"
|
||||||
|
|
||||||
app.secret_key = b"random bytes representing flask secret key"
|
|
||||||
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "true" # !! Only in development environment.
|
|
||||||
|
|
||||||
app.config["DISCORD_CLIENT_ID"] = 490732332240863233 # Discord client ID.
|
app.config["DISCORD_CLIENT_ID"] = 490732332240863233 # Discord client ID.
|
||||||
app.config["DISCORD_CLIENT_SECRET"] = "" # Discord client secret.
|
app.config["DISCORD_CLIENT_SECRET"] = "" # Discord client secret.
|
||||||
@@ -32,25 +29,25 @@ discord = DiscordOAuth2Session(app)
|
|||||||
|
|
||||||
|
|
||||||
@app.route("/login/")
|
@app.route("/login/")
|
||||||
def login():
|
async def login():
|
||||||
return discord.create_session()
|
return await discord.create_session()
|
||||||
|
|
||||||
|
|
||||||
@app.route("/callback/")
|
@app.route("/callback/")
|
||||||
def callback():
|
async def callback():
|
||||||
discord.callback()
|
await discord.callback()
|
||||||
return redirect(url_for(".me"))
|
return redirect(url_for(".me"))
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(Unauthorized)
|
@app.errorhandler(Unauthorized)
|
||||||
def redirect_unauthorized(e):
|
async def redirect_unauthorized(e):
|
||||||
return redirect(url_for("login"))
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/me/")
|
@app.route("/me/")
|
||||||
@requires_authorization
|
@requires_authorization
|
||||||
def me():
|
async def me():
|
||||||
user = discord.fetch_user()
|
user = await discord.fetch_user()
|
||||||
return f"""
|
return f"""
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -70,8 +67,8 @@ For an example to the working application, check [`test_app.py`](tests/test_app.
|
|||||||
|
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
* Flask
|
* Quart
|
||||||
* requests_oauthlib
|
* Async-OAuthlib
|
||||||
* cachetools
|
* cachetools
|
||||||
* discord.py
|
* discord.py
|
||||||
|
|
||||||
@@ -80,4 +77,4 @@ For an example to the working application, check [`test_app.py`](tests/test_app.
|
|||||||
Head over to [documentation] for full API reference.
|
Head over to [documentation] for full API reference.
|
||||||
|
|
||||||
|
|
||||||
[documentation]: https://flask-discord.readthedocs.io/en/latest/
|
[documentation]: https://quart-discord.readthedocs.io/en/latest/
|
||||||
|
|||||||
24
docs/api.rst
24
docs/api.rst
@@ -8,11 +8,11 @@ attributes and available methods.
|
|||||||
Discord OAuth2 Client
|
Discord OAuth2 Client
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
.. autoclass:: flask_discord.DiscordOAuth2Session
|
.. autoclass:: quart_discord.DiscordOAuth2Session
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
.. autoclass:: flask_discord._http.DiscordOAuth2HttpClient
|
.. autoclass:: quart_discord._http.DiscordOAuth2HttpClient
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
@@ -20,23 +20,23 @@ Discord OAuth2 Client
|
|||||||
Models
|
Models
|
||||||
------
|
------
|
||||||
|
|
||||||
.. autoclass:: flask_discord.models.Guild
|
.. autoclass:: quart_discord.models.Guild
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
.. autoclass:: flask_discord.models.User
|
.. autoclass:: quart_discord.models.User
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
.. autoclass:: flask_discord.models.Bot
|
.. autoclass:: quart_discord.models.Bot
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
.. autoclass:: flask_discord.models.Integration
|
.. autoclass:: quart_discord.models.Integration
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
.. autoclass:: flask_discord.models.UserConnection
|
.. autoclass:: quart_discord.models.UserConnection
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
@@ -44,20 +44,20 @@ Models
|
|||||||
Utilities
|
Utilities
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. autodecorator:: flask_discord.requires_authorization
|
.. autodecorator:: quart_discord.requires_authorization
|
||||||
|
|
||||||
|
|
||||||
Exceptions
|
Exceptions
|
||||||
----------
|
----------
|
||||||
|
|
||||||
.. autoclass:: flask_discord.HttpException
|
.. autoclass:: quart_discord.HttpException
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: flask_discord.RateLimited
|
.. autoclass:: quart_discord.RateLimited
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: flask_discord.Unauthorized
|
.. autoclass:: quart_discord.Unauthorized
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: flask_discord.AccessDenied
|
.. autoclass:: quart_discord.AccessDenied
|
||||||
:members:
|
:members:
|
||||||
|
|||||||
14
docs/conf.py
14
docs/conf.py
@@ -14,16 +14,16 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0, os.path.abspath('..'))
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
sys.path.append('../flask_discord/')
|
sys.path.append('../quart_discord/')
|
||||||
|
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
project = 'Flask-Discord'
|
project = 'Quart-Discord'
|
||||||
copyright = '2019, □ | The Cosmos'
|
copyright = '2020, Philip Dowie'
|
||||||
author = '□ | The Cosmos'
|
author = 'Philip Dowie'
|
||||||
|
|
||||||
with open('../flask_discord/__init__.py') as f:
|
with open('../quart_discord/__init__.py') as f:
|
||||||
ver = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1)
|
ver = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1)
|
||||||
# The short X.Y version
|
# The short X.Y version
|
||||||
version = ver
|
version = ver
|
||||||
@@ -48,9 +48,9 @@ extensions = [
|
|||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
'python': ('https://docs.python.org/3', None),
|
'python': ('https://docs.python.org/3', None),
|
||||||
'discord': ('https://discordpy.readthedocs.io/en/latest/', None),
|
'discord': ('https://discordpy.readthedocs.io/en/latest/', None),
|
||||||
'flask': ('https://flask.palletsprojects.com/en/1.1.x/', None),
|
'quart': ('https://pgjones.gitlab.io/quart/', None),
|
||||||
'cachetools': ('https://cachetools.readthedocs.io/en/stable/', None),
|
'cachetools': ('https://cachetools.readthedocs.io/en/stable/', None),
|
||||||
'requests_oauthlib': ('https://requests-oauthlib.readthedocs.io/en/latest/', None)
|
'async_oauthlib': ('https://async-oauthlib.readthedocs.io/en/latest/', None)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
.. Flask-Discord documentation master file, created by
|
.. Quart-Discord documentation master file, created by
|
||||||
sphinx-quickstart on Wed May 8 08:29:45 2019.
|
sphinx-quickstart on Wed May 8 08:29:45 2019.
|
||||||
You can adapt this file completely to your liking, but it should at least
|
You can adapt this file completely to your liking, but it should at least
|
||||||
contain the root `toctree` directive.
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
Welcome to Flask-Discord's documentation!
|
Welcome to Quart-Discord's documentation!
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
.. image:: /images/background.jpg
|
.. image:: /images/background.jpg
|
||||||
|
|
||||||
Flask-Discord is an extension made for Flask which makes implementation of
|
Quart-Discord is an extension made for Quart which makes implementation of
|
||||||
Discord's OAuth2 API easier.
|
Discord's OAuth2 API easier.
|
||||||
|
|
||||||
**Features**
|
**Features**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
Introduction
|
Introduction
|
||||||
============
|
============
|
||||||
|
|
||||||
Flask-Discord is an extension for Flask - Python web framework which
|
Quart-Discord is an extension for Quart - Python web framework which
|
||||||
makes easy implementation of Discord OAuth2 API. After creating a discord
|
makes easy implementation of Discord OAuth2 API. After creating a discord
|
||||||
client object, one can easily request authorization and hence any of the
|
client object, one can easily request authorization and hence any of the
|
||||||
resources provided by the discord OAuth2 API under the available scope
|
resources provided by the discord OAuth2 API under the available scope
|
||||||
@@ -14,14 +14,14 @@ permissions.
|
|||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
- **Flask**
|
- **Quart**
|
||||||
This is an Flask extension.
|
This is a Quart extension.
|
||||||
|
|
||||||
- **requests_oauthlib**
|
- **requests_oauthlib**
|
||||||
It also requires requests_oauthlib to make OAuth2 sessions with discord.
|
It also requires requests_oauthlib to make OAuth2 sessions with discord.
|
||||||
|
|
||||||
- **cachetools**
|
- **cachetools**
|
||||||
Flask Discord supports caching discord objects to boost the performance when page loads.
|
Quart Discord supports caching discord objects to boost the performance when page loads.
|
||||||
|
|
||||||
- **discord.py**
|
- **discord.py**
|
||||||
Makes use of discord.py for re-using many Discord models.
|
Makes use of discord.py for re-using many Discord models.
|
||||||
@@ -29,15 +29,15 @@ Requirements
|
|||||||
Installing
|
Installing
|
||||||
----------
|
----------
|
||||||
|
|
||||||
You can install Flask-Discord directly from PyPI using PIP and following command
|
You can install Quart-Discord directly from PyPI using PIP and following command
|
||||||
in shell or command prompt: ::
|
in shell or command prompt: ::
|
||||||
|
|
||||||
python3 -m pip install -U Flask-Discord
|
python3 -m pip install -U Quart-Discord
|
||||||
|
|
||||||
You can also install the latest development version (**maybe unstable/broken**) by
|
You can also install the latest development version (**maybe unstable/broken**) by
|
||||||
using following command: ::
|
using following command: ::
|
||||||
|
|
||||||
python3 -m pip install -U git+https://github.com/thec0sm0s/Flask-Discord.git@dev
|
python3 -m pip install -U git+https://github.com/jnawk/Quart-Discord.git@dev
|
||||||
|
|
||||||
|
|
||||||
Basic Usage
|
Basic Usage
|
||||||
@@ -48,14 +48,12 @@ in exchange for fetching user's details and display them on web page.
|
|||||||
|
|
||||||
.. code-block:: python3
|
.. code-block:: python3
|
||||||
|
|
||||||
from flask import Flask, redirect, url_for
|
from quart import Quart, redirect, url_for
|
||||||
from flask_discord import DiscordOAuth2Session, requires_authorization, Unauthorized
|
from quart_discord import DiscordOAuth2Session, requires_authorization, Unauthorized
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Quart(__name__)
|
||||||
|
|
||||||
app.secret_key = b"random bytes representing flask secret key"
|
app.secret_key = b"random bytes representing quart secret key"
|
||||||
# OAuth2 must make use of HTTPS in production environment.
|
|
||||||
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "true" # !! Only in development environment.
|
|
||||||
|
|
||||||
app.config["DISCORD_CLIENT_ID"] = 490732332240863233 # Discord client ID.
|
app.config["DISCORD_CLIENT_ID"] = 490732332240863233 # Discord client ID.
|
||||||
app.config["DISCORD_CLIENT_SECRET"] = "" # Discord client secret.
|
app.config["DISCORD_CLIENT_SECRET"] = "" # Discord client secret.
|
||||||
@@ -67,25 +65,25 @@ in exchange for fetching user's details and display them on web page.
|
|||||||
|
|
||||||
|
|
||||||
@app.route("/login/")
|
@app.route("/login/")
|
||||||
def login():
|
async def login():
|
||||||
return discord.create_session()
|
return await discord.create_session()
|
||||||
|
|
||||||
|
|
||||||
@app.route("/callback/")
|
@app.route("/callback/")
|
||||||
def callback():
|
async def callback():
|
||||||
discord.callback()
|
await discord.callback()
|
||||||
return redirect(url_for(".me"))
|
return redirect(url_for(".me"))
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(Unauthorized)
|
@app.errorhandler(Unauthorized)
|
||||||
def redirect_unauthorized(e):
|
async def redirect_unauthorized(e):
|
||||||
return redirect(url_for("login"))
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/me/")
|
@app.route("/me/")
|
||||||
@requires_authorization
|
@requires_authorization
|
||||||
def me():
|
async def me():
|
||||||
user = discord.fetch_user()
|
user = await discord.fetch_user()
|
||||||
return f"""
|
return f"""
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -99,26 +97,3 @@ in exchange for fetching user's details and display them on web page.
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
**Lazy initialization with flask factory pattern**
|
|
||||||
|
|
||||||
.. code-block:: python3
|
|
||||||
|
|
||||||
from flask_discord import DiscordOAuth2Session
|
|
||||||
|
|
||||||
discord = DiscordOAuth2Session()
|
|
||||||
|
|
||||||
def get_app():
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
app.secret_key = b"random bytes representing flask secret key"
|
|
||||||
# OAuth2 must make use of HTTPS in production environment.
|
|
||||||
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "true" # !! Only in development environment.
|
|
||||||
app.config["DISCORD_CLIENT_ID"] = 490732332240863233 # Discord client ID.
|
|
||||||
app.config["DISCORD_CLIENT_SECRET"] = "" # Discord client secret.
|
|
||||||
app.config["DISCORD_REDIRECT_URI"] = "" # URL to your callback endpoint.
|
|
||||||
app.config["DISCORD_BOT_TOKEN"] = "" # Required to access BOT resources.
|
|
||||||
|
|
||||||
discord.init_app(app)
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.1.61"
|
__version__ = "0.3.0"
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import cachetools
|
import cachetools
|
||||||
import requests
|
import aiohttp
|
||||||
import typing
|
import typing
|
||||||
import json
|
import os
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
from . import configs
|
from . import configs
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
|
|
||||||
from flask import session, request
|
from quart import session, request
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from requests_oauthlib import OAuth2Session
|
from async_oauthlib import OAuth2Session
|
||||||
|
|
||||||
|
|
||||||
class DiscordOAuth2HttpClient(abc.ABC):
|
class DiscordOAuth2HttpClient(abc.ABC):
|
||||||
"""An OAuth2 http abstract base class providing some factory methods.
|
"""An OAuth2 http abstract base class providing some factory methods.
|
||||||
This class is meant to be overridden by :py:class:`flask_discord.DiscordOAuth2Session` and should not be
|
This class is meant to be overridden by :py:class:`quart_discord.DiscordOAuth2Session` and should not be
|
||||||
used directly.
|
used directly.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -25,78 +25,53 @@ class DiscordOAuth2HttpClient(abc.ABC):
|
|||||||
"DISCORD_OAUTH2_TOKEN",
|
"DISCORD_OAUTH2_TOKEN",
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, app, client_id=None, client_secret=None, redirect_uri=None, bot_token=None, users_cache=None):
|
||||||
self, app=None,
|
self.client_id = client_id or app.config["DISCORD_CLIENT_ID"]
|
||||||
client_id=None, client_secret=None, redirect_uri=None,
|
self.__client_secret = client_secret or app.config["DISCORD_CLIENT_SECRET"]
|
||||||
bot_token=None, users_cache=None, proxy=None, proxy_auth=None
|
self.redirect_uri = redirect_uri or app.config["DISCORD_REDIRECT_URI"]
|
||||||
):
|
self.__bot_token = bot_token or app.config.get("DISCORD_BOT_TOKEN", str())
|
||||||
self.client_id = client_id
|
|
||||||
self.__client_secret = client_secret
|
|
||||||
self.redirect_uri = redirect_uri
|
|
||||||
self.__bot_token = bot_token
|
|
||||||
self.users_cache = users_cache
|
|
||||||
self.proxy = proxy
|
|
||||||
self.proxy_auth = proxy_auth
|
|
||||||
|
|
||||||
if app is not None:
|
|
||||||
self.init_app(app)
|
|
||||||
|
|
||||||
def init_app(self, app):
|
|
||||||
"""A method to lazily initialize the application.
|
|
||||||
Use this when you're using flask factory pattern to create your instances of your flask application.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
app : Flask
|
|
||||||
An instance of your `flask application <http://flask.pocoo.org/docs/1.0/api/#flask.Flask>`_.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.client_id = self.client_id or app.config["DISCORD_CLIENT_ID"]
|
|
||||||
self.__client_secret = self.__client_secret or app.config["DISCORD_CLIENT_SECRET"]
|
|
||||||
self.redirect_uri = self.redirect_uri or app.config["DISCORD_REDIRECT_URI"]
|
|
||||||
self.__bot_token = self.__bot_token or app.config.get("DISCORD_BOT_TOKEN", str())
|
|
||||||
self.users_cache = cachetools.LFUCache(
|
self.users_cache = cachetools.LFUCache(
|
||||||
app.config.get("DISCORD_USERS_CACHE_MAX_LIMIT", configs.DISCORD_USERS_CACHE_DEFAULT_MAX_LIMIT)
|
app.config.get("DISCORD_USERS_CACHE_MAX_LIMIT", configs.DISCORD_USERS_CACHE_DEFAULT_MAX_LIMIT)
|
||||||
) if self.users_cache is None else self.users_cache
|
) if users_cache is None else users_cache
|
||||||
if not issubclass(self.users_cache.__class__, Mapping):
|
if not issubclass(self.users_cache.__class__, Mapping):
|
||||||
raise ValueError("Instance users_cache must be a mapping like object.")
|
raise ValueError("Instance users_cache must be a mapping like object.")
|
||||||
self.proxy = self.proxy or app.config.get("DISCORD_PROXY_SETTINGS")
|
if "http://" in self.redirect_uri:
|
||||||
self.proxy_auth = self.proxy_auth or app.config.get("DISCORD_PROXY_AUTH_SETTINGS")
|
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "true"
|
||||||
app.discord = self
|
app.discord = self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_id(self) -> typing.Union[int, None]:
|
def user_id(self) -> typing.Union[int, None]:
|
||||||
"""A property which returns Discord user ID if it exists in flask :py:attr:`flask.session` object.
|
"""A property which returns Discord user ID if it exists in quart :py:attr:`quart.session` object.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
int
|
int
|
||||||
The Discord user ID of current user.
|
The Discord user ID of current user.
|
||||||
None
|
None
|
||||||
If the user ID doesn't exists in flask :py:attr:`flask.session`.
|
If the user ID doesn't exists in quart :py:attr:`quart.session`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return session.get("DISCORD_USER_ID")
|
return session.get("DISCORD_USER_ID")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def save_authorization_token(token: dict):
|
async def save_authorization_token(token: dict):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_authorization_token() -> dict:
|
async def get_authorization_token() -> dict:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def _fetch_token(self, state):
|
async def _fetch_token(self, state):
|
||||||
discord = self._make_session(state=state)
|
discord = await self._make_session(state=state)
|
||||||
return discord.fetch_token(
|
return await discord.fetch_token(
|
||||||
configs.DISCORD_TOKEN_URL,
|
configs.DISCORD_TOKEN_URL,
|
||||||
client_secret=self.__client_secret,
|
client_secret=self.__client_secret,
|
||||||
authorization_response=request.url
|
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.
|
"""A low level method used for creating OAuth2 session.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -117,7 +92,7 @@ class DiscordOAuth2HttpClient(abc.ABC):
|
|||||||
"""
|
"""
|
||||||
return OAuth2Session(
|
return OAuth2Session(
|
||||||
client_id=self.client_id,
|
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"),
|
state=state or session.get("DISCORD_OAUTH2_STATE"),
|
||||||
scope=scope,
|
scope=scope,
|
||||||
redirect_uri=self.redirect_uri,
|
redirect_uri=self.redirect_uri,
|
||||||
@@ -128,7 +103,7 @@ class DiscordOAuth2HttpClient(abc.ABC):
|
|||||||
auto_refresh_url=configs.DISCORD_TOKEN_URL,
|
auto_refresh_url=configs.DISCORD_TOKEN_URL,
|
||||||
token_updater=self.save_authorization_token)
|
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.
|
"""Sends HTTP request to provided route or discord endpoint.
|
||||||
|
|
||||||
Note
|
Note
|
||||||
@@ -154,33 +129,29 @@ class DiscordOAuth2HttpClient(abc.ABC):
|
|||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
flask_discord.Unauthorized
|
quart_discord.Unauthorized
|
||||||
Raises :py:class:`flask_discord.Unauthorized` if current user is not authorized.
|
Raises :py:class:`quart_discord.Unauthorized` if current user is not authorized.
|
||||||
flask_discord.RateLimited
|
quart_discord.RateLimited
|
||||||
Raises an instance of :py:class:`flask_discord.RateLimited` if application is being rate limited by Discord.
|
Raises an instance of :py:class:`quart_discord.RateLimited` if application is being rate limited by Discord.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
route = configs.DISCORD_API_BASE_URL + route
|
route = configs.DISCORD_API_BASE_URL + route
|
||||||
|
discord = await self._make_session()
|
||||||
|
async with (await discord.request(
|
||||||
|
method, route, data, **kwargs
|
||||||
|
) if oauth else aiohttp.request(method, route, data=data, **kwargs)) as response:
|
||||||
|
|
||||||
if self.proxy is not None:
|
if response.status == 401:
|
||||||
kwargs["proxy"] = self.proxy
|
raise exceptions.Unauthorized
|
||||||
if self.proxy_auth is not None:
|
if response.status == 429:
|
||||||
kwargs["proxy_auth"] = self.proxy_auth
|
raise exceptions.RateLimited(response)
|
||||||
|
|
||||||
response = self._make_session(
|
try:
|
||||||
).request(method, route, data, **kwargs) if oauth else requests.request(method, route, data=data, **kwargs)
|
return await response.json()
|
||||||
|
except aiohttp.ContentTypeError:
|
||||||
|
return await response.text()
|
||||||
|
|
||||||
if response.status_code == 401:
|
async def bot_request(self, route: str, method="GET", **kwargs) -> typing.Union[dict, str]:
|
||||||
raise exceptions.Unauthorized()
|
|
||||||
if response.status_code == 429:
|
|
||||||
raise exceptions.RateLimited(response)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return response.json()
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
return response.text
|
|
||||||
|
|
||||||
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.
|
"""Make HTTP request to specified endpoint with bot token as authorization headers.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -198,11 +169,11 @@ class DiscordOAuth2HttpClient(abc.ABC):
|
|||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
flask_discord.Unauthorized
|
quart_discord.Unauthorized
|
||||||
Raises :py:class:`flask_discord.Unauthorized` if current user is not authorized.
|
Raises :py:class:`quart_discord.Unauthorized` if current user is not authorized.
|
||||||
flask_discord.RateLimited
|
quart_discord.RateLimited
|
||||||
Raises an instance of :py:class:`flask_discord.RateLimited` if application is being rate limited by Discord.
|
Raises an instance of :py:class:`quart_discord.RateLimited` if application is being rate limited by Discord.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
headers = {"Authorization": f"Bot {self.__bot_token}"}
|
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)
|
||||||
@@ -5,31 +5,31 @@ import discord
|
|||||||
from . import configs, _http, models, utils, exceptions
|
from . import configs, _http, models, utils, exceptions
|
||||||
|
|
||||||
from oauthlib.common import add_params_to_uri
|
from oauthlib.common import add_params_to_uri
|
||||||
from flask import request, session, redirect, current_app
|
from quart import request, session, redirect, current_app
|
||||||
|
|
||||||
|
|
||||||
class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
|
class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
|
||||||
"""Main client class representing hypothetical OAuth2 session with discord.
|
"""Main client class representing hypothetical OAuth2 session with discord.
|
||||||
It uses Flask `session <http://flask.pocoo.org/docs/1.0/api/#flask.session>`_ local proxy object
|
It uses Quart `session <https://pgjones.gitlab.io/quart/reference/source/quart.sessions.html#quart.sessions.Session>`_
|
||||||
to save state, authorization token and keeps record of users sessions across different requests.
|
local proxy object to save state, authorization token and keeps record of users sessions across different requests.
|
||||||
This class inherits :py:class:`flask_discord._http.DiscordOAuth2HttpClient` class.
|
This class inherits :py:class:`quart_discord._http.DiscordOAuth2HttpClient` class.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
app : Flask
|
app : Quart
|
||||||
An instance of your `flask application <http://flask.pocoo.org/docs/1.0/api/#flask.Flask>`_.
|
An instance of your `quart application <https://pgjones.gitlab.io/quart/reference/source/quart.app.html#quart.app.Quart>`_.
|
||||||
client_id : int, optional
|
client_id : int, optional
|
||||||
The client ID of discord application provided. Can be also set to flask config
|
The client ID of discord application provided. Can be also set to quart config
|
||||||
with key ``DISCORD_CLIENT_ID``.
|
with key ``DISCORD_CLIENT_ID``.
|
||||||
client_secret : str, optional
|
client_secret : str, optional
|
||||||
The client secret of discord application provided. Can be also set to flask config
|
The client secret of discord application provided. Can be also set to quart config
|
||||||
with key ``DISCORD_CLIENT_SECRET``.
|
with key ``DISCORD_CLIENT_SECRET``.
|
||||||
redirect_uri : str, optional
|
redirect_uri : str, optional
|
||||||
The default URL to use to redirect user to after authorization. Can be also set to flask config
|
The default URL to use to redirect user to after authorization. Can be also set to quart config
|
||||||
with key ``DISCORD_REDIRECT_URI``.
|
with key ``DISCORD_REDIRECT_URI``.
|
||||||
bot_token : str, optional
|
bot_token : str, optional
|
||||||
The bot token of the application. This is required when you also need to access bot scope resources
|
The bot token of the application. This is required when you also need to access bot scope resources
|
||||||
beyond the normal resources provided by the OAuth. Can be also set to flask config with
|
beyond the normal resources provided by the OAuth. Can be also set to quart config with
|
||||||
key ``DISCORD_BOT_TOKEN``.
|
key ``DISCORD_BOT_TOKEN``.
|
||||||
users_cache : cachetools.LFUCache, optional
|
users_cache : cachetools.LFUCache, optional
|
||||||
Any dict like mapping to internally cache the authorized users. Preferably an instance of
|
Any dict like mapping to internally cache the authorized users. Preferably an instance of
|
||||||
@@ -57,7 +57,7 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
|
|||||||
def __get_state():
|
def __get_state():
|
||||||
return session.pop("DISCORD_OAUTH2_STATE", str())
|
return session.pop("DISCORD_OAUTH2_STATE", str())
|
||||||
|
|
||||||
def create_session(
|
async def create_session(
|
||||||
self, scope: list = None, *, data: dict = None, prompt: bool = True,
|
self, scope: list = None, *, data: dict = None, prompt: bool = True,
|
||||||
permissions: typing.Union[discord.Permissions, int] = 0, **params
|
permissions: typing.Union[discord.Permissions, int] = 0, **params
|
||||||
):
|
):
|
||||||
@@ -71,7 +71,7 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
|
|||||||
<https://discordapp.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes>`_.
|
<https://discordapp.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes>`_.
|
||||||
data : dict, optional
|
data : dict, optional
|
||||||
A mapping of your any custom data which you want to access after authorization grant. Use
|
A mapping of your any custom data which you want to access after authorization grant. Use
|
||||||
`:py:meth:flask_discord.DiscordOAuth2Session.callback` to retrieve this data in your callback view.
|
`:py:meth:quart_discord.DiscordOAuth2Session.callback` to retrieve this data in your callback view.
|
||||||
prompt : bool, optional
|
prompt : bool, optional
|
||||||
Determines if the OAuth2 grant should be explicitly prompted and re-approved. Defaults to True.
|
Determines if the OAuth2 grant should be explicitly prompted and re-approved. Defaults to True.
|
||||||
Specify False for implicit grant which will skip the authorization screen and redirect to redirect URI.
|
Specify False for implicit grant which will skip the authorization screen and redirect to redirect URI.
|
||||||
@@ -85,7 +85,7 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
redirect
|
redirect
|
||||||
Flask redirect to discord authorization servers to complete authorization code grant process.
|
Quart redirect to discord authorization servers to complete authorization code grant process.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
scope = scope or request.args.get("scope", str()).split() or configs.DISCORD_OAUTH_DEFAULT_SCOPES
|
scope = scope or request.args.get("scope", str()).split() or configs.DISCORD_OAUTH_DEFAULT_SCOPES
|
||||||
@@ -95,7 +95,7 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
|
|||||||
|
|
||||||
state = jwt.encode(data or dict(), current_app.config["SECRET_KEY"]).decode(encoding="utf-8")
|
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)
|
authorization_url, state = discord_session.authorization_url(configs.DISCORD_AUTHORIZATION_BASE_URL)
|
||||||
|
|
||||||
self.__save_state(state)
|
self.__save_state(state)
|
||||||
@@ -117,50 +117,50 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
|
|||||||
return redirect(authorization_url)
|
return redirect(authorization_url)
|
||||||
|
|
||||||
@staticmethod
|
@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.
|
"""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.
|
Meaning by default, it uses client side session handling.
|
||||||
|
|
||||||
Override this method if you want to handle the user's session server side. If this method is overridden then,
|
Override this method if you want to handle the user's session server side. If this method is overridden then,
|
||||||
you must also override :py:meth:`flask_discord.DiscordOAuth2Session.get_authorization_token`.
|
you must also override :py:meth:`quart_discord.DiscordOAuth2Session.get_authorization_token`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
session["DISCORD_OAUTH2_TOKEN"] = token
|
session["DISCORD_OAUTH2_TOKEN"] = token
|
||||||
|
|
||||||
@staticmethod
|
@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
|
"""A static method which returns a dict containing Discord OAuth2 token and other secrets which was saved
|
||||||
previously by `:py:meth:`flask_discord.DiscordOAuth2Session.save_authorization_token` from user's cookies.
|
previously by `:py:meth:`quart_discord.DiscordOAuth2Session.save_authorization_token` from user's cookies.
|
||||||
|
|
||||||
You must override this method if you are implementing server side session handling.
|
You must override this method if you are implementing server side session handling.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return session.get("DISCORD_OAUTH2_TOKEN")
|
return session.get("DISCORD_OAUTH2_TOKEN")
|
||||||
|
|
||||||
def callback(self):
|
async def callback(self):
|
||||||
"""A method which should be always called after completing authorization code grant process
|
"""A method which should be always called after completing authorization code grant process
|
||||||
usually in callback view.
|
usually in callback view.
|
||||||
It fetches the authorization token and saves it flask
|
It fetches the authorization token and saves it quart
|
||||||
`session <http://flask.pocoo.org/docs/1.0/api/#flask.session>`_ object.
|
`session <https://pgjones.gitlab.io/quart/reference/source/quart.sessions.html#quart.sessions.Session>`_ object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
error = request.values.get("error")
|
error = (await request.values).get("error")
|
||||||
if error:
|
if error:
|
||||||
if error == "access_denied":
|
if error == "access_denied":
|
||||||
raise exceptions.AccessDenied()
|
raise exceptions.AccessDenied()
|
||||||
raise exceptions.HttpException(error)
|
raise exceptions.HttpException(error)
|
||||||
|
|
||||||
state = self.__get_state()
|
state = self.__get_state()
|
||||||
token = self._fetch_token(state)
|
token = await self._fetch_token(state)
|
||||||
self.save_authorization_token(token)
|
await self.save_authorization_token(token)
|
||||||
|
|
||||||
return jwt.decode(state, current_app.config["SECRET_KEY"])
|
return jwt.decode(state, current_app.config["SECRET_KEY"])
|
||||||
|
|
||||||
def revoke(self):
|
def revoke(self):
|
||||||
"""This method clears current discord token, state and all session data from flask
|
"""This method clears current discord token, state and all session data from quart
|
||||||
`session <http://flask.pocoo.org/docs/1.0/api/#flask.session>`_. Which means user will have
|
`session <https://pgjones.gitlab.io/quart/reference/source/quart.sessions.html#quart.sessions.Session>`_. Which
|
||||||
to go through discord authorization token grant flow again. Also tries to remove the user from internal
|
means user will have to go through discord authorization token grant flow again. Also tries to remove the user
|
||||||
cache if they exist.
|
from internal cache if they exist.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -172,31 +172,30 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
async def authorized(self):
|
||||||
def authorized(self):
|
|
||||||
"""A boolean indicating whether current session has authorization token or not."""
|
"""A boolean indicating whether current session has authorization token or not."""
|
||||||
return self._make_session().authorized
|
return (await self._make_session()).authorized
|
||||||
|
|
||||||
@staticmethod
|
@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.
|
"""This method returns user object from the internal cache if it exists otherwise makes an API call to do so.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
flask_discord.models.User
|
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
|
@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
|
"""This method returns list of user connection objects from internal cache if it exists otherwise
|
||||||
makes an API call to do so.
|
makes an API call to do so.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list
|
list
|
||||||
List of :py:class:`flask_discord.models.UserConnection` objects.
|
List of :py:class:`quart_discord.models.UserConnection` objects.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
user = models.User.get_from_cache()
|
user = models.User.get_from_cache()
|
||||||
@@ -206,17 +205,17 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return models.UserConnection.fetch_from_api()
|
return await models.UserConnection.fetch_from_api()
|
||||||
|
|
||||||
@staticmethod
|
@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
|
"""This method returns list of guild objects from internal cache if it exists otherwise makes an API
|
||||||
call to do so.
|
call to do so.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list
|
list
|
||||||
List of :py:class:`flask_discord.models.Guild` objects.
|
List of :py:class:`quart_discord.models.Guild` objects.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
user = models.User.get_from_cache()
|
user = models.User.get_from_cache()
|
||||||
@@ -226,4 +225,4 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return models.Guild.fetch_from_api()
|
return await models.Guild.fetch_from_api()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
DISCORD_API_BASE_URL = "https://discordapp.com/api"
|
DISCORD_API_BASE_URL = "https://discord.com/api"
|
||||||
|
|
||||||
DISCORD_AUTHORIZATION_BASE_URL = DISCORD_API_BASE_URL + "/oauth2/authorize"
|
DISCORD_AUTHORIZATION_BASE_URL = DISCORD_API_BASE_URL + "/oauth2/authorize"
|
||||||
DISCORD_TOKEN_URL = DISCORD_API_BASE_URL + "/oauth2/token"
|
DISCORD_TOKEN_URL = DISCORD_API_BASE_URL + "/oauth2/token"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from flask import current_app
|
from quart import current_app
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
@@ -22,20 +22,20 @@ class DiscordModelsBase(metaclass=DiscordModelsMeta):
|
|||||||
self._payload = payload
|
self._payload = payload
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _request(*args, **kwargs):
|
async def _request(*args, **kwargs):
|
||||||
"""A shorthand to :py:func:flask_discord.request`. It uses Flask current_app local proxy to get the
|
"""A shorthand to :py:func:quart_discord.request`. It uses Quart current_app local proxy to get the
|
||||||
Flask-Discord client.
|
Quart-Discord client.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return current_app.discord.request(*args, **kwargs)
|
return await current_app.discord.request(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _bot_request(*args, **kwargs):
|
async def _bot_request(*args, **kwargs):
|
||||||
"""A shorthand to :py:func:flask_discord.bot_request`."""
|
"""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
|
@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
|
"""A class method which returns an instance or list of instances of this model by implicitly making an
|
||||||
API call to Discord.
|
API call to Discord.
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class DiscordModelsBase(metaclass=DiscordModelsMeta):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
request_method = cls._bot_request if cls.BOT else cls._request
|
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:
|
if cls.MANY:
|
||||||
return [cls(_) for _ in payload]
|
return [cls(_) for _ in payload]
|
||||||
return cls(payload)
|
return cls(payload)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from flask import current_app
|
from quart import current_app
|
||||||
|
|
||||||
from .base import DiscordModelsBase
|
from .base import DiscordModelsBase
|
||||||
from .integration import Integration
|
from .integration import Integration
|
||||||
@@ -57,24 +57,24 @@ class UserConnection(DiscordModelsBase):
|
|||||||
return bool(self.visibility)
|
return bool(self.visibility)
|
||||||
|
|
||||||
@classmethod
|
@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
|
"""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:`flask_discord.User` exists in the users internal cache
|
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:`flask_discord.User.connections`
|
who are attached to these connections then, the cached property :py:attr:`quart_discord.User.connections`
|
||||||
is updated.
|
is updated.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
cache : bool
|
cache : bool
|
||||||
Determines if the :py:attr:`flask_discord.User.guilds` cache should be updated with the new guilds.
|
Determines if the :py:attr:`quart_discord.User.guilds` cache should be updated with the new guilds.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list[flask_discord.UserConnection, ...]
|
list[quart_discord.UserConnection, ...]
|
||||||
List of instances of :py:class:`flask_discord.UserConnection` to which this user belongs.
|
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:
|
if cache:
|
||||||
user = current_app.discord.users_cache.get(current_app.discord.user_id)
|
user = current_app.discord.users_cache.get(current_app.discord.user_id)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from .base import DiscordModelsBase
|
from .base import DiscordModelsBase
|
||||||
from flask import current_app
|
from quart import current_app
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from .. import configs
|
from .. import configs
|
||||||
@@ -66,23 +66,23 @@ class Guild(DiscordModelsBase):
|
|||||||
return configs.DISCORD_GUILD_ICON_BASE_URL.format(guild_id=self.id, icon_hash=self.icon_hash)
|
return configs.DISCORD_GUILD_ICON_BASE_URL.format(guild_id=self.id, icon_hash=self.icon_hash)
|
||||||
|
|
||||||
@classmethod
|
@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
|
"""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:`flask_discord.User` exists in the users internal cache
|
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:`flask_discord.User.guilds` is updated.
|
who belongs to these guilds then, the cached property :py:attr:`quart_discord.User.guilds` is updated.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
cache : bool
|
cache : bool
|
||||||
Determines if the :py:attr:`flask_discord.User.guilds` cache should be updated with the new guilds.
|
Determines if the :py:attr:`quart_discord.User.guilds` cache should be updated with the new guilds.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list[flask_discord.Guild, ...]
|
list[quart_discord.Guild, ...]
|
||||||
List of instances of :py:class:`flask_discord.Guild` to which this user belongs.
|
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:
|
if cache:
|
||||||
user = current_app.discord.users_cache.get(current_app.discord.user_id)
|
user = current_app.discord.users_cache.get(current_app.discord.user_id)
|
||||||
@@ -5,7 +5,7 @@ from .. import exceptions
|
|||||||
from .base import DiscordModelsBase
|
from .base import DiscordModelsBase
|
||||||
from .connections import UserConnection
|
from .connections import UserConnection
|
||||||
|
|
||||||
from flask import current_app, session
|
from quart import current_app, session
|
||||||
|
|
||||||
|
|
||||||
class User(DiscordModelsBase):
|
class User(DiscordModelsBase):
|
||||||
@@ -48,7 +48,7 @@ class User(DiscordModelsBase):
|
|||||||
An integer representing the
|
An integer representing the
|
||||||
`type of nitro subscription <https://discordapp.com/developers/docs/resources/user#user-object-premium-types>`_.
|
`type of nitro subscription <https://discordapp.com/developers/docs/resources/user#user-object-premium-types>`_.
|
||||||
connections : list
|
connections : list
|
||||||
A list of :py:class:`flask_discord.UserConnection` instances. These are cached and this list might be empty.
|
A list of :py:class:`quart_discord.UserConnection` instances. These are cached and this list might be empty.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -69,12 +69,12 @@ class User(DiscordModelsBase):
|
|||||||
self.premium_type = self._payload.get("premium_type")
|
self.premium_type = self._payload.get("premium_type")
|
||||||
|
|
||||||
# Few properties which are intended to be cached.
|
# Few properties which are intended to be cached.
|
||||||
self._guilds = None # Mapping of guild ID to flask_discord.models.Guild(...).
|
self._guilds = None # Mapping of guild ID to quart_discord.models.Guild(...).
|
||||||
self.connections = None # List of flask_discord.models.UserConnection(...).
|
self.connections = None # List of quart_discord.models.UserConnection(...).
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def guilds(self):
|
def guilds(self):
|
||||||
"""A cached mapping of user's guild ID to :py:class:`flask_discord.Guild`. The guilds are cached when the first
|
"""A cached mapping of user's guild ID to :py:class:`quart_discord.Guild`. The guilds are cached when the first
|
||||||
API call for guilds is requested so it might be an empty dict.
|
API call for guilds is requested so it might be an empty dict.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -125,7 +125,7 @@ class User(DiscordModelsBase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@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
|
"""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.
|
API call to Discord. The user returned from API will always be cached and update in internal cache.
|
||||||
|
|
||||||
@@ -133,10 +133,10 @@ class User(DiscordModelsBase):
|
|||||||
----------
|
----------
|
||||||
guilds : bool
|
guilds : bool
|
||||||
A boolean indicating if user's guilds should be cached or not. Defaults to ``False``. If chose to not
|
A boolean indicating if user's guilds should be cached or not. Defaults to ``False``. If chose to not
|
||||||
cache, user's guilds can always be obtained from :py:func:`flask_discord.Guilds.fetch_from_api()`.
|
cache, user's guilds can always be obtained from :py:func:`quart_discord.Guilds.fetch_from_api()`.
|
||||||
connections : bool
|
connections : bool
|
||||||
A boolean indicating if user's connections should be cached or not. Defaults to ``False``. If chose to not
|
A boolean indicating if user's connections should be cached or not. Defaults to ``False``. If chose to not
|
||||||
cache, user's connections can always be obtained from :py:func:`flask_discord.Connections.fetch_from_api()`.
|
cache, user's connections can always be obtained from :py:func:`quart_discord.Connections.fetch_from_api()`.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -144,14 +144,14 @@ class User(DiscordModelsBase):
|
|||||||
An instance of this model itself.
|
An instance of this model itself.
|
||||||
[cls, ...]
|
[cls, ...]
|
||||||
List of instances of this model when many of these models exist."""
|
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})
|
current_app.discord.users_cache.update({self.id: self})
|
||||||
session["DISCORD_USER_ID"] = self.id
|
session["DISCORD_USER_ID"] = self.id
|
||||||
|
|
||||||
if guilds:
|
if guilds:
|
||||||
self.fetch_guilds()
|
await self.fetch_guilds()
|
||||||
if connections:
|
if connections:
|
||||||
self.fetch_connections()
|
await self.fetch_connections()
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ class User(DiscordModelsBase):
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
flask_discord.User
|
quart_discord.User
|
||||||
An user instance if it exists in internal cache.
|
An user instance if it exists in internal cache.
|
||||||
None
|
None
|
||||||
If the current doesn't exists in internal cache.
|
If the current doesn't exists in internal cache.
|
||||||
@@ -169,7 +169,7 @@ class User(DiscordModelsBase):
|
|||||||
"""
|
"""
|
||||||
return current_app.discord.users_cache.get(current_app.discord.user_id)
|
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.
|
"""Method to add user to the guild, provided OAuth2 session has already been created with ``guilds.join`` scope.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -184,40 +184,40 @@ class User(DiscordModelsBase):
|
|||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
flask_discord.Unauthorized
|
quart_discord.Unauthorized
|
||||||
Raises :py:class:`flask_discord.Unauthorized` if current user is not authorized.
|
Raises :py:class:`quart_discord.Unauthorized` if current user is not authorized.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
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:
|
except KeyError:
|
||||||
raise exceptions.Unauthorized
|
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
|
"""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.
|
and returns list of all guilds the user is member of.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list
|
list
|
||||||
List of :py:class:`flask_discord.Guilds` instances.
|
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
|
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
|
"""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.
|
cache and returns list of all connection instances.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list
|
list
|
||||||
A list of :py:class:`flask_discord.UserConnection` instances.
|
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
|
return self.connections
|
||||||
|
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import functools
|
import functools
|
||||||
|
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
from flask import current_app
|
from quart import current_app
|
||||||
|
|
||||||
|
|
||||||
class JSONBool(object):
|
class JSONBool(object):
|
||||||
@@ -35,7 +35,7 @@ def json_bool(value):
|
|||||||
# Decorators.
|
# Decorators.
|
||||||
|
|
||||||
def requires_authorization(view):
|
def requires_authorization(view):
|
||||||
"""A decorator for flask views which raises exception :py:class:`flask_discord.Unauthorized` if the user
|
"""A decorator for quart views which raises exception :py:class:`quart_discord.Unauthorized` if the user
|
||||||
is not authorized from Discord OAuth2.
|
is not authorized from Discord OAuth2.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -43,9 +43,9 @@ def requires_authorization(view):
|
|||||||
# TODO: Add support to validate scopes.
|
# TODO: Add support to validate scopes.
|
||||||
|
|
||||||
@functools.wraps(view)
|
@functools.wraps(view)
|
||||||
def wrapper(*args, **kwargs):
|
async def wrapper(*args, **kwargs):
|
||||||
if not current_app.discord.authorized:
|
if not await current_app.discord.authorized():
|
||||||
raise exceptions.Unauthorized
|
raise exceptions.Unauthorized
|
||||||
return view(*args, **kwargs)
|
return await view(*args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
Flask
|
Quart
|
||||||
pyjwt
|
pyjwt
|
||||||
requests
|
requests
|
||||||
|
aiohttp
|
||||||
oauthlib
|
oauthlib
|
||||||
discord.py
|
discord.py
|
||||||
cachetools
|
cachetools
|
||||||
setuptools
|
setuptools
|
||||||
requests_oauthlib
|
Async-OAuthlib
|
||||||
|
|||||||
21
setup.py
21
setup.py
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Flask-Discord
|
Quart-Discord
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
An Discord OAuth2 flask extension.
|
An Discord OAuth2 quart extension.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@@ -12,15 +12,15 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
|
|
||||||
def __get_version():
|
def __get_version():
|
||||||
with open("flask_discord/__init__.py") as package_init_file:
|
with open("quart_discord/__init__.py") as package_init_file:
|
||||||
return re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', package_init_file.read(), re.MULTILINE).group(1)
|
return re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', package_init_file.read(), re.MULTILINE).group(1)
|
||||||
|
|
||||||
|
|
||||||
requirements = [
|
requirements = [
|
||||||
'Flask',
|
'Quart',
|
||||||
'pyjwt',
|
'pyjwt',
|
||||||
'oauthlib',
|
'oauthlib',
|
||||||
'requests_oauthlib',
|
'Async-OAuthlib',
|
||||||
'cachetools',
|
'cachetools',
|
||||||
'requests',
|
'requests',
|
||||||
'discord.py',
|
'discord.py',
|
||||||
@@ -40,13 +40,13 @@ extra_requirements = {
|
|||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='Flask-Discord',
|
name='Quart-Discord',
|
||||||
version=__get_version(),
|
version=__get_version(),
|
||||||
url='https://github.com/thec0sm0s/Flask-Discord',
|
url='https://github.com/jnawk/Quart-Discord',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
author='□ | The Cosmos',
|
author='Philip Dowie',
|
||||||
author_email='deepakrajko14@gmail.com',
|
author_email='philip@jnawk.nz',
|
||||||
description='Discord OAuth2 extension for Flask.',
|
description='Discord OAuth2 extension for Quart.',
|
||||||
long_description=__doc__,
|
long_description=__doc__,
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
@@ -55,7 +55,6 @@ setup(
|
|||||||
install_requires=requirements,
|
install_requires=requirements,
|
||||||
extra_requirements=extra_requirements,
|
extra_requirements=extra_requirements,
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Framework :: Flask',
|
|
||||||
'Environment :: Web Environment',
|
'Environment :: Web Environment',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from flask import Flask, redirect, url_for
|
from quart import Quart, redirect, url_for
|
||||||
from flask_discord import DiscordOAuth2Session, requires_authorization
|
from quart_discord import DiscordOAuth2Session, requires_authorization
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Quart(__name__)
|
||||||
|
|
||||||
app.secret_key = b"%\xe0'\x01\xdeH\x8e\x85m|\xb3\xffCN\xc9g"
|
app.secret_key = b"%\xe0'\x01\xdeH\x8e\x85m|\xb3\xffCN\xc9g"
|
||||||
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "true" # !! Only in development environment.
|
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "true" # !! Only in development environment.
|
||||||
@@ -21,8 +21,8 @@ HYPERLINK = '<a href="{}">{}</a>'
|
|||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
async def index():
|
||||||
if not discord.authorized:
|
if not await discord.authorized():
|
||||||
return f"""
|
return f"""
|
||||||
{HYPERLINK.format(url_for(".login"), "Login")} <br />
|
{HYPERLINK.format(url_for(".login"), "Login")} <br />
|
||||||
{HYPERLINK.format(url_for(".login_with_data"), "Login with custom data")} <br />
|
{HYPERLINK.format(url_for(".login_with_data"), "Login with custom data")} <br />
|
||||||
@@ -39,35 +39,37 @@ def index():
|
|||||||
|
|
||||||
|
|
||||||
@app.route("/login/")
|
@app.route("/login/")
|
||||||
def login():
|
async def login():
|
||||||
return discord.create_session()
|
return await discord.create_session()
|
||||||
|
|
||||||
|
|
||||||
@app.route("/login-data/")
|
@app.route("/login-data/")
|
||||||
def login_with_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/")
|
@app.route("/invite-bot/")
|
||||||
def 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/")
|
@app.route("/invite-oauth/")
|
||||||
def 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/")
|
@app.route("/callback/")
|
||||||
def callback():
|
async def callback():
|
||||||
data = discord.callback()
|
data = await discord.callback()
|
||||||
redirect_to = data.get("redirect", "/")
|
redirect_to = data.get("redirect", "/")
|
||||||
return redirect(redirect_to)
|
return redirect(redirect_to)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/me/")
|
@app.route("/me/")
|
||||||
def me():
|
async def me():
|
||||||
user = discord.fetch_user()
|
user = await discord.fetch_user()
|
||||||
return f"""
|
return f"""
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -84,21 +86,21 @@ def me():
|
|||||||
|
|
||||||
|
|
||||||
@app.route("/me/guilds/")
|
@app.route("/me/guilds/")
|
||||||
def user_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])
|
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>/")
|
@app.route("/add_to/<int:guild_id>/")
|
||||||
def add_to_guild(guild_id):
|
async def add_to_guild(guild_id):
|
||||||
user = discord.fetch_user()
|
user = await discord.fetch_user()
|
||||||
return user.add_to_guild(guild_id)
|
return await user.add_to_guild(guild_id)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/me/connections/")
|
@app.route("/me/connections/")
|
||||||
def my_connections():
|
async def my_connections():
|
||||||
user = discord.fetch_user()
|
user = await discord.fetch_user()
|
||||||
connections = discord.fetch_connections()
|
connections = await discord.fetch_connections()
|
||||||
return f"""
|
return f"""
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -113,14 +115,14 @@ def my_connections():
|
|||||||
|
|
||||||
|
|
||||||
@app.route("/logout/")
|
@app.route("/logout/")
|
||||||
def logout():
|
async def logout():
|
||||||
discord.revoke()
|
discord.revoke()
|
||||||
return redirect(url_for(".index"))
|
return redirect(url_for(".index"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/secret/")
|
@app.route("/secret/")
|
||||||
@requires_authorization
|
@requires_authorization
|
||||||
def secret():
|
async def secret():
|
||||||
return os.urandom(16)
|
return os.urandom(16)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user