From ce488393e613e8f6c17d829b595a704e65af5b48 Mon Sep 17 00:00:00 2001 From: thecosmos Date: Wed, 8 May 2019 15:46:24 +0530 Subject: [PATCH] Documentation --- README.md | 67 +++++++++++++++++++++++++++++ flask_discord/__init__.py | 3 ++ flask_discord/_http.py | 55 +++++++++++++++++++++-- flask_discord/client.py | 38 +++++++++++++++- flask_discord/models/connections.py | 63 +++++++++++++++++++++++++-- flask_discord/models/guild.py | 19 +++++++- flask_discord/models/user.py | 47 +++++++++++++++++++- 7 files changed, 282 insertions(+), 10 deletions(-) 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