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)