diff --git a/README.md b/README.md
index 7a931dc..41f1efd 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,68 @@
# Flask-Discord
+Discord OAuth2 extension for Flask.
+
+
+### Installation
+To install current latest release you can use following command:
+```sh
+python3 -m pip install Flask-Discord
+```
+
+
+### Basic Example
+```python
+from flask import Flask, redirect, url_for
+from flask_discord import DiscordOAuth2Session
+
+CONFIGS = {
+ "client_id": 9999999999,
+ "client_secret": "your client secret",
+ "redirect_uri": "default redirect uri",
+}
+
+app = Flask(__name__)
+app.secret_key = "random bytes representing flask secret key"
+discord = DiscordOAuth2Session(**CONFIGS)
+
+
+@app.route("/login")
+def login():
+ return discord.create_session()
+
+
+@app.route("/callback")
+def callback():
+ discord.callback()
+ return redirect(url_for(".me"))
+
+
+@app.route("/me")
+def me():
+ user = discord.fetch_user()
+ return f"""
+
+
+ {user.name}
+
+
+
+
+
+
+ """
+
+
+
+if __name__ == "__main__":
+ app.run()
+
+```
+
+
+### Requirements
+* Flask
+* requests_oauthlib
+
+
+### Documentation
+
diff --git a/flask_discord/__init__.py b/flask_discord/__init__.py
index 3f1050e..273e082 100644
--- a/flask_discord/__init__.py
+++ b/flask_discord/__init__.py
@@ -4,3 +4,6 @@ from .client import DiscordOAuth2Session
__all__ = [
"DiscordOAuth2Session",
]
+
+
+__version__ = "0.0.1"
diff --git a/flask_discord/_http.py b/flask_discord/_http.py
index 08e4f00..fed0670 100644
--- a/flask_discord/_http.py
+++ b/flask_discord/_http.py
@@ -1,4 +1,5 @@
import os
+import abc
from . import configs
@@ -6,7 +7,20 @@ from flask import session, jsonify
from requests_oauthlib import OAuth2Session
-class DiscordOAuth2HttpClient(object):
+class DiscordOAuth2HttpClient(abc.ABC):
+ """An OAuth2 http abstract base class providing some factory methods.
+ This class is meant to be overridden by flask_discord.DiscordOAuth2Session class.
+
+ Attributes
+ ----------
+ client_id : int
+ The client ID of discord application provided.
+ client_secret : str
+ The client secret of discord application provided.
+ redirect_uri : str
+ The default URL to use to redirect user to after authorization.
+
+ """
def __init__(self, client_id, client_secret, redirect_uri):
self.client_id = client_id
@@ -19,7 +33,25 @@ class DiscordOAuth2HttpClient(object):
def _token_updater(token):
session["oauth2_token"] = token
- def _make_session(self, token=None, state=None, scope=None):
+ def _make_session(self, token: str = None, state: str = None, scope: list = None) -> OAuth2Session:
+ """A low level method used for creating OAuth2 session.
+
+ Parameters
+ ----------
+ token : str, optional
+ The authorization token to use which was previously received from authorization code grant.
+ state : str, optional
+ The state to use for OAuth2 session.
+ scope : list, optional
+ List of valid `Discord OAuth2 Scopes
+ `_.
+
+ Returns
+ -------
+ OAuth2Session
+ An instance of OAuth2Session class.
+
+ """
return OAuth2Session(
client_id=self.client_id,
token=token or session.get("oauth2_token"),
@@ -33,7 +65,24 @@ class DiscordOAuth2HttpClient(object):
auto_refresh_url=configs.TOKEN_URL,
token_updater=self._token_updater)
- def get(self, route):
+ def get(self, route: str) -> dict:
+ """Sends HTTP GET request to provided route or discord endpoint.
+
+ Note
+ ----
+ It automatically prefixes the API Base URL so you will just have to pass routes or URL endpoints.
+
+ Parameters
+ ----------
+ route : str
+ Route or endpoint URL to send HTTP GET request to.
+ Example: ``/users/@me``
+
+ Returns
+ -------
+ dict
+ Dictionary containing received from sent HTTP GET request.
+ """
return self._make_session().get(configs.API_BASE_URL + route).json()
def get_json(self):
diff --git a/flask_discord/client.py b/flask_discord/client.py
index a6f80d8..ace664d 100644
--- a/flask_discord/client.py
+++ b/flask_discord/client.py
@@ -4,8 +4,38 @@ from flask import request, session, redirect
class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
+ """Main client class representing hypothetical OAuth2 session with discord.
+ It uses Flask `session `_ local proxy object
+ to save state, authorization token and keeps record of users sessions across different requests.
+ This class inherits flask_discord._http.DiscordOAuth2HttpClient class which
- def create_session(self, scope=None):
+ Parameters
+ ----------
+ client_id : int
+ Client ID of your discord application.
+ client_secret : str
+ Client secret of your discord application.
+ redirect_uri : str
+ The default URL to be used to redirect user after the OAuth2 authorization.
+
+ """
+
+ def create_session(self, scope: list = None):
+ """Primary method used to create OAuth2 session and redirect users for
+ authorization code grant.
+
+ Parameters
+ ----------
+ scope : list, optional
+ An optional list of valid `Discord OAuth2 Scopes
+ `_.
+
+ Returns
+ -------
+ redirect
+ Flask redirect to discord authorization servers to complete authorization code grant process.
+
+ """
scope = scope or request.args.get("scope", str()).split() or configs.DEFAULT_SCOPES
discord_session = self._make_session(scope=scope)
authorization_url, state = discord_session.authorization_url(configs.AUTHORIZATION_BASE_URL)
@@ -13,6 +43,12 @@ class DiscordOAuth2Session(_http.DiscordOAuth2HttpClient):
return redirect(authorization_url)
def callback(self):
+ """A method which should be always called after completing authorization code grant process
+ usually in callback view.
+ It fetches the authorization token and saves it flask
+ `session `_ object.
+
+ """
if request.values.get("error"):
return request.values["error"]
discord = self._make_session(state=session.get("oauth2_state"))
diff --git a/flask_discord/models/connections.py b/flask_discord/models/connections.py
index 78ae8df..5a10284 100644
--- a/flask_discord/models/connections.py
+++ b/flask_discord/models/connections.py
@@ -2,15 +2,44 @@ from .user import User
class Integration(object):
+ """"Class representing discord server integrations.
+
+ Attributes
+ ----------
+ id : int
+ Integration ID.
+ name : str
+ Name of integration.
+ type : str
+ Integration type (twitch, youtube, etc).
+ enabled : bool
+ A boolean representing if this integration is enabled.
+ syncing : bool
+ A boolean representing if this integration is syncing.
+ role_id : int
+ ID that this integration uses for subscribers.
+ expire_behaviour : int
+ An integer representing the behaviour of expiring subscribers.
+ expire_grace_period : int
+ An integer representing the grace period before expiring subscribers.
+ user : User
+ Object representing user of this integration.
+ account : dict
+ A dictionary representing raw
+ `account `_ object.
+ synced_at : ISO8601 timestamp
+ Representing when this integration was last synced.
+
+ """
def __init__(self, payload):
self._payload = payload
- self.id = self._payload.get("id")
+ self.id = int(self._payload.get("id", 0))
self.name = self._payload.get("name")
self.type = self._payload.get("type")
self.enabled = self._payload.get("enabled")
self.syncing = self._payload.get("syncing")
- self.role_id = self._payload.get("role_id")
+ self.role_id = int(self._payload.get("role_id", 0))
self.expire_behaviour = self._payload.get("expire_behaviour")
self.expire_grace_period = self._payload.get("expire_grace_period")
self.user = User(self._payload.get("user", dict()))
@@ -19,10 +48,37 @@ class Integration(object):
class UserConnection(object):
+ """Class representing connections in discord account of the user.
+
+ Attributes
+ ----------
+ id : int
+ ID of the connection account.
+ name : str
+ The username of the connection account.
+ type : str
+ The service of connection (twitch, youtube).
+ revoked : bool
+ A boolean representing whether the connection is revoked.
+ integrations : list
+ A list of server Integration objects.
+ verified : bool
+ A boolean representing whether the connection is verified.
+ friend_sync : bool
+ A boolean representing whether friend sync is enabled for this connection.
+ show_activity : bool
+ A boolean representing whether activities related to this connection will
+ be shown in presence updates.
+ visibility : int
+ An integer representing
+ `visibility `_
+ of this connection.
+
+ """
def __init__(self, payload):
self._payload = payload
- self.id = self._payload.get("id")
+ self.id = int(self._payload.get("id", 0))
self.name = self._payload.get("name")
self.type = self._payload.get("type")
self.revoked = self._payload.get("revoked")
@@ -37,4 +93,5 @@ class UserConnection(object):
@property
def is_visible(self):
+ """A property returning bool if this integration is visible to everyone."""
return bool(self.visibility)
diff --git a/flask_discord/models/guild.py b/flask_discord/models/guild.py
index 084b44e..e8a6c8d 100644
--- a/flask_discord/models/guild.py
+++ b/flask_discord/models/guild.py
@@ -2,10 +2,26 @@ from .. import configs
class Guild(object):
+ """Class representing discord Guild the user is part of.
+
+ Attributes
+ ----------
+ id : int
+ Discord ID of the guild.
+ name : str
+ Name of the guild.
+ icon_hash : str
+ Hash of guild's icon.
+ is_owner : bool
+ Boolean determining if current user is owner of the guild or not.
+ permissions_value : int
+ An integer representing permissions of current user in the guild.
+
+ """
def __init__(self, payload):
self._payload = payload
- self.id = self._payload["id"]
+ self.id = int(self._payload["id"])
self.name = self._payload["name"]
self.icon_hash = self._payload.get("icon")
self.is_owner = self._payload.get("owner")
@@ -16,4 +32,5 @@ class Guild(object):
@property
def icon_url(self):
+ """A property returning direct URL to the guild's icon."""
return configs.GUILD_ICON_BASE_URL.format(guild_id=self.id, icon_hash=self.icon_hash)
diff --git a/flask_discord/models/user.py b/flask_discord/models/user.py
index 6e3245e..8f1f58a 100644
--- a/flask_discord/models/user.py
+++ b/flask_discord/models/user.py
@@ -2,12 +2,42 @@ from .. import configs
class User(object):
+ """Class representing Discord User.
+
+ Attributes
+ ----------
+ id : int
+ The discord ID of the user.
+ username : str
+ The discord username of the user.
+ discriminator : int
+ 4 - digits discord tag of the user.
+ avatar_hash : str
+ Hash of users avatar.
+ bot : bool
+ A boolean representing whether the user belongs to an OAuth2 application.
+ mfa_enabled : bool
+ A boolean representing whether the user has two factor enabled on their account.
+ locale : str
+ The user's chosen language option.
+ verified : bool
+ A boolean representing whether the email on this account has been verified.
+ email : str
+ User's email ID.
+ flags : int
+ An integer representing the
+ `user flags `_.
+ premium_type : int
+ An integer representing the
+ `type of nitro subscription `_.
+
+ """
def __init__(self, payload):
self._payload = payload
- self.id = self._payload["id"]
+ self.id = int(self._payload["id"])
self.username = self._payload["username"]
- self.discriminator = self._payload["discriminator"]
+ self.discriminator = int(self._payload["discriminator"])
self.avatar_hash = self._payload.get("avatar", self.discriminator)
self.bot = self._payload.get("bot", False)
self.mfa_enabled = self._payload.get("mfa_enabled")
@@ -22,20 +52,33 @@ class User(object):
@property
def name(self):
+ """An alias to the username attribute."""
return self.username
@property
def avatar_url(self):
+ """A property returning direct URL to user's avatar."""
return configs.USER_AVATAR_BASE_URL.format(user_id=self.id, avatar_hash=self.avatar_hash)
@property
def is_avatar_animated(self):
+ """A boolean representing if avatar of user is animated. Meaning user has GIF avatar."""
return self.avatar_hash.startswith("a_")
def to_json(self):
+ """A utility method which returns raw user object as it was returned from discord.
+
+ Returns
+ -------
+ dict
+ A dict of user's document.
+ """
return self._payload
class Bot(User):
+ """Class representing the client user itself.
+
+ """
pass