diff --git a/TwitchLib.Api.Core.Interfaces/IHttpCallHandler.cs b/TwitchLib.Api.Core.Interfaces/IHttpCallHandler.cs index 0d69d1f7..cd360191 100644 --- a/TwitchLib.Api.Core.Interfaces/IHttpCallHandler.cs +++ b/TwitchLib.Api.Core.Interfaces/IHttpCallHandler.cs @@ -1,5 +1,4 @@ -#nullable disable -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using TwitchLib.Api.Core.Enums; @@ -10,7 +9,7 @@ namespace TwitchLib.Api.Core.Interfaces; /// public interface IHttpCallHandler { - Task> GeneralRequestAsync(string url, string method, string payload = null, ApiVersion api = ApiVersion.Helix, string clientId = null, string accessToken = null); + Task> GeneralRequestAsync(string url, string method, string? payload = null, ApiVersion api = ApiVersion.Helix, string? clientId = null, string? accessToken = null); Task PutBytesAsync(string url, byte[] payload); - Task RequestReturnResponseCodeAsync(string url, string method, List> getParams = null); + Task RequestReturnResponseCodeAsync(string url, string method, List>? getParams = null); } diff --git a/TwitchLib.Api.Core/ApiBase.cs b/TwitchLib.Api.Core/ApiBase.cs index ae89e86c..d4f208a7 100644 --- a/TwitchLib.Api.Core/ApiBase.cs +++ b/TwitchLib.Api.Core/ApiBase.cs @@ -1,7 +1,8 @@ -#nullable disable -using System; +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Net; +using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -34,7 +35,7 @@ public ApiBase(IApiSettings settings, IRateLimiter rateLimiter, IHttpCallHandler _jsonSerializer = new TwitchLibJsonSerializer(); } - public async ValueTask GetAccessTokenAsync(string accessToken = null) + public async ValueTask GetAccessTokenAsync(string? accessToken = null) { if (!string.IsNullOrWhiteSpace(accessToken)) return accessToken; @@ -51,7 +52,7 @@ public async ValueTask GetAccessTokenAsync(string accessToken = null) return null; } - internal async Task GenerateServerBasedAccessToken() + internal async Task GenerateServerBasedAccessToken() { var result = await _http.GeneralRequestAsync($"{BaseAuth}/token?client_id={Settings.ClientId}&client_secret={Settings.Secret}&grant_type=client_credentials", "POST", null, ApiVersion.Auth, Settings.ClientId, null).ConfigureAwait(false); if (result.Key == 200) @@ -74,7 +75,7 @@ internal void ForceAccessTokenAndClientIdForHelix(string clientId, string access throw new ClientIdAndOAuthTokenRequired("As of May 1, all calls to Twitch's Helix API require Client-ID and OAuth access token be set. Example: api.Settings.AccessToken = \"twitch-oauth-access-token-here\"; api.Settings.ClientId = \"twitch-client-id-here\";"); } - protected async Task TwitchGetAsync(string resource, ApiVersion api, List> getParams = null, string accessToken = null, string clientId = null, string customBase = null) + protected async Task TwitchGetAsync(string resource, ApiVersion api, List>? getParams = null, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, getParams, api, customBase); @@ -87,7 +88,7 @@ protected async Task TwitchGetAsync(string resource, ApiVersion api, Lis return await _rateLimiter.Perform(async () => (await _http.GeneralRequestAsync(url, "GET", null, api, clientId, accessToken).ConfigureAwait(false)).Value).ConfigureAwait(false); } - protected async Task TwitchGetGenericAsync(string resource, ApiVersion api, List> getParams = null, string accessToken = null, string clientId = null, string customBase = null) + protected async Task TwitchGetGenericAsync(string resource, ApiVersion api, List>? getParams = null, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, getParams, api, customBase); @@ -100,7 +101,7 @@ protected async Task TwitchGetGenericAsync(string resource, ApiVersion api return await _rateLimiter.Perform(async () => JsonConvert.DeserializeObject((await _http.GeneralRequestAsync(url, "GET", null, api, clientId, accessToken).ConfigureAwait(false)).Value, _twitchLibJsonDeserializer)).ConfigureAwait(false); } - protected async Task TwitchPatchGenericAsync(string resource, ApiVersion api, string payload, List> getParams = null, string accessToken = null, string clientId = null, string customBase = null) + protected async Task TwitchPatchGenericAsync(string resource, ApiVersion api, string payload, List>? getParams = null, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, getParams, api, customBase); @@ -113,7 +114,7 @@ protected async Task TwitchPatchGenericAsync(string resource, ApiVersion a return await _rateLimiter.Perform(async () => JsonConvert.DeserializeObject((await _http.GeneralRequestAsync(url, "PATCH", payload, api, clientId, accessToken).ConfigureAwait(false)).Value, _twitchLibJsonDeserializer)).ConfigureAwait(false); } - protected async Task> TwitchPatchAsync(string resource, ApiVersion api, string payload, List> getParams = null, string accessToken = null, string clientId = null, string customBase = null) + protected async Task> TwitchPatchAsync(string resource, ApiVersion api, string payload, List>? getParams = null, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, getParams, api, customBase); @@ -126,7 +127,7 @@ protected async Task> TwitchPatchAsync(string resource return await _rateLimiter.Perform(async () => (await _http.GeneralRequestAsync(url, "PATCH", payload, api, clientId, accessToken).ConfigureAwait(false))).ConfigureAwait(false); } - protected async Task> TwitchDeleteAsync(string resource, ApiVersion api, List> getParams = null, string accessToken = null, string clientId = null, string customBase = null) + protected async Task> TwitchDeleteAsync(string resource, ApiVersion api, List>? getParams = null, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, getParams, api, customBase); @@ -139,7 +140,7 @@ protected async Task> TwitchDeleteAsync(string resourc return await _rateLimiter.Perform(async () => (await _http.GeneralRequestAsync(url, "DELETE", null, api, clientId, accessToken).ConfigureAwait(false))).ConfigureAwait(false); } - protected async Task TwitchPostGenericAsync(string resource, ApiVersion api, string payload, List> getParams = null, string accessToken = null, string clientId = null, string customBase = null) + protected async Task TwitchPostGenericAsync(string resource, ApiVersion api, string payload, List>? getParams = null, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, getParams, api, customBase); @@ -152,7 +153,7 @@ protected async Task TwitchPostGenericAsync(string resource, ApiVersion ap return await _rateLimiter.Perform(async () => JsonConvert.DeserializeObject((await _http.GeneralRequestAsync(url, "POST", payload, api, clientId, accessToken).ConfigureAwait(false)).Value, _twitchLibJsonDeserializer)).ConfigureAwait(false); } - protected async Task TwitchPostGenericModelAsync(string resource, ApiVersion api, RequestModel model, string accessToken = null, string clientId = null, string customBase = null) + protected async Task TwitchPostGenericModelAsync(string resource, ApiVersion api, RequestModel? model, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, api: api, overrideUrl: customBase); @@ -165,7 +166,7 @@ protected async Task TwitchPostGenericModelAsync(string resource, ApiVersi return await _rateLimiter.Perform(async () => JsonConvert.DeserializeObject((await _http.GeneralRequestAsync(url, "POST", model != null ? _jsonSerializer.SerializeObject(model) : "", api, clientId, accessToken).ConfigureAwait(false)).Value, _twitchLibJsonDeserializer)).ConfigureAwait(false); } - protected async Task TwitchDeleteGenericAsync(string resource, ApiVersion api, List> getParams = null, string accessToken = null, string clientId = null, string customBase = null) + protected async Task TwitchDeleteGenericAsync(string resource, ApiVersion api, List>? getParams = null, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, getParams, api, customBase); @@ -178,7 +179,7 @@ protected async Task TwitchDeleteGenericAsync(string resource, ApiVersion return await _rateLimiter.Perform(async () => JsonConvert.DeserializeObject((await _http.GeneralRequestAsync(url, "DELETE", null, api, clientId, accessToken).ConfigureAwait(false)).Value, _twitchLibJsonDeserializer)).ConfigureAwait(false); } - protected async Task TwitchPutGenericAsync(string resource, ApiVersion api, string payload = null, List> getParams = null, string accessToken = null, string clientId = null, string customBase = null) + protected async Task TwitchPutGenericAsync(string resource, ApiVersion api, string? payload = null, List>? getParams = null, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, getParams, api, customBase); @@ -191,7 +192,7 @@ protected async Task TwitchPutGenericAsync(string resource, ApiVersion api return await _rateLimiter.Perform(async () => JsonConvert.DeserializeObject((await _http.GeneralRequestAsync(url, "PUT", payload, api, clientId, accessToken).ConfigureAwait(false)).Value, _twitchLibJsonDeserializer)).ConfigureAwait(false); } - protected async Task TwitchPutAsync(string resource, ApiVersion api, string payload, List> getParams = null, string accessToken = null, string clientId = null, string customBase = null) + protected async Task TwitchPutAsync(string resource, ApiVersion api, string payload, List>? getParams = null, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, getParams, api, customBase); @@ -204,7 +205,7 @@ protected async Task TwitchPutAsync(string resource, ApiVersion api, str return await _rateLimiter.Perform(async () => (await _http.GeneralRequestAsync(url, "PUT", payload, api, clientId, accessToken).ConfigureAwait(false)).Value).ConfigureAwait(false); } - protected async Task> TwitchPostAsync(string resource, ApiVersion api, string payload, List> getParams = null, string accessToken = null, string clientId = null, string customBase = null) + protected async Task> TwitchPostAsync(string resource, ApiVersion api, string payload, List>? getParams = null, string? accessToken = null, string? clientId = null, string? customBase = null) { var url = ConstructResourceUrl(resource, getParams, api, customBase); @@ -223,12 +224,12 @@ protected Task PutBytesAsync(string url, byte[] payload) return _http.PutBytesAsync(url, payload); } - internal Task RequestReturnResponseCode(string url, string method, List> getParams = null) + internal Task RequestReturnResponseCode(string url, string method, List>? getParams = null) { return _http.RequestReturnResponseCodeAsync(url, method, getParams); } - protected async Task GetGenericAsync(string url, List> getParams = null, string accessToken = null, ApiVersion api = ApiVersion.Helix, string clientId = null) + protected async Task GetGenericAsync(string url, List>? getParams = null, string? accessToken = null, ApiVersion api = ApiVersion.Helix, string? clientId = null) { if (getParams != null) { @@ -250,7 +251,7 @@ protected async Task GetGenericAsync(string url, List JsonConvert.DeserializeObject((await _http.GeneralRequestAsync(url, "GET", null, api, clientId, accessToken).ConfigureAwait(false)).Value, _twitchLibJsonDeserializer)).ConfigureAwait(false); } - internal Task GetSimpleGenericAsync(string url, List> getParams = null) + internal Task GetSimpleGenericAsync(string url, List>? getParams = null) { if (getParams != null) { @@ -314,9 +315,9 @@ protected override string ResolvePropertyName(string propertyName) } } - private string ConstructResourceUrl(string resource = null, List> getParams = null, ApiVersion api = ApiVersion.Helix, string overrideUrl = null) + private string ConstructResourceUrl(string? resource = null, List>? getParams = null, ApiVersion api = ApiVersion.Helix, string? overrideUrl = null) { - var url = ""; + var url = new StringBuilder(); if (overrideUrl == null) { if (resource == null) @@ -324,27 +325,26 @@ private string ConstructResourceUrl(string resource = null, List 0) { - for (var i = 0; i < getParams.Count; i++) + var queryStartIndex = url.Length; + foreach (var param in getParams) { - if (i == 0) - url += $"?{getParams[i].Key}={Uri.EscapeDataString(getParams[i].Value)}"; - else - url += $"&{getParams[i].Key}={Uri.EscapeDataString(getParams[i].Value)}"; + url.Append($"&{param.Key}={Uri.EscapeDataString(param.Value)}"); } + url[queryStartIndex] = '?'; } - return url; + return url.ToString(); } } diff --git a/TwitchLib.Api.Core/Common/Helpers.cs b/TwitchLib.Api.Core/Common/Helpers.cs index 2490bda7..8edecbec 100644 --- a/TwitchLib.Api.Core/Common/Helpers.cs +++ b/TwitchLib.Api.Core/Common/Helpers.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Text; using TwitchLib.Api.Core.Enums; @@ -14,7 +15,15 @@ public static class Helpers /// public static string FormatOAuth(string token) { +#if NET + Span ranges = stackalloc Range[3]; + var tokenSpan = token.AsSpan(); + return tokenSpan.Split(ranges, ' ') is 1 + ? token + : tokenSpan[ranges[1]].ToString(); +#else return token.Contains(" ") ? token.Split(' ')[1] : token; +#endif } /// @@ -100,13 +109,13 @@ public static string AuthScopesToString(AuthScopes scope) AuthScopes.Moderator_Read_Banned_Users => "moderator:manage:banned_users", AuthScopes.Moderator_Read_Chat_Messages => "moderator:read:chat_messages", AuthScopes.Moderator_Read_Moderators => "moderator:read:moderators", - AuthScopes.Moderator_Read_Suspicious_Users=>"moderator:read:suspicious_users", - AuthScopes.Moderator_Read_Unban_Requests=>"moderator:read:unban_requests", - AuthScopes.Moderator_Read_VIPs=>"moderator:read:vips", - AuthScopes.Moderator_Read_Warnings=>"moderator:read:warnings", - AuthScopes.User_Edit_Broadcast=>"user:edit:broadcast", - AuthScopes.User_Read_Emotes=>"user:read:emotes", - AuthScopes.User_Read_Whispers=>"user:read:whispers", + AuthScopes.Moderator_Read_Suspicious_Users => "moderator:read:suspicious_users", + AuthScopes.Moderator_Read_Unban_Requests => "moderator:read:unban_requests", + AuthScopes.Moderator_Read_VIPs => "moderator:read:vips", + AuthScopes.Moderator_Read_Warnings => "moderator:read:warnings", + AuthScopes.User_Edit_Broadcast => "user:edit:broadcast", + AuthScopes.User_Read_Emotes => "user:read:emotes", + AuthScopes.User_Read_Whispers => "user:read:whispers", AuthScopes.Any => string.Empty, AuthScopes.None => string.Empty, _ => string.Empty @@ -213,7 +222,22 @@ public static AuthScopes StringToScope(string scope) /// input as Base64 string public static string Base64Encode(string plainText) { +#if NET + var bytesLen = Encoding.UTF8.GetByteCount(plainText); + byte[]? array = null; + Span plainTextBytes = bytesLen <= 256 + ? stackalloc byte[bytesLen] + : (array = ArrayPool.Shared.Rent(bytesLen)); + + Encoding.UTF8.GetBytes(plainText, plainTextBytes); + var base64 = Convert.ToBase64String(plainTextBytes.Slice(0, bytesLen)); + + if (array is not null) + ArrayPool.Shared.Return(array); + return base64; +#else var plainTextBytes = Encoding.UTF8.GetBytes(plainText); return Convert.ToBase64String(plainTextBytes); +#endif } } diff --git a/TwitchLib.Api.Core/HttpCallHandlers/TwitchHttpClient.cs b/TwitchLib.Api.Core/HttpCallHandlers/TwitchHttpClient.cs index ec844d5a..f0d9df57 100644 --- a/TwitchLib.Api.Core/HttpCallHandlers/TwitchHttpClient.cs +++ b/TwitchLib.Api.Core/HttpCallHandlers/TwitchHttpClient.cs @@ -1,13 +1,13 @@ -#nullable disable +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using TwitchLib.Api.Core.Common; using TwitchLib.Api.Core.Enums; using TwitchLib.Api.Core.Exceptions; @@ -28,7 +28,7 @@ public class TwitchHttpClient : IHttpCallHandler /// Creates an Instance of the TwitchHttpClient Class. /// /// Instance Of Logger, otherwise no logging is used, - public TwitchHttpClient(ILogger logger = null) + public TwitchHttpClient(ILogger logger) { _logger = logger; _http = new HttpClient(new TwitchHttpClientHandler(_logger)); @@ -60,7 +60,7 @@ public async Task PutBytesAsync(string url, byte[] payload) /// KeyValuePair with the key being the returned StatusCode and the Value being the ResponseBody as string /// public async Task> GeneralRequestAsync(string url, string method, - string payload = null, ApiVersion api = ApiVersion.Helix, string clientId = null, string accessToken = null) + string? payload = null, ApiVersion api = ApiVersion.Helix, string? clientId = null, string? accessToken = null) { var request = new HttpRequestMessage { @@ -97,25 +97,25 @@ public async Task> GeneralRequestAsync(string url, str } await HandleWebException(response); - return new KeyValuePair(0, null); + return new KeyValuePair(0, null!); } - public async Task RequestReturnResponseCodeAsync(string url, string method, List> getParams = null) + public async Task RequestReturnResponseCodeAsync(string url, string method, List>? getParams = null) { - if (getParams != null) + var urlStringBuilder = new StringBuilder(url); + if (getParams?.Count > 0) { - for (var i = 0; i < getParams.Count; i++) + var queryStartIndex = urlStringBuilder.Length; + foreach (var param in getParams) { - if (i == 0) - url += $"?{getParams[i].Key}={Uri.EscapeDataString(getParams[i].Value)}"; - else - url += $"&{getParams[i].Key}={Uri.EscapeDataString(getParams[i].Value)}"; + urlStringBuilder.Append($"&{param.Key}={Uri.EscapeDataString(param.Value)}"); } + urlStringBuilder[queryStartIndex] = '?'; } var request = new HttpRequestMessage { - RequestUri = new Uri(url), + RequestUri = new Uri(urlStringBuilder.ToString()), Method = new HttpMethod(method) }; var response = await _http.SendAsync(request).ConfigureAwait(false); @@ -124,7 +124,7 @@ public async Task RequestReturnResponseCodeAsync(string url, string method, private async Task HandleWebException(HttpResponseMessage errorResp) { - var bodyContent = await errorResp.Content.ReadAsStringAsync(); + var bodyContent = await errorResp.Content.ReadAsStringAsync().ConfigureAwait(false); var deserializedError = JsonConvert.DeserializeObject(bodyContent); switch (errorResp.StatusCode) diff --git a/TwitchLib.Api.Core/Internal/TwitchLibCustomHttpMessageHandler.cs b/TwitchLib.Api.Core/Internal/TwitchLibCustomHttpMessageHandler.cs index b0507294..ad2271a4 100644 --- a/TwitchLib.Api.Core/Internal/TwitchLibCustomHttpMessageHandler.cs +++ b/TwitchLib.Api.Core/Internal/TwitchLibCustomHttpMessageHandler.cs @@ -1,5 +1,4 @@ -#nullable disable -using System; +using System; using System.Diagnostics; using System.Net; using System.Net.Http; @@ -34,35 +33,29 @@ public TwitchHttpClientHandler(ILogger logger) : base(new Http /// protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - if (request.Content != null) - _logger?.LogInformation("Timestamp: {timestamp} Type: {type} Method: {method} Resource: {url} Content: {content}", - DateTime.Now, "Request", request.Method.ToString(), request.RequestUri.ToString(), await request.Content.ReadAsStringAsync()); - else - _logger?.LogInformation("Timestamp: {timestamp} Type: {type} Method: {method} Resource: {url}", - DateTime.Now, "Request", request.Method.ToString(), request.RequestUri.ToString()); + var contentStr = request.Content is null + ? "" + : await request.Content.ReadAsStringAsync().ConfigureAwait(false); + _logger.LogInformation("Timestamp: {timestamp} Type: {type} Method: {method} Resource: {url} Content: {content}", + DateTime.Now, "Request", request.Method, request.RequestUri, contentStr); +#if NET + var startTime = Stopwatch.GetTimestamp(); + var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); + var elapsed = Stopwatch.GetElapsedTime(startTime); +#else var stopwatch = Stopwatch.StartNew(); var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); stopwatch.Stop(); + var elapsed = stopwatch.Elapsed; +#endif - if (response.IsSuccessStatusCode) - { - if (response.Content != null) - _logger?.LogInformation("Timestamp: {timestamp} Type: {type} Resource: {url} Statuscode: {statuscode} Elapsed: {elapsed} ms Content: {content}", - DateTime.Now, "Response", response.RequestMessage.RequestUri, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, await response.Content.ReadAsStringAsync()); - else - _logger?.LogInformation("Timestamp: {timestamp} Type: {type} Resource: {url} Statuscode: {statuscode} Elapsed: {elapsed} ms", - DateTime.Now, "Response", response.RequestMessage.RequestUri, (int)response.StatusCode, stopwatch.ElapsedMilliseconds); - } - else - { - if (response.Content != null) - _logger?.LogError("Timestamp: {timestamp} Type: {type} Resource: {url} Statuscode: {statuscode} Elapsed: {elapsed} ms Content: {content}", - DateTime.Now, "Response", response.RequestMessage.RequestUri, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, await response.Content.ReadAsStringAsync()); - else - _logger?.LogError("Timestamp: {timestamp} Type: {type} Resource: {url} Statuscode: {statuscode} Elapsed: {elapsed} ms", - DateTime.Now, "Response", response.RequestMessage.RequestUri, (int)response.StatusCode, stopwatch.ElapsedMilliseconds); - } + var logLevel = response.IsSuccessStatusCode ? LogLevel.Information : LogLevel.Error; + contentStr = response.Content is null + ? "" + : await response.Content.ReadAsStringAsync().ConfigureAwait(false); + _logger.Log(logLevel, "Timestamp: {timestamp} Type: {type} Resource: {url} Statuscode: {statuscode} Elapsed: {elapsed} ms Content: {content}", + DateTime.Now, "Response", response.RequestMessage.RequestUri, (int)response.StatusCode, elapsed.TotalMilliseconds, contentStr); return response; } diff --git a/TwitchLib.Api.Core/RateLimiter/CountByIntervalAwaitableConstraint.cs b/TwitchLib.Api.Core/RateLimiter/CountByIntervalAwaitableConstraint.cs index 693f161f..ad70b2bf 100644 --- a/TwitchLib.Api.Core/RateLimiter/CountByIntervalAwaitableConstraint.cs +++ b/TwitchLib.Api.Core/RateLimiter/CountByIntervalAwaitableConstraint.cs @@ -1,5 +1,4 @@ -#nullable disable -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -22,7 +21,7 @@ public class CountByIntervalAwaitableConstraint : IAwaitableConstraint private SemaphoreSlim _semafore { get; } = new SemaphoreSlim(1, 1); private ITime _time { get; } - public CountByIntervalAwaitableConstraint(int count, TimeSpan timeSpan, ITime time=null) + public CountByIntervalAwaitableConstraint(int count, TimeSpan timeSpan, ITime? time = null) { if (count <= 0) throw new ArgumentException("count should be strictly positive", nameof(count)); @@ -42,7 +41,7 @@ public async Task WaitForReadiness(CancellationToken cancellationTo var count = 0; var now = _time.GetTimeNow(); var target = now - _timeSpan; - LinkedListNode element = _timeStamps.First, last = null; + LinkedListNode? element = _timeStamps.First, last = null; while ((element != null) && (element.Value > target)) { last = element; diff --git a/TwitchLib.Api.Core/RateLimiter/DisposeAction.cs b/TwitchLib.Api.Core/RateLimiter/DisposeAction.cs index aa81a1ba..0c372042 100644 --- a/TwitchLib.Api.Core/RateLimiter/DisposeAction.cs +++ b/TwitchLib.Api.Core/RateLimiter/DisposeAction.cs @@ -1,5 +1,4 @@ -#nullable disable -using System; +using System; namespace TwitchLib.Api.Core.RateLimiter; @@ -8,13 +7,14 @@ namespace TwitchLib.Api.Core.RateLimiter; /// public class DisposeAction : IDisposable { - private Action _act; + private Action? _act; - public DisposeAction(Action act) + public DisposeAction(Action? act) { _act = act; } + /// public void Dispose() { _act?.Invoke(); diff --git a/TwitchLib.Api.Core/RateLimiter/PersistentCountByIntervalAwaitableConstraint.cs b/TwitchLib.Api.Core/RateLimiter/PersistentCountByIntervalAwaitableConstraint.cs index f72362f6..f83ef81a 100644 --- a/TwitchLib.Api.Core/RateLimiter/PersistentCountByIntervalAwaitableConstraint.cs +++ b/TwitchLib.Api.Core/RateLimiter/PersistentCountByIntervalAwaitableConstraint.cs @@ -1,5 +1,4 @@ -#nullable disable -using System; +using System; using System.Collections.Generic; using TwitchLib.Api.Core.Interfaces; @@ -20,7 +19,7 @@ public class PersistentCountByIntervalAwaitableConstraint : CountByIntervalAwait /// Action is used to save state. /// Initial timestamps. public PersistentCountByIntervalAwaitableConstraint(int count, TimeSpan timeSpan, - Action saveStateAction, IEnumerable initialTimeStamps, ITime time = null) : base(count, timeSpan, time) + Action saveStateAction, IEnumerable? initialTimeStamps, ITime? time = null) : base(count, timeSpan, time) { _saveStateAction = saveStateAction; diff --git a/TwitchLib.Api.Helix/Helix.cs b/TwitchLib.Api.Helix/Helix.cs index 846cc49f..9a3d2e55 100644 --- a/TwitchLib.Api.Helix/Helix.cs +++ b/TwitchLib.Api.Helix/Helix.cs @@ -12,7 +12,6 @@ namespace TwitchLib.Api.Helix; /// public class Helix { - private readonly ILogger _logger; /// /// API Settings like the ClientId, Client Secret and so on /// @@ -137,11 +136,10 @@ public class Helix /// Instance Of RateLimiter, otherwise no ratelimiter is used. /// Instance of ApiSettings, otherwise defaults used. (Can be changed later) /// Instance of HttpCallHandler, otherwise default handler used. - public Helix(ILoggerFactory loggerFactory = null, IRateLimiter rateLimiter = null, IApiSettings settings = null, IHttpCallHandler http = null) + public Helix(ILoggerFactory loggerFactory, IRateLimiter rateLimiter = null, IApiSettings settings = null, IHttpCallHandler http = null) { - _logger = loggerFactory?.CreateLogger(); rateLimiter = rateLimiter ?? BypassLimiter.CreateLimiterBypassInstance(); - http = http ?? new TwitchHttpClient(loggerFactory?.CreateLogger()); + http = http ?? new TwitchHttpClient(loggerFactory.CreateLogger()); Settings = settings ?? new ApiSettings(); Analytics = new Analytics(Settings, rateLimiter, http); diff --git a/TwitchLib.Api/Events/OnAuthorizationFlowErrorArgs.cs b/TwitchLib.Api/Events/OnAuthorizationFlowErrorArgs.cs index 573c3bb3..4ef74025 100644 --- a/TwitchLib.Api/Events/OnAuthorizationFlowErrorArgs.cs +++ b/TwitchLib.Api/Events/OnAuthorizationFlowErrorArgs.cs @@ -1,5 +1,4 @@ -#nullable disable -namespace TwitchLib.Api.Events; +namespace TwitchLib.Api.Events; /// /// On Authorization Flow Error Args @@ -14,5 +13,5 @@ public class OnAuthorizationFlowErrorArgs /// /// Message /// - public string Message { get; set; } + public string Message { get; set; } = null!; } diff --git a/TwitchLib.Api/Events/OnUserAuthorizationDetectedArgs.cs b/TwitchLib.Api/Events/OnUserAuthorizationDetectedArgs.cs index c3c304c4..b3097b7e 100644 --- a/TwitchLib.Api/Events/OnUserAuthorizationDetectedArgs.cs +++ b/TwitchLib.Api/Events/OnUserAuthorizationDetectedArgs.cs @@ -1,5 +1,4 @@ -#nullable disable -using System.Collections.Generic; +using System.Collections.Generic; using TwitchLib.Api.Core.Enums; namespace TwitchLib.Api.Events; @@ -12,30 +11,30 @@ public class OnUserAuthorizationDetectedArgs /// /// Id /// - public string Id { get; set; } + public string Id { get; set; } = null!; /// /// Scopes /// - public List Scopes { get; set; } + public List Scopes { get; set; } = null!; /// /// Username /// - public string Username { get; set; } + public string Username { get; set; } = null!; /// /// Access Token /// - public string Token { get; set; } + public string Token { get; set; } = null!; /// /// Refresh Token /// - public string Refresh { get; set; } + public string Refresh { get; set; } = null!; /// /// Client Id /// - public string ClientId { get; set; } + public string ClientId { get; set; } = null!; } diff --git a/TwitchLib.Api/Helpers/ExtensionAnalyticsHelper.cs b/TwitchLib.Api/Helpers/ExtensionAnalyticsHelper.cs index 78ff34b2..9e7e0699 100644 --- a/TwitchLib.Api/Helpers/ExtensionAnalyticsHelper.cs +++ b/TwitchLib.Api/Helpers/ExtensionAnalyticsHelper.cs @@ -1,5 +1,4 @@ -#nullable disable -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -15,7 +14,7 @@ public static class ExtensionAnalyticsHelper { public static async Task> HandleUrlAsync(string url) { - var cnts = await GetContentsAsync(url); + var cnts = await GetContentsAsync(url).ConfigureAwait(false); var data = ExtractData(cnts); return data.Select(line => new ExtensionAnalytics(line)).ToList(); @@ -29,7 +28,14 @@ private static IEnumerable ExtractData(IEnumerable cnts) private static async Task GetContentsAsync(string url) { var client = new HttpClient(); - var lines = (await client.GetStringAsync(url)).Split(new[] { Environment.NewLine }, StringSplitOptions.None); + + var str = await client.GetStringAsync(url).ConfigureAwait(false); + var lines = str +#if NET + .Split(Environment.NewLine, StringSplitOptions.None); +#else + .Split(new[] { Environment.NewLine }, StringSplitOptions.None); +#endif return lines; } } diff --git a/TwitchLib.Api/Services/Core/LiveStreamMonitor/CoreMonitor.cs b/TwitchLib.Api/Services/Core/LiveStreamMonitor/CoreMonitor.cs index 0b9155c2..e38329bf 100644 --- a/TwitchLib.Api/Services/Core/LiveStreamMonitor/CoreMonitor.cs +++ b/TwitchLib.Api/Services/Core/LiveStreamMonitor/CoreMonitor.cs @@ -1,5 +1,4 @@ -#nullable disable -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using TwitchLib.Api.Helix.Models.Streams.GetStreams; @@ -11,11 +10,11 @@ internal abstract class CoreMonitor { protected readonly ITwitchAPI _api; - public abstract Task GetStreamsAsync(List channels, string accessToken = null); - public abstract Task> CompareStream(string channel, string accessToken = null); + public abstract Task GetStreamsAsync(List channels, string? accessToken = null); + public abstract Task> CompareStream(string channel, string? accessToken = null); protected CoreMonitor(ITwitchAPI api) { _api = api; } -} \ No newline at end of file +} diff --git a/TwitchLib.Api/Services/Core/LiveStreamMonitor/IdBasedMonitor.cs b/TwitchLib.Api/Services/Core/LiveStreamMonitor/IdBasedMonitor.cs index f56cd3df..413fec1e 100644 --- a/TwitchLib.Api/Services/Core/LiveStreamMonitor/IdBasedMonitor.cs +++ b/TwitchLib.Api/Services/Core/LiveStreamMonitor/IdBasedMonitor.cs @@ -1,5 +1,4 @@ -#nullable disable -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using TwitchLib.Api.Helix.Models.Streams.GetStreams; @@ -11,12 +10,12 @@ internal class IdBasedMonitor : CoreMonitor { public IdBasedMonitor(ITwitchAPI api) : base(api) { } - public override Task> CompareStream(string channel, string accessToken = null) + public override Task> CompareStream(string channel, string? accessToken = null) { return Task.FromResult(new Func(stream => stream.UserId == channel)); } - public override Task GetStreamsAsync(List channels, string accessToken = null) + public override Task GetStreamsAsync(List channels, string? accessToken = null) { return _api.Helix.Streams.GetStreamsAsync(first: channels.Count, userIds: channels, accessToken: accessToken); } diff --git a/TwitchLib.Api/Services/Core/LiveStreamMonitor/NameBasedMonitor.cs b/TwitchLib.Api/Services/Core/LiveStreamMonitor/NameBasedMonitor.cs index 394e8eda..f25f9b0b 100644 --- a/TwitchLib.Api/Services/Core/LiveStreamMonitor/NameBasedMonitor.cs +++ b/TwitchLib.Api/Services/Core/LiveStreamMonitor/NameBasedMonitor.cs @@ -1,5 +1,4 @@ -#nullable disable -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -15,7 +14,7 @@ internal class NameBasedMonitor : CoreMonitor public NameBasedMonitor(ITwitchAPI api) : base(api) { } - public override async Task> CompareStream(string channel, string accessToken = null) + public override async Task> CompareStream(string channel, string? accessToken = null) { if (!_channelToId.TryGetValue(channel, out var channelId)) { @@ -26,7 +25,7 @@ public override async Task> CompareStream(string channel, str return stream => stream.UserId == channelId; } - public override Task GetStreamsAsync(List channels, string accessToken = null) + public override Task GetStreamsAsync(List channels, string? accessToken = null) { return _api.Helix.Streams.GetStreamsAsync(first: channels.Count, userLogins: channels, accessToken: accessToken); } @@ -35,4 +34,4 @@ public void ClearCache() { _channelToId.Clear(); } -} \ No newline at end of file +} diff --git a/TwitchLib.Api/Services/Events/FollowerService/OnNewFollowersDetectedArgs.cs b/TwitchLib.Api/Services/Events/FollowerService/OnNewFollowersDetectedArgs.cs index 64e4fdae..341f9b49 100644 --- a/TwitchLib.Api/Services/Events/FollowerService/OnNewFollowersDetectedArgs.cs +++ b/TwitchLib.Api/Services/Events/FollowerService/OnNewFollowersDetectedArgs.cs @@ -1,5 +1,4 @@ -#nullable disable -using System; +using System; using System.Collections.Generic; using TwitchLib.Api.Helix.Models.Channels.GetChannelFollowers; @@ -12,7 +11,7 @@ namespace TwitchLib.Api.Services.Events.FollowerService; public class OnNewFollowersDetectedArgs : EventArgs { /// Event property representing channel the service is currently monitoring. - public string Channel; + public string Channel { get; set; } = null!; /// Event property representing all new followers detected. - public List NewFollowers; + public List NewFollowers { get; set; } = null!; } diff --git a/TwitchLib.Api/TwitchAPI.cs b/TwitchLib.Api/TwitchAPI.cs index f4d7bfc3..3dfded57 100644 --- a/TwitchLib.Api/TwitchAPI.cs +++ b/TwitchLib.Api/TwitchAPI.cs @@ -1,6 +1,6 @@ -#nullable disable using System.ComponentModel; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using TwitchLib.Api.Core; using TwitchLib.Api.Core.HttpCallHandlers; using TwitchLib.Api.Core.Interfaces; @@ -26,11 +26,12 @@ public class TwitchAPI : ITwitchAPI /// Instance Of RateLimiter, otherwise no ratelimiter is used. /// Instance of ApiSettings, otherwise defaults used, can be changed later /// Instance of HttpCallHandler, otherwise default handler used - public TwitchAPI(ILoggerFactory loggerFactory = null, IRateLimiter rateLimiter = null, IApiSettings settings = null, IHttpCallHandler http = null) + public TwitchAPI(ILoggerFactory? loggerFactory = null, IRateLimiter? rateLimiter = null, IApiSettings? settings = null, IHttpCallHandler? http = null) { - _logger = loggerFactory?.CreateLogger(); - rateLimiter = rateLimiter ?? BypassLimiter.CreateLimiterBypassInstance(); - http = http ?? new TwitchHttpClient(loggerFactory?.CreateLogger()); + loggerFactory ??= NullLoggerFactory.Instance; + _logger = loggerFactory.CreateLogger(); + rateLimiter ??= BypassLimiter.CreateLimiterBypassInstance(); + http ??= new TwitchHttpClient(loggerFactory.CreateLogger()); Settings = settings ?? new ApiSettings(); Auth = new Auth.Auth(Settings, rateLimiter, http); @@ -41,7 +42,7 @@ public TwitchAPI(ILoggerFactory loggerFactory = null, IRateLimiter rateLimiter = Settings.PropertyChanged += SettingsPropertyChanged; } - private void SettingsPropertyChanged(object sender, PropertyChangedEventArgs e) + private void SettingsPropertyChanged(object? sender, PropertyChangedEventArgs e) { switch (e.PropertyName) {