Skip to content

Commit

Permalink
added a non async version of the library
Browse files Browse the repository at this point in the history
  • Loading branch information
justanotherbyte committed Aug 22, 2021
1 parent 77a3df9 commit 499e139
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 2 deletions.
130 changes: 130 additions & 0 deletions discord/ext/oauth/no_async/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import weakref

from typing import Optional, Union, List

from .http import Route, HTTPClient
from ..token import AccessTokenResponse
from ..user import User


__all__: tuple = (
"OAuth2Client",
)


class NoAsyncOAuth2Client:
"""
A class representing a client interacting with the discord OAuth2 API.
"""
def __init__(
self,
*,
client_id: int,
client_secret: str,
redirect_uri: str,
scopes: Optional[List[str]] = None
):
"""A class representing a client interacting with the discord OAuth2 API.
:param client_id: The OAuth application's client_id
:type client_id: int
:param client_secret: The OAuth application's client_secret
:type client_secret: str
:param redirect_uri: The OAuth application's redirect_uri. Must be from one of the configured uri's on the developer portal
:type redirect_uri: str
:param scopes: A list of OAuth2 scopes, defaults to None
:type scopes: Optional[List[str]], optional
"""
self._id = client_id
self._auth = client_secret
self._redirect = redirect_uri
self._scopes = " ".join(scopes) if scopes is not None else None

self.http = HTTPClient()
self.http._state_info.update(
{
"client_id": self._id,
"client_secret": self._auth,
"redirect_uri": self._redirect,
"scopes": self._scopes,
}
)

self._user_cache = weakref.WeakValueDictionary()

def exchange_code(self, code: str) -> AccessTokenResponse:
"""Exchanges the code you receive from the OAuth2 redirect.
:param code: The code you've received from the OAuth2 redirect
:type code: str
:return: A response class containing information about the access token
:rtype: AccessTokenResponse
"""
route = Route("POST", "/oauth2/token")
post_data = {
"client_id": self._id,
"client_secret": self._auth,
"grant_type": "authorization_code",
"code": code,
"redirect_uri": self._redirect,
}
if self._scopes is not None:
post_data["scope"] = self._scopes
request_data = self.http.request(route, data=post_data)
token_resp = AccessTokenResponse(data=request_data)
return token_resp

def refresh_token(self, refresh_token: Union[str, AccessTokenResponse]) -> AccessTokenResponse:
"""Refreshes an access token. Takes either a string or an AccessTokenResponse.
:param refresh_token: The refresh token you received when exchanging a redirect code
:type refresh_token: Union[str, AccessTokenResponse]
:return: A new access token response containg information about the refreshed access token
:rtype: AccessTokenResponse
"""
refresh_token = (
refresh_token if isinstance(refresh_token, str) else refresh_token.token
)
route = Route("POST", "/oauth2/token")
post_data = {
"client_id": self._id,
"client_secret": self._auth,
"grant_type": "refresh_token",
"refresh_token": refresh_token,
}
request_data = self.http.request(route, data=post_data)
token_resp = AccessTokenResponse(data=request_data)
return token_resp

def fetch_user(self, access_token_response: AccessTokenResponse) -> User:
"""Makes an api call to fetch a user using their access token.
:param access_token_response: A class holding information about an access token
:type access_token_response: AccessTokenResponse
:return: Returns a User object holding information about the select user
:rtype: User
"""
access_token = access_token_response.token
route = Route("GET", "/users/@me")
headers = {"Authorization": "Bearer {}".format(access_token)}
resp = self.http.request(route, headers=headers)
user = User(http=self.http, data=resp, acr=access_token_response)
self._user_cache.update({user.id: user})
return user

def get_user(self, id: int) -> Optional[User]:
"""Gets a user from the cache. The cache is a WeakValueDictionary, so objects may be removed without notice.
:param id: The id of the user you want to get
:type id: int
:return: A possible user object. Returns None if no User is found in cache.
:rtype: Optional[User]
"""
user = self._user_cache.get(id)
return user

def close(self):
"""Closes and performs cleanup operations on the client, such as clearing its cache.
"""
self._user_cache.clear()
self.http.close()
21 changes: 21 additions & 0 deletions discord/ext/oauth/no_async/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from requests import Response


class ExtOauthException(Exception):
"""
The base exception the library always raises
"""


class HTTPException(ExtOauthException):
"""
The error that is raised whenever an http error occurs
"""

def __init__(self, resp: Response, *, json: dict = {}):
self.resp = resp
self.msg = json.get("error_description") or json.get("message")

def __str__(self):
fmt = "{0.status_code}: {0.reason}: {1}"
return fmt.format(self.resp, self.msg)
47 changes: 47 additions & 0 deletions discord/ext/oauth/no_async/http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import requests

from .errors import HTTPException


__all__: tuple = (
"Route",
"HTTPClient"
)


class Route:
BASE = "https://discord.com/api/v9"

def __init__(self, method: str, endpoint: str, **params):
self.url = self.BASE + endpoint.format(**params)
self.method = method


class HTTPClient:
def __init__(self):
self.__session = None # filled in later
self._state_info = {} # client fills this

def _create_session(self) -> requests.Session:
self.__session = requests.Session()
return self.__session

def request(self, route: Route, **kwargs) -> dict:
if self.__session is None or self.__session.closed is True:
self._create_session()

headers = kwargs.pop("headers", {})

headers["Content-Type"] = "application/x-www-form-urlencoded" # the discord OAuth2 api requires this header to be set to this
kwargs["headers"] = headers

resp = self.__session.request(route.method, route.url, **kwargs)
json = resp.json()
if 200 <= resp.status < 300:
return json
else:
raise HTTPException(resp, json=json)

def close(self):
self.__session.close()
self.__session = None
58 changes: 58 additions & 0 deletions discord/ext/oauth/no_async/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from __future__ import annotations

from typing import List, TYPE_CHECKING

from .http import Route
from ..user import User

if TYPE_CHECKING:
from ..guild import Guild
from ..token import AccessTokenResponse


__all__: tuple = (
"User",
)

class NoAsyncUser(User):
def refresh(self) -> AccessTokenResponse:
"""Refreshes the access token for the user and returns a fresh access token response.
:return: A class holding information about the new access token
:rtype: AccessTokenResponse
"""
refresh_token = self.refresh_token
route = Route("POST", "/oauth2/token")
post_data = {
"client_id": self._http._state_info["client_id"],
"client_secret": self._http._state_info["client_secret"],
"grant_type": "refresh_token",
"refresh_token": refresh_token,
}
request_data = self._http.request(route, data=post_data)
token_resp = AccessTokenResponse(data=request_data)
self.refresh_token = token_resp.refresh_token
self.access_token = token_resp.token
self._acr = token_resp
return token_resp

def fetch_guilds(self, *, refresh: bool = True) -> List[Guild]:
"""Makes an api call to fetch the guilds the user is in. Can fill a normal dictionary cache.
:param refresh: Whether or not to refresh the guild cache attached to this user object. If false, returns the cached guilds, defaults to True
:type refresh: bool, optional
:return: A List of Guild objects either from cache or returned from the api call
:rtype: List[Guild]
"""
if not refresh and self.guilds:
return self.guilds

route = Route("GET", "/users/@me/guilds")
headers = {"Authorization": "Bearer {}".format(self.access_token)}
resp = self._http.request(route, headers=headers)
self.guilds = []
for array in resp:
guild = Guild(data=array, user=self)
self.guilds.append(guild)

return self.guilds
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
aiohttp
aiohttp
requests
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
author='moanie',
python_requires='>=3.7.0',
url='https://github.com/moanie/discord.ext.oauth',
version="0.1.0",
version="0.2.0",
packages=[
"discord/ext/oauth"
],
Expand Down

0 comments on commit 499e139

Please sign in to comment.