diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 592ad7e92..ea83d59eb 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -43,6 +43,9 @@ namespace Discord.API public LoginState LoginState { get; private set; } public TokenType AuthTokenType { get; private set; } internal string AuthToken { get; private set; } + internal string ClientId { get; private set; } + private string ClientSecret { get; set; } + internal IRestClient RestClient { get; private set; } internal ulong? CurrentUserId { get; set; } public RateLimitPrecision RateLimitPrecision { get; private set; } @@ -52,7 +55,7 @@ namespace Discord.API /// Unknown OAuth token type. public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, - JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true) + JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true, string clientId = null, string clientSecret = null) { _restClientProvider = restClientProvider; UserAgent = userAgent; @@ -64,6 +67,10 @@ namespace Discord.API RequestQueue = new RequestQueue(); _stateLock = new SemaphoreSlim(1, 1); + //this here is what we need for auto refreshes. + ClientId = clientId; + ClientSecret = clientSecret; + SetBaseUrl(DiscordConfig.APIUrl); } @@ -113,6 +120,13 @@ namespace Discord.API } finally { _stateLock.Release(); } } + /// + /// This method handles the "login" in the rest module. It simply sets the headers for the requests with the right header: authorization + bearer token. + /// + /// The type of token used. + /// The actual value of the token + /// The optional options which are unused, so don't bother giving any I guess. + /// private async Task LoginInternalAsync(TokenType tokenType, string token, RequestOptions options = null) { if (LoginState != LoginState.LoggedOut) @@ -127,7 +141,7 @@ namespace Discord.API AuthToken = null; await RequestQueue.SetCancelTokenAsync(_loginCancelToken.Token).ConfigureAwait(false); RestClient.SetCancelToken(_loginCancelToken.Token); - + AuthTokenType = tokenType; AuthToken = token?.TrimEnd(); if (tokenType != TokenType.Webhook) @@ -141,7 +155,6 @@ namespace Discord.API throw; } } - public async Task LogoutAsync() { await _stateLock.WaitAsync().ConfigureAwait(false); @@ -244,6 +257,20 @@ namespace Discord.API return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } + internal Task SendFormAsync(string method, Expression> endpointExpr, IEnumerable> payload, BucketIds ids, + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class + => SendFormAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(method, ids, endpointExpr, funcName), clientBucket, options); + public async Task SendFormAsync(string method, string endpoint, IEnumerable> payload, + BucketId bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class + { + options = options ?? new RequestOptions(); + options.BucketId = bucketId; + + //string json = payload != null ? SerializeJson(payload) : null; + var request = new FormRestRequest(RestClient, method, endpoint, payload, options); + return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); + } + internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(method, ids, endpointExpr, funcName), clientBucket, options); @@ -282,6 +309,56 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); await SendAsync("GET", () => "auth/login", new BucketIds(), options: options).ConfigureAwait(false); } + /// + /// This method allows for retrieval of tokens from the Discord Api. It handles both code based authorizations and refresh tokens. + /// + /// The type of token, either Code or Refresh. + /// The value of the token. + /// The redirect url set in your application's oauth2 settings + /// The scopes requested. + /// + /// Returns with the information about the retrieved token. + public async Task GetTokenAsync(TokenType tokenType, string token, string redirectUrl, IEnumerable scopes, RequestOptions options = null) + { + Preconditions.NotNull(token, nameof(token)); + options = RequestOptions.CreateOrClone(options); + options.IgnoreState = true; //we need to ignore the state since these calls would happen without being logged in. + + //for any case the Authorization header must be null + AuthToken = null; + if (tokenType == TokenType.Code) + { + //we then get the token with a code. + var kvp = new List> + { + new KeyValuePair("client_id" , ClientId), + new KeyValuePair("client_secret", ClientSecret), + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", token), + new KeyValuePair("redirect_uri", redirectUrl), + new KeyValuePair("scope", string.Join(" ", scopes)) + }; + + return await SendFormAsync("POST", () => "oauth2/token", kvp, new BucketIds(), options: options).ConfigureAwait(false); + } + else if (tokenType == TokenType.Refresh) + { + //we then refresh. + //we then get the token with a refresh token. + var kvp = new List> + { + new KeyValuePair("client_id" , ClientId), + new KeyValuePair("client_secret", ClientSecret), + new KeyValuePair("grant_type", "refresh_token"), + new KeyValuePair("refresh_token", token), + new KeyValuePair("redirect_uri", redirectUrl), + new KeyValuePair("scope", string.Join(" ", scopes)) + }; + + return await SendFormAsync("POST", () => "oauth2/token", kvp, new BucketIds(), options: options).ConfigureAwait(false); + } + throw new ArgumentException($"The provided TokenType is invalid: {tokenType}. Only user TokenType.Refresh or TokenType.Code with this method."); + } //Gateway public async Task GetGatewayAsync(RequestOptions options = null)