import requests from config import Config import urllib config = Config() class DiscordUserInfo: UserId : int = None Username : str = None Discriminator : str = None AvatarHash : str = None GlobalName : str = None def __init__(self, UserId : int, Username : str, AvatarHash : str, GlobalName : str = None, Discriminator : str = "0000") -> None: self.UserId = UserId self.Username = Username self.Discriminator = Discriminator self.AvatarHash = AvatarHash self.GlobalName = GlobalName def GetAvatarURL(self) -> str: """ GetAvatarURL returns the URL to the user's avatar. """ if self.AvatarHash is None: return f"https://cdn.discordapp.com/embed/avatars/{str(int(self.Discriminator) % 5)}.png" return f"https://cdn.discordapp.com/avatars/{str(self.UserId)}/{self.AvatarHash}.png" def __repr__(self) -> str: return f"" class UnexpectedStatusCode(Exception): pass class MissingScope(Exception): pass def ExchangeCodeForToken( AccessCode : str ) -> dict: """ ExchangeCodeForToken exchanges a Discord OAuth2 access code for a Discord OAuth2 token. """ DiscordOAuth2TokenExchangeURL = "https://discord.com/api/oauth2/token" DiscordOAuth2TokenExchangeHeaders = { "Content-Type": "application/x-www-form-urlencoded" } DiscordOAuth2TokenExchangeData = { "client_id": config.DISCORD_CLIENT_ID, "client_secret": config.DISCORD_CLIENT_SECRET, "grant_type": "authorization_code", "code": AccessCode, "redirect_uri": config.DISCORD_REDIRECT_URI, "scope": "identify" } DiscordOAuth2TokenExchangeResponse = requests.post(DiscordOAuth2TokenExchangeURL, headers=DiscordOAuth2TokenExchangeHeaders, data=DiscordOAuth2TokenExchangeData) DiscordOAuth2TokenExchangeResponseJSON = DiscordOAuth2TokenExchangeResponse.json() if DiscordOAuth2TokenExchangeResponse.status_code != 200: raise UnexpectedStatusCode(f"Unexpected status code {DiscordOAuth2TokenExchangeResponse.status_code} when exchanging code for token.") # Make sure "identify" is in the scope if "identify" not in DiscordOAuth2TokenExchangeResponseJSON["scope"]: raise MissingScope("Missing scope 'identify' when exchanging code for token.") if "guilds.join" not in DiscordOAuth2TokenExchangeResponseJSON["scope"]: raise MissingScope("Missing scope 'guilds.join' when refreshing token.") return DiscordOAuth2TokenExchangeResponseJSON def GetUserInfoFromToken( AccessToken : str ) -> DiscordUserInfo: """ GetUserInfoFromToken gets a user's Discord user info from their access token. """ DiscordOAuth2GetUserInfoURL = "https://discord.com/api/users/@me" DiscordOAuth2GetUserInfoHeaders = { "Authorization": f"Bearer {AccessToken}" } DiscordOAuth2GetUserInfoResponse = requests.get(DiscordOAuth2GetUserInfoURL, headers=DiscordOAuth2GetUserInfoHeaders) DiscordOAuth2GetUserInfoResponseJSON = DiscordOAuth2GetUserInfoResponse.json() if DiscordOAuth2GetUserInfoResponse.status_code != 200: raise UnexpectedStatusCode(f"Unexpected status code {DiscordOAuth2GetUserInfoResponse.status_code} when getting user info from token.") return DiscordUserInfo( UserId = DiscordOAuth2GetUserInfoResponseJSON["id"], Username = DiscordOAuth2GetUserInfoResponseJSON["username"], Discriminator = DiscordOAuth2GetUserInfoResponseJSON["discriminator"], AvatarHash = DiscordOAuth2GetUserInfoResponseJSON["avatar"], GlobalName = DiscordOAuth2GetUserInfoResponseJSON["global_name"] ) def GenerateAuthorizationURL( State : str ) -> str: """ GenerateAuthorizationURL generates a Discord OAuth2 authorization URL. """ DiscordOAuth2AuthorizationURL = config.DISCORD_AUTHORIZATION_BASE_URL DiscordOAuth2AuthorizationURLParams = { "client_id": config.DISCORD_CLIENT_ID, "redirect_uri": config.DISCORD_REDIRECT_URI, "response_type": "code", "scope": "identify guilds.join", "state": State } # Format the URL DiscordOAuth2AuthorizationURL = f"{DiscordOAuth2AuthorizationURL}?{urllib.parse.urlencode(DiscordOAuth2AuthorizationURLParams)}" return DiscordOAuth2AuthorizationURL def RefreshAccessToken( RefreshToken : str ) -> dict: """ RefreshAccessToken refreshes a Discord OAuth2 access token from a refresh token. """ DiscordOAuth2TokenRefreshData = { "client_id": config.DISCORD_CLIENT_ID, "client_secret": config.DISCORD_CLIENT_SECRET, "grant_type": "refresh_token", "refresh_token": RefreshToken } DiscordOAuth2TokenRefreshURL = "https://discord.com/api/oauth2/token" DiscordOAuth2TokenRefreshHeaders = { "Content-Type": "application/x-www-form-urlencoded" } DiscordOAuth2TokenRefreshResponse = requests.post(DiscordOAuth2TokenRefreshURL, headers=DiscordOAuth2TokenRefreshHeaders, data=DiscordOAuth2TokenRefreshData) DiscordOAuth2TokenRefreshResponseJSON = DiscordOAuth2TokenRefreshResponse.json() if DiscordOAuth2TokenRefreshResponse.status_code != 200: raise UnexpectedStatusCode(f"Unexpected status code {DiscordOAuth2TokenRefreshResponse.status_code} when refreshing token.") if "identify" not in DiscordOAuth2TokenRefreshResponseJSON["scope"]: raise MissingScope("Missing scope 'identify' when refreshing token.") if "guilds.join" not in DiscordOAuth2TokenRefreshResponseJSON["scope"]: raise MissingScope("Missing scope 'guilds.join' when refreshing token.") return DiscordOAuth2TokenRefreshResponseJSON