| @@ -29,29 +29,13 @@ namespace Discord.API | |||||
| public TokenType AuthTokenType { get; private set; } | public TokenType AuthTokenType { get; private set; } | ||||
| public IRestClient RestClient { get; private set; } | public IRestClient RestClient { get; private set; } | ||||
| public IRequestQueue RequestQueue { get; private set; } | public IRequestQueue RequestQueue { get; private set; } | ||||
| internal DiscordRawClient(RestClientProvider restClientProvider, CancellationToken cancelToken, TokenType authTokenType, string authToken) | |||||
| internal DiscordRawClient(RestClientProvider restClientProvider, CancellationToken cancelToken) | |||||
| { | { | ||||
| _cancelToken = cancelToken; | _cancelToken = cancelToken; | ||||
| AuthTokenType = authTokenType; | |||||
| switch (authTokenType) | |||||
| { | |||||
| case TokenType.Bot: | |||||
| authToken = $"Bot {authToken}"; | |||||
| break; | |||||
| case TokenType.Bearer: | |||||
| authToken = $"Bearer {authToken}"; | |||||
| break; | |||||
| case TokenType.User: | |||||
| break; | |||||
| default: | |||||
| throw new ArgumentException("Unknown oauth token type", nameof(authTokenType)); | |||||
| } | |||||
| _restClient = restClientProvider(DiscordConfig.ClientAPIUrl, cancelToken); | _restClient = restClientProvider(DiscordConfig.ClientAPIUrl, cancelToken); | ||||
| _restClient.SetHeader("accept", "*/*"); | _restClient.SetHeader("accept", "*/*"); | ||||
| _restClient.SetHeader("authorization", authToken); | |||||
| _restClient.SetHeader("user-agent", DiscordConfig.UserAgent); | _restClient.SetHeader("user-agent", DiscordConfig.UserAgent); | ||||
| _requestQueue = new RequestQueue(_restClient); | _requestQueue = new RequestQueue(_restClient); | ||||
| @@ -69,6 +53,27 @@ namespace Discord.API | |||||
| _serializer.ContractResolver = new OptionalContractResolver(); | _serializer.ContractResolver = new OptionalContractResolver(); | ||||
| } | } | ||||
| public void SetToken(TokenType tokenType, string token) | |||||
| { | |||||
| AuthTokenType = tokenType; | |||||
| switch (tokenType) | |||||
| { | |||||
| case TokenType.Bot: | |||||
| token = $"Bot {token}"; | |||||
| break; | |||||
| case TokenType.Bearer: | |||||
| token = $"Bearer {token}"; | |||||
| break; | |||||
| case TokenType.User: | |||||
| break; | |||||
| default: | |||||
| throw new ArgumentException("Unknown oauth token type", nameof(tokenType)); | |||||
| } | |||||
| _restClient.SetHeader("authorization", token); | |||||
| } | |||||
| //Core | //Core | ||||
| public Task Send(string method, string endpoint, GlobalBucket bucket = GlobalBucket.General) | public Task Send(string method, string endpoint, GlobalBucket bucket = GlobalBucket.General) | ||||
| => SendInternal(method, endpoint, null, true, bucket); | => SendInternal(method, endpoint, null, true, bucket); | ||||
| @@ -122,7 +127,7 @@ namespace Discord.API | |||||
| stopwatch.Stop(); | stopwatch.Stop(); | ||||
| double milliseconds = ToMilliseconds(stopwatch); | double milliseconds = ToMilliseconds(stopwatch); | ||||
| SentRequest(this, new SentRequestEventArgs(method, endpoint, bytes, milliseconds)); | |||||
| SentRequest?.Invoke(this, new SentRequestEventArgs(method, endpoint, bytes, milliseconds)); | |||||
| return responseStream; | return responseStream; | ||||
| } | } | ||||
| @@ -134,11 +139,23 @@ namespace Discord.API | |||||
| stopwatch.Stop(); | stopwatch.Stop(); | ||||
| double milliseconds = ToMilliseconds(stopwatch); | double milliseconds = ToMilliseconds(stopwatch); | ||||
| SentRequest(this, new SentRequestEventArgs(method, endpoint, bytes, milliseconds)); | |||||
| SentRequest?.Invoke(this, new SentRequestEventArgs(method, endpoint, bytes, milliseconds)); | |||||
| return responseStream; | return responseStream; | ||||
| } | } | ||||
| //Auth | |||||
| public async Task Login(LoginParams args) | |||||
| { | |||||
| var response = await Send<LoginResponse>("POST", "auth/login", args).ConfigureAwait(false); | |||||
| SetToken(TokenType.User, response.Token); | |||||
| } | |||||
| public async Task ValidateToken() | |||||
| { | |||||
| await Send("GET", "auth/login").ConfigureAwait(false); | |||||
| } | |||||
| //Gateway | //Gateway | ||||
| public async Task<GetGatewayResponse> GetGateway() | public async Task<GetGatewayResponse> GetGateway() | ||||
| { | { | ||||
| @@ -0,0 +1,12 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class LoginParams | |||||
| { | |||||
| [JsonProperty("email")] | |||||
| public string Email { get; set; } | |||||
| [JsonProperty("password")] | |||||
| public string Password { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class LoginResponse | |||||
| { | |||||
| [JsonProperty("token")] | |||||
| public string Token { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -70,6 +70,8 @@ | |||||
| <Compile Include="API\Optional.cs" /> | <Compile Include="API\Optional.cs" /> | ||||
| <Compile Include="API\Rest\DeleteMessagesParam.cs" /> | <Compile Include="API\Rest\DeleteMessagesParam.cs" /> | ||||
| <Compile Include="API\Rest\GetGuildMembersParams.cs" /> | <Compile Include="API\Rest\GetGuildMembersParams.cs" /> | ||||
| <Compile Include="API\Rest\LoginParams.cs" /> | |||||
| <Compile Include="API\Rest\LoginResponse.cs" /> | |||||
| <Compile Include="API\Rest\ModifyCurrentUserNickParams.cs" /> | <Compile Include="API\Rest\ModifyCurrentUserNickParams.cs" /> | ||||
| <Compile Include="API\Rest\UploadFileParams.cs" /> | <Compile Include="API\Rest\UploadFileParams.cs" /> | ||||
| <Compile Include="API\Rest\GuildPruneParams.cs" /> | <Compile Include="API\Rest\GuildPruneParams.cs" /> | ||||
| @@ -14,7 +14,8 @@ namespace Discord | |||||
| IRestClient RestClient { get; } | IRestClient RestClient { get; } | ||||
| IRequestQueue RequestQueue { get; } | IRequestQueue RequestQueue { get; } | ||||
| Task Login(TokenType tokenType, string token); | |||||
| Task Login(string email, string password); | |||||
| Task Login(TokenType tokenType, string token, bool validateToken = true); | |||||
| Task Logout(); | Task Logout(); | ||||
| Task<IChannel> GetChannel(ulong id); | Task<IChannel> GetChannel(ulong id); | ||||
| @@ -47,43 +47,64 @@ namespace Discord.Rest | |||||
| _log.Message += (s,e) => Log.Raise(this, e); | _log.Message += (s,e) => Log.Raise(this, e); | ||||
| } | } | ||||
| public async Task Login(TokenType tokenType, string token) | |||||
| public async Task Login(string email, string password) | |||||
| { | { | ||||
| await _connectionLock.WaitAsync().ConfigureAwait(false); | await _connectionLock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| await LoginInternal(tokenType, token).ConfigureAwait(false); | |||||
| await LoginInternal(email, password).ConfigureAwait(false); | |||||
| } | } | ||||
| finally { _connectionLock.Release(); } | finally { _connectionLock.Release(); } | ||||
| } | } | ||||
| private async Task LoginInternal(TokenType tokenType, string token) | |||||
| public async Task Login(TokenType tokenType, string token, bool validateToken = true) | |||||
| { | |||||
| await _connectionLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await LoginInternal(tokenType, token, validateToken).ConfigureAwait(false); | |||||
| } | |||||
| finally { _connectionLock.Release(); } | |||||
| } | |||||
| private async Task LoginInternal(string email, string password) | |||||
| { | { | ||||
| if (IsLoggedIn) | if (IsLoggedIn) | ||||
| LogoutInternal(); | LogoutInternal(); | ||||
| try | try | ||||
| { | { | ||||
| var cancelTokenSource = new CancellationTokenSource(); | var cancelTokenSource = new CancellationTokenSource(); | ||||
| BaseClient = new API.DiscordRawClient(_restClientProvider, cancelTokenSource.Token, tokenType, token); | |||||
| BaseClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); | |||||
| BaseClient = new API.DiscordRawClient(_restClientProvider, cancelTokenSource.Token); | |||||
| //MessageQueue = new MessageQueue(RestClient, _restLogger); | |||||
| //await MessageQueue.Start(_cancelTokenSource.Token).ConfigureAwait(false); | |||||
| var args = new LoginParams { Email = email, Password = password }; | |||||
| await BaseClient.Login(args).ConfigureAwait(false); | |||||
| await CompleteLogin(cancelTokenSource, false).ConfigureAwait(false); | |||||
| } | |||||
| catch { LogoutInternal(); throw; } | |||||
| } | |||||
| private async Task LoginInternal(TokenType tokenType, string token, bool validateToken) | |||||
| { | |||||
| if (IsLoggedIn) | |||||
| LogoutInternal(); | |||||
| try | |||||
| { | |||||
| var cancelTokenSource = new CancellationTokenSource(); | |||||
| BaseClient = new API.DiscordRawClient(_restClientProvider, cancelTokenSource.Token); | |||||
| try | |||||
| { | |||||
| var currentUser = await BaseClient.GetCurrentUser().ConfigureAwait(false); | |||||
| _currentUser = new SelfUser(this, currentUser); | |||||
| } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized && tokenType == TokenType.Bearer) { } //Ignore 401 if Bearer doesnt have identity | |||||
| _cancelTokenSource = cancelTokenSource; | |||||
| IsLoggedIn = true; | |||||
| LoggedIn.Raise(this); | |||||
| BaseClient.SetToken(tokenType, token); | |||||
| await CompleteLogin(cancelTokenSource, validateToken).ConfigureAwait(false); | |||||
| } | } | ||||
| catch { LogoutInternal(); throw; } | catch { LogoutInternal(); throw; } | ||||
| } | } | ||||
| private async Task CompleteLogin(CancellationTokenSource cancelTokenSource, bool validateToken) | |||||
| { | |||||
| BaseClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); | |||||
| if (validateToken) | |||||
| await BaseClient.ValidateToken().ConfigureAwait(false); | |||||
| _cancelTokenSource = cancelTokenSource; | |||||
| IsLoggedIn = true; | |||||
| LoggedIn.Raise(this); | |||||
| } | |||||
| public async Task Logout() | public async Task Logout() | ||||
| { | { | ||||
| @@ -99,9 +120,14 @@ namespace Discord.Rest | |||||
| { | { | ||||
| bool wasLoggedIn = IsLoggedIn; | bool wasLoggedIn = IsLoggedIn; | ||||
| try { _cancelTokenSource.Cancel(false); } catch { } | |||||
| if (_cancelTokenSource != null) | |||||
| { | |||||
| try { _cancelTokenSource.Cancel(false); } | |||||
| catch { } | |||||
| } | |||||
| BaseClient = null; | BaseClient = null; | ||||
| _currentUser = null; | |||||
| if (wasLoggedIn) | if (wasLoggedIn) | ||||
| { | { | ||||
| @@ -87,10 +87,7 @@ namespace Discord.Rest | |||||
| bool isCurrentUser = (await Discord.GetCurrentUser().ConfigureAwait(false)).Id == Id; | bool isCurrentUser = (await Discord.GetCurrentUser().ConfigureAwait(false)).Id == Id; | ||||
| if (isCurrentUser && args.Nickname.IsSpecified) | if (isCurrentUser && args.Nickname.IsSpecified) | ||||
| { | { | ||||
| var nickArgs = new ModifyCurrentUserNickParams | |||||
| { | |||||
| Nickname = args.Nickname.Value | |||||
| }; | |||||
| var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; | |||||
| await Discord.BaseClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); | await Discord.BaseClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); | ||||
| args.Nickname = new API.Optional<string>(); //Remove | args.Nickname = new API.Optional<string>(); //Remove | ||||
| } | } | ||||