using Discord.Logging; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Threading; using System.Threading.Tasks; namespace Discord.Rest { public abstract class BaseDiscordClient : IDiscordClient { public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); public event Func LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } } private readonly AsyncEvent> _loggedInEvent = new AsyncEvent>(); public event Func LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } } private readonly AsyncEvent> _loggedOutEvent = new AsyncEvent>(); internal readonly Logger _restLogger; private readonly SemaphoreSlim _stateLock; private bool _isFirstLogin, _isDisposed; internal API.DiscordRestApiClient ApiClient { get; } internal LogManager LogManager { get; } /// /// Gets the login state of the client. /// public LoginState LoginState { get; private set; } /// /// Gets the logged-in user. /// public ISelfUser CurrentUser { get; protected set; } /// public TokenType TokenType => ApiClient.AuthTokenType; /// Creates a new REST-only Discord client. internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) { ApiClient = client; LogManager = new LogManager(config.LogLevel); LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); _stateLock = new SemaphoreSlim(1, 1); _restLogger = LogManager.CreateLogger("Rest"); _isFirstLogin = config.DisplayInitialLog; ApiClient.RequestQueue.RateLimitTriggered += async (id, info) => { if (info == null) await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {id?.ToString() ?? "null"}").ConfigureAwait(false); else await _restLogger.WarningAsync($"Rate limit triggered: {id?.ToString() ?? "null"}").ConfigureAwait(false); }; ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); } public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) { await _stateLock.WaitAsync().ConfigureAwait(false); try { await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false); } finally { _stateLock.Release(); } } internal virtual async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken) { if (_isFirstLogin) { _isFirstLogin = false; await LogManager.WriteInitialLog().ConfigureAwait(false); } if (LoginState != LoginState.LoggedOut) await LogoutInternalAsync().ConfigureAwait(false); LoginState = LoginState.LoggingIn; try { // If token validation is enabled, validate the token and let it throw any ArgumentExceptions // that result from invalid parameters if (validateToken) { try { TokenUtils.ValidateToken(tokenType, token); } catch (ArgumentException ex) { // log these ArgumentExceptions and allow for the client to attempt to log in anyways await LogManager.WarningAsync("Discord", "A supplied token was invalid.", ex).ConfigureAwait(false); } } await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); await OnLoginAsync(tokenType, token).ConfigureAwait(false); LoginState = LoginState.LoggedIn; } catch { await LogoutInternalAsync().ConfigureAwait(false); throw; } await _loggedInEvent.InvokeAsync().ConfigureAwait(false); } internal virtual Task OnLoginAsync(TokenType tokenType, string token) => Task.Delay(0); public async Task LogoutAsync() { await _stateLock.WaitAsync().ConfigureAwait(false); try { await LogoutInternalAsync().ConfigureAwait(false); } finally { _stateLock.Release(); } } internal virtual async Task LogoutInternalAsync() { if (LoginState == LoginState.LoggedOut) return; LoginState = LoginState.LoggingOut; await ApiClient.LogoutAsync().ConfigureAwait(false); await OnLogoutAsync().ConfigureAwait(false); CurrentUser = null; LoginState = LoginState.LoggedOut; await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); } internal virtual Task OnLogoutAsync() => Task.Delay(0); internal virtual void Dispose(bool disposing) { if (!_isDisposed) { #pragma warning disable IDISP007 ApiClient.Dispose(); #pragma warning restore IDISP007 _stateLock?.Dispose(); _isDisposed = true; } } /// public void Dispose() => Dispose(true); /// public Task GetRecommendedShardCountAsync(RequestOptions options = null) => ClientHelper.GetRecommendShardCountAsync(this, options); //IDiscordClient /// ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; /// ISelfUser IDiscordClient.CurrentUser => CurrentUser; /// Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => throw new NotSupportedException(); /// Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); /// Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); /// Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); /// Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); /// Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); /// Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) => Task.FromResult(null); /// Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); /// Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); /// /// Creating a guild is not supported with the base client. Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) => throw new NotSupportedException(); /// Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); /// Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) => Task.FromResult(null); /// Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); /// Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) => Task.FromResult(null); /// Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) => Task.FromResult(null); /// Task IDiscordClient.StartAsync() => Task.Delay(0); /// Task IDiscordClient.StopAsync() => Task.Delay(0); } }