From 832f84b5a653b1bc03dc3f81e4efac569f75d3c9 Mon Sep 17 00:00:00 2001 From: d4n3436 Date: Thu, 11 Jan 2024 22:35:20 -0500 Subject: [PATCH] Update solution to .NET 8 --- .github/workflows/dotnet.yml | 2 +- README.md | 4 +- src/Apis/Bing/BingVisualSearch.cs | 10 +-- src/Apis/Dictionary/ArrayOrStringConverter.cs | 2 +- src/Apis/Dictionary/DictionaryClient.cs | 16 ++--- src/Apis/Genius/GeniusClient.cs | 13 +--- src/Apis/Google/GoogleLensClient.cs | 21 ++---- src/Apis/Musixmatch/MusixmatchClient.cs | 13 +--- src/Apis/Musixmatch/MusixmatchClientState.cs | 10 +-- src/Apis/Urban/UrbanDictionary.cs | 24 +++---- src/Apis/Wikipedia/WikipediaClient.cs | 12 +--- src/Apis/WolframAlpha/WolframAlphaClient.cs | 12 +--- src/Apis/Yandex/YandexImageSearch.cs | 46 ++++--------- src/Entities/DualLocalizedString.cs | 2 +- src/Entities/FergunHttpClientLogger.cs | 45 ++++++++++++ src/Entities/FergunLocalizationManager.cs | 4 +- .../FergunLoggingHttpMessageHandler.cs | 69 ------------------- ...nLoggingHttpMessageHandlerBuilderFilter.cs | 33 --------- src/Entities/IFergunLocalizer.cs | 4 +- src/Entities/SharedResource.cs | 4 +- src/Extensions/Extensions.cs | 3 +- src/Extensions/HostBuilderExtensions.cs | 26 ------- src/Extensions/PaginatorExtensions.cs | 6 +- src/Fergun.csproj | 18 ++--- src/Hardware/LinuxHardwareInfo.cs | 4 +- src/Hardware/MemoryStatus.cs | 2 +- src/Program.cs | 9 ++- src/Resources/SharedResource.es.resx | 2 +- src/Resources/SharedResource.resx | 2 +- src/Services/InteractionHandlingService.cs | 12 +--- src/Utils/CommandUtils.cs | 8 +-- tests/Fergun.Tests/Apis/GoogleLensTests.cs | 2 +- tests/Fergun.Tests/Apis/WolframAlphaTests.cs | 2 +- .../MicrosoftVoiceConverterTests.cs | 5 +- .../Extensions/ListExtensionsTests.cs | 4 +- .../Extensions/TimestampExtensionsTests.cs | 6 +- tests/Fergun.Tests/Fergun.Tests.csproj | 6 +- 37 files changed, 146 insertions(+), 317 deletions(-) create mode 100644 src/Entities/FergunHttpClientLogger.cs delete mode 100644 src/Entities/FergunLoggingHttpMessageHandler.cs delete mode 100644 src/Entities/FergunLoggingHttpMessageHandlerBuilderFilter.cs delete mode 100644 src/Extensions/HostBuilderExtensions.cs diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 6e20ba0..fccddd8 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/README.md b/README.md index 44d1c06..2794b58 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Have any questions or need help with the bot? Join the [support server](https:// ### 0. Prerequisites * A Discord bot application (You can create one [here](https://discord.com/developers/applications)). -* [.NET 6 SDK](https://dotnet.microsoft.com/download) +* [.NET 8 SDK](https://dotnet.microsoft.com/download) ### 1. Build and run the bot * Clone the repository: @@ -45,7 +45,7 @@ Have any questions or need help with the bot? Join the [support server](https:// * Go to the build output folder: ``` - cd src/bin/Release/net6.0 + cd src/bin/Release/net8.0 ``` * Open `appsettings.json` with a text editor and set the application token: diff --git a/src/Apis/Bing/BingVisualSearch.cs b/src/Apis/Bing/BingVisualSearch.cs index 3717d1b..c7f37e2 100644 --- a/src/Apis/Bing/BingVisualSearch.cs +++ b/src/Apis/Bing/BingVisualSearch.cs @@ -60,7 +60,7 @@ public async Task> ReverseImageSearch BingSafeSearchLevel safeSearch = BingSafeSearchLevel.Moderate, string? language = null, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); using var request = BuildRequest(url, "SimilarImages", safeSearch, language); @@ -129,12 +129,4 @@ private static HttpRequestMessage BuildRequest(string url, string invokedSkill, return request; } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(BingVisualSearch)); - } - } } \ No newline at end of file diff --git a/src/Apis/Dictionary/ArrayOrStringConverter.cs b/src/Apis/Dictionary/ArrayOrStringConverter.cs index bafb4c3..f8f5063 100644 --- a/src/Apis/Dictionary/ArrayOrStringConverter.cs +++ b/src/Apis/Dictionary/ArrayOrStringConverter.cs @@ -16,7 +16,7 @@ public override IReadOnlyList Read(ref Utf8JsonReader reader, Type typeT return reader.TokenType switch { JsonTokenType.StartArray => JsonSerializer.Deserialize>(ref reader)!, - JsonTokenType.String => reader.ValueTextEquals(ReadOnlySpan.Empty) ? Array.Empty() : new[] { reader.GetString()! }, + JsonTokenType.String => reader.ValueTextEquals(ReadOnlySpan.Empty) ? [] : [reader.GetString()!], JsonTokenType.Null => Array.Empty(), _ => throw new JsonException("Token type must be either array, string or null.") }; diff --git a/src/Apis/Dictionary/DictionaryClient.cs b/src/Apis/Dictionary/DictionaryClient.cs index 3ef003e..a942ec1 100644 --- a/src/Apis/Dictionary/DictionaryClient.cs +++ b/src/Apis/Dictionary/DictionaryClient.cs @@ -27,7 +27,9 @@ public DictionaryClient(HttpClient httpClient) /// public async Task> GetSearchResultsAsync(string text, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); + cancellationToken.ThrowIfCancellationRequested(); + var response = (await _httpClient.GetFromJsonAsync(new Uri($"https://thor-graphql.dictionary.com/v2/search?searchText={Uri.EscapeDataString(text)}"), cancellationToken).ConfigureAwait(false))!; return response.Data; } @@ -35,7 +37,9 @@ public async Task> GetSearchResultsAsync(string t /// public async Task GetDefinitionsAsync(string word, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); + cancellationToken.ThrowIfCancellationRequested(); + return (await _httpClient.GetFromJsonAsync(new Uri($"https://api-portal.dictionary.com/dcom/pageData/{Uri.EscapeDataString(word)}"), cancellationToken).ConfigureAwait(false))!; } @@ -50,12 +54,4 @@ public void Dispose() _httpClient.Dispose(); _disposed = true; } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(DictionaryClient)); - } - } } \ No newline at end of file diff --git a/src/Apis/Genius/GeniusClient.cs b/src/Apis/Genius/GeniusClient.cs index ac835e6..cb934e1 100644 --- a/src/Apis/Genius/GeniusClient.cs +++ b/src/Apis/Genius/GeniusClient.cs @@ -56,7 +56,8 @@ public GeniusClient(HttpClient httpClient) /// public async Task> SearchSongsAsync(string query, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ArgumentException.ThrowIfNullOrEmpty(query); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); var model = await _httpClient.GetFromJsonAsync>(new Uri($"search?q={Uri.EscapeDataString(query)}", UriKind.Relative), cancellationToken).ConfigureAwait(false); @@ -67,7 +68,7 @@ public async Task> SearchSongsAsync(string query, Can /// public async Task GetSongAsync(int id, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); using var response = await _httpClient.GetAsync(new Uri($"songs/{id}", UriKind.Relative), HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); @@ -92,12 +93,4 @@ public void Dispose() _httpClient.Dispose(); _disposed = true; } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(GeniusClient)); - } - } } \ No newline at end of file diff --git a/src/Apis/Google/GoogleLensClient.cs b/src/Apis/Google/GoogleLensClient.cs index 3b1f94b..904021b 100644 --- a/src/Apis/Google/GoogleLensClient.cs +++ b/src/Apis/Google/GoogleLensClient.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -19,9 +18,9 @@ public sealed class GoogleLensClient : IGoogleLensClient, IDisposable private readonly HttpClient _httpClient; private bool _disposed; - private static ReadOnlySpan ResultsCallbackStart => Encoding.UTF8.GetBytes("AF_initDataCallback({key: 'ds:0'"); - private static ReadOnlySpan OcrCallbackStart => Encoding.UTF8.GetBytes("AF_initDataCallback({key: 'ds:1'"); - private static ReadOnlySpan CallbackEnd => Encoding.UTF8.GetBytes(", sideChannel: {}});"); + private static ReadOnlySpan ResultsCallbackStart => "AF_initDataCallback({key: 'ds:0'"u8; + private static ReadOnlySpan OcrCallbackStart => "AF_initDataCallback({key: 'ds:1'"u8; + private static ReadOnlySpan CallbackEnd => ", sideChannel: {}});"u8; /// /// Initializes a new instance of the class. @@ -47,7 +46,8 @@ public GoogleLensClient(HttpClient httpClient) /// public async Task OcrAsync(string url, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ArgumentNullException.ThrowIfNull(url); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); byte[] page = await _httpClient.GetByteArrayAsync(new Uri($"https://lens.google.com/uploadbyurl?url={Uri.EscapeDataString(url)}"), cancellationToken); @@ -64,7 +64,8 @@ public async Task OcrAsync(string url, CancellationToken cancellationTok /// public async Task> ReverseImageSearchAsync(string url, string? language = null, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ArgumentNullException.ThrowIfNull(url); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); string requestUrl = $"https://lens.google.com/uploadbyurl?url={Uri.EscapeDataString(url)}"; @@ -149,12 +150,4 @@ public void Dispose() _httpClient.Dispose(); _disposed = true; } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(GoogleLensClient)); - } - } } \ No newline at end of file diff --git a/src/Apis/Musixmatch/MusixmatchClient.cs b/src/Apis/Musixmatch/MusixmatchClient.cs index e387872..7f4c7da 100644 --- a/src/Apis/Musixmatch/MusixmatchClient.cs +++ b/src/Apis/Musixmatch/MusixmatchClient.cs @@ -54,7 +54,8 @@ public MusixmatchClient(HttpClient httpClient, MusixmatchClientState state, ILog /// public async Task> SearchSongsAsync(string query, bool onlyWithLyrics = true, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ArgumentException.ThrowIfNullOrEmpty(query); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); string url = $"https://apic-desktop.musixmatch.com/ws/1.1/track.search?q_track_artist={Uri.EscapeDataString(query)}&s_track_rating=desc&format=json&app_id={AppId}&f_has_lyrics={(onlyWithLyrics ? 1 : 0)}&f_is_instrumental={(onlyWithLyrics ? 0 : 1)}"; @@ -74,7 +75,7 @@ public async Task> SearchSongsAsync(string query, /// public async Task GetSongAsync(int id, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); string url = $"https://apic-desktop.musixmatch.com/ws/1.1/macro.community.lyrics.get?track_id={id}&version=2&format=json&app_id={AppId}"; @@ -212,12 +213,4 @@ private async Task SendRequestAndValidateAsync(string url, Cancell return document; } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(MusixmatchClient)); - } - } } \ No newline at end of file diff --git a/src/Apis/Musixmatch/MusixmatchClientState.cs b/src/Apis/Musixmatch/MusixmatchClientState.cs index 5573540..6593bf7 100644 --- a/src/Apis/Musixmatch/MusixmatchClientState.cs +++ b/src/Apis/Musixmatch/MusixmatchClientState.cs @@ -39,7 +39,7 @@ public MusixmatchClientState(IHttpClientFactory httpClientFactory) /// The API response is not successful. public async ValueTask GetUserTokenAsync(bool refresh = false) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); if (!refresh && _userToken is not null) { @@ -109,12 +109,4 @@ private async Task FetchUserTokenAsync() return token; } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(MusixmatchClientState)); - } - } } \ No newline at end of file diff --git a/src/Apis/Urban/UrbanDictionary.cs b/src/Apis/Urban/UrbanDictionary.cs index 68851d8..1b83eee 100644 --- a/src/Apis/Urban/UrbanDictionary.cs +++ b/src/Apis/Urban/UrbanDictionary.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Net.Http.Json; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -46,7 +47,7 @@ public UrbanDictionary(HttpClient httpClient) /// public async Task> GetDefinitionsAsync(string term, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); await using var stream = await _httpClient.GetStreamAsync(new Uri($"define?term={Uri.EscapeDataString(term)}", UriKind.Relative), cancellationToken).ConfigureAwait(false); @@ -57,7 +58,7 @@ public async Task> GetDefinitionsAsync(string ter /// public async Task> GetRandomDefinitionsAsync(CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); await using var stream = await _httpClient.GetStreamAsync(new Uri("random", UriKind.Relative), cancellationToken).ConfigureAwait(false); @@ -68,7 +69,7 @@ public async Task> GetRandomDefinitionsAsync(Canc /// public async Task GetDefinitionAsync(int id, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); await using var stream = await _httpClient.GetStreamAsync(new Uri($"define?defid={id}", UriKind.Relative), cancellationToken).ConfigureAwait(false); @@ -81,7 +82,7 @@ public async Task> GetRandomDefinitionsAsync(Canc /// public async Task> GetWordsOfTheDayAsync(CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); await using var stream = await _httpClient.GetStreamAsync(new Uri("words_of_the_day", UriKind.Relative), cancellationToken).ConfigureAwait(false); @@ -92,17 +93,16 @@ public async Task> GetWordsOfTheDayAsync(Cancella /// public async Task> GetAutocompleteResultsAsync(string term, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); - await using var stream = await _httpClient.GetStreamAsync(new Uri($"autocomplete?term={Uri.EscapeDataString(term)}", UriKind.Relative), cancellationToken).ConfigureAwait(false); - return (await JsonSerializer.DeserializeAsync>(stream, (JsonSerializerOptions?)null, cancellationToken).ConfigureAwait(false))!; + return (await _httpClient.GetFromJsonAsync>(new Uri($"autocomplete?term={Uri.EscapeDataString(term)}", UriKind.Relative), cancellationToken).ConfigureAwait(false))!; } /// public async Task> GetAutocompleteResultsExtraAsync(string term, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); await using var stream = await _httpClient.GetStreamAsync(new Uri($"autocomplete-extra?term={Uri.EscapeDataString(term)}", UriKind.Relative), cancellationToken).ConfigureAwait(false); @@ -121,12 +121,4 @@ public void Dispose() _httpClient.Dispose(); _disposed = true; } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(UrbanDictionary)); - } - } } \ No newline at end of file diff --git a/src/Apis/Wikipedia/WikipediaClient.cs b/src/Apis/Wikipedia/WikipediaClient.cs index 6adbaa9..f1a1632 100644 --- a/src/Apis/Wikipedia/WikipediaClient.cs +++ b/src/Apis/Wikipedia/WikipediaClient.cs @@ -35,7 +35,7 @@ public WikipediaClient(HttpClient httpClient) /// public async Task GetArticleAsync(int id, string language, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); string url = $"https://{language}.wikipedia.org/w/api.php?" + @@ -71,7 +71,7 @@ public WikipediaClient(HttpClient httpClient) /// public async Task> SearchArticlesAsync(string query, string language, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); string url = $"https://{language}.wikipedia.org/w/api.php?action=query&list=search&srsearch=intitle:{Uri.EscapeDataString(query)}&utf8&format=json&srprop="; @@ -99,12 +99,4 @@ public void Dispose() _httpClient.Dispose(); _disposed = true; } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(WikipediaClient)); - } - } } \ No newline at end of file diff --git a/src/Apis/WolframAlpha/WolframAlphaClient.cs b/src/Apis/WolframAlpha/WolframAlphaClient.cs index 37cca93..1397d3d 100644 --- a/src/Apis/WolframAlpha/WolframAlphaClient.cs +++ b/src/Apis/WolframAlpha/WolframAlphaClient.cs @@ -52,9 +52,9 @@ public WolframAlphaClient(HttpClient httpClient) /// public async Task> GetAutocompleteResultsAsync(string input, string language, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(input); ArgumentNullException.ThrowIfNull(language); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); await using var stream = await _httpClient.GetStreamAsync(new Uri($"https://{GetSubdomain(language)}.wolframalpha.com/n/v1/api/autocomplete/?i={Uri.EscapeDataString(input)}&appid={AppId}"), cancellationToken).ConfigureAwait(false); @@ -72,9 +72,9 @@ public async Task> GetAutocompleteResultsAsync(string inpu /// public async Task SendQueryAsync(string input, string language, bool reinterpret = true, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(input); ArgumentNullException.ThrowIfNull(language); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); string escapedInput = Uri.EscapeDataString(input); @@ -107,12 +107,4 @@ private static string GetSubdomain(string language) => "ja" => "ja", _ => "www" }; - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(WolframAlphaClient)); - } - } } \ No newline at end of file diff --git a/src/Apis/Yandex/YandexImageSearch.cs b/src/Apis/Yandex/YandexImageSearch.cs index 749c55a..9e8c46a 100644 --- a/src/Apis/Yandex/YandexImageSearch.cs +++ b/src/Apis/Yandex/YandexImageSearch.cs @@ -45,17 +45,15 @@ public YandexImageSearch(HttpClient httpClient) /// public async Task OcrAsync(string url, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ArgumentNullException.ThrowIfNull(url); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); // Get CBIR ID - using var request = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri($"https://yandex.com/images-apphost/image-download?url={Uri.EscapeDataString(url)}&cbird=37&images_avatars_size=orig&images_avatars_namespace=images-cbir") - }; - using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var uri = new Uri($"https://yandex.com/images-apphost/image-download?url={Uri.EscapeDataString(url)}&cbird=37&images_avatars_size=orig&images_avatars_namespace=images-cbir"); + + using var response = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); try { @@ -81,15 +79,10 @@ public YandexImageSearch(HttpClient httpClient) .GetInt32(); // Get OCR text - const string ocrJsonRequest = @"{""blocks"":[{""block"":{""block"":""i-react-ajax-adapter:ajax""},""params"":{""type"":""CbirOcr"",""subtype"":""legacy""},""version"":2}]}"; + const string ocrJsonRequest = """{"blocks":[{"block":{"block":"i-react-ajax-adapter:ajax"},"params":{"type":"CbirOcr","subtype":"legacy"},"version":2}]}"""; - using var ocrRequest = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri($"https://yandex.com/images/search?format=json&request={ocrJsonRequest}&rpt=ocr&cbir_id={imageShard}/{imageId}") - }; - - using var ocrResponse = await _httpClient.SendAsync(ocrRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var ocrRequestUri = new Uri($"https://yandex.com/images/search?format=json&request={ocrJsonRequest}&rpt=ocr&cbir_id={imageShard}/{imageId}"); + using var ocrResponse = await _httpClient.GetAsync(ocrRequestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); ocrResponse.EnsureSuccessStatusCode(); // A byte array is used because SendAsync returns a chunked response and the Stream from ReadAsStreamAsync is not seekable. @@ -114,16 +107,15 @@ public YandexImageSearch(HttpClient httpClient) public async Task> ReverseImageSearchAsync(string url, YandexSearchFilterMode mode = YandexSearchFilterMode.Moderate, CancellationToken cancellationToken = default) { - EnsureNotDisposed(); + ArgumentNullException.ThrowIfNull(url); + ObjectDisposedException.ThrowIf(_disposed, this); cancellationToken.ThrowIfCancellationRequested(); - const string imageSearchRequest = @"{""blocks"":[{""block"":""content_type_similar"",""params"":{},""version"":2}]}"; + const string imageSearchRequest = """{"blocks":[{"block":"content_type_similar","params":{},"version":2}]}"""; - using var request = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri($"https://yandex.com/images/search?rpt=imageview&url={Uri.EscapeDataString(url)}&cbir_page=similar&format=json&request={imageSearchRequest}") - }; + using var request = new HttpRequestMessage(); + request.Method = HttpMethod.Get; + request.RequestUri = new Uri($"https://yandex.com/images/search?rpt=imageview&url={Uri.EscapeDataString(url)}&cbir_page=similar&format=json&request={imageSearchRequest}"); var now = DateTimeOffset.UtcNow; @@ -183,7 +175,7 @@ public async Task> ReverseImageSe .FirstOrDefault()? .GetElementsByClassName("serp-item") .Select(x => JsonDocument.Parse(x.GetAttribute("data-bem")!).RootElement.GetProperty("serp-item").Deserialize()!) - .ToArray() ?? Array.Empty(); + .ToArray() ?? []; } /// @@ -197,12 +189,4 @@ public void Dispose() _httpClient.Dispose(); _disposed = true; } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(YandexImageSearch)); - } - } } \ No newline at end of file diff --git a/src/Entities/DualLocalizedString.cs b/src/Entities/DualLocalizedString.cs index e9911d8..d785a3a 100644 --- a/src/Entities/DualLocalizedString.cs +++ b/src/Entities/DualLocalizedString.cs @@ -37,7 +37,7 @@ private DualLocalizedString(LocalizedString localizedString, IStringLocalizer lo /// public static DualLocalizedString Create(IStringLocalizer localizer, CultureInfo culture, string name) => - Create(localizer, culture, name, Array.Empty()); + Create(localizer, culture, name, []); /// /// Creates a new using the provided values. diff --git a/src/Entities/FergunHttpClientLogger.cs b/src/Entities/FergunHttpClientLogger.cs new file mode 100644 index 0000000..d7afd5b --- /dev/null +++ b/src/Entities/FergunHttpClientLogger.cs @@ -0,0 +1,45 @@ +using System; +using System.Net.Http; +using Microsoft.Extensions.Http.Logging; +using Microsoft.Extensions.Logging; + +namespace Fergun; + +public partial class FergunHttpClientLogger : IHttpClientLogger +{ + private readonly ILogger _logger; + + public FergunHttpClientLogger(ILogger logger) + { + _logger = logger; + } + + + /// + public object? LogRequestStart(HttpRequestMessage request) => null; + + /// + public void LogRequestStop(object? context, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed) + { + Log.RequestEnd(_logger, request.Method, Log.GetUriString(request.RequestUri), (int)response.StatusCode, elapsed.TotalMilliseconds); + } + + /// + public void LogRequestFailed(object? context, HttpRequestMessage request, HttpResponseMessage? response, Exception exception, + TimeSpan elapsed) + { + } + + internal static partial class Log + { + [LoggerMessage(101, LogLevel.Information, "HTTP {HttpMethod} {Uri} responded {StatusCode} in {ElapsedMilliseconds} ms", EventName = "RequestEnd")] + internal static partial void RequestEnd(ILogger logger, HttpMethod httpMethod, string? uri, int statusCode, double elapsedMilliseconds); + + internal static string? GetUriString(Uri? requestUri) + { + return requestUri?.IsAbsoluteUri == true + ? requestUri.AbsoluteUri + : requestUri?.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Entities/FergunLocalizationManager.cs b/src/Entities/FergunLocalizationManager.cs index 64074e6..0726912 100644 --- a/src/Entities/FergunLocalizationManager.cs +++ b/src/Entities/FergunLocalizationManager.cs @@ -16,8 +16,8 @@ public sealed class FergunLocalizationManager : ILocalizationManager { private const string ModulesNamespace = "Fergun.Modules"; private readonly IStringLocalizerFactory _localizerFactory; - private readonly Dictionary _types = new(); - private readonly Dictionary _modules = new(); // TODO: use Options pattern + private readonly Dictionary _types = []; + private readonly Dictionary _modules = []; // TODO: use Options pattern private readonly Dictionary _supportedLocales = new() { { "es", "es-ES" } diff --git a/src/Entities/FergunLoggingHttpMessageHandler.cs b/src/Entities/FergunLoggingHttpMessageHandler.cs deleted file mode 100644 index 56f7267..0000000 --- a/src/Entities/FergunLoggingHttpMessageHandler.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace Fergun; - -/// -/// Handles logging of an HTTP request. -/// -public class FergunLoggingHttpMessageHandler : DelegatingHandler -{ - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class with a specified logger. - /// - /// The to log to. - /// is . - public FergunLoggingHttpMessageHandler(ILogger logger) - { - ArgumentNullException.ThrowIfNull(logger); - - _logger = logger; - } - - /// - /// Loggs the request from the sent . - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(request); - - var stopwatch = Stopwatch.StartNew(); - var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); - Log.RequestEnd(_logger, request, response, stopwatch.Elapsed); - - return response; - } - - internal static class Log - { - public static readonly EventId RequestEndEvent = new(101, "RequestEnd"); - - private static readonly LogDefineOptions _skipEnabledCheckLogDefineOptions = new() { SkipEnabledCheck = true }; - - private static readonly Action _requestEnd = - LoggerMessage.Define( - LogLevel.Information, - RequestEndEvent, - "HTTP {HttpMethod} {Uri} responded {StatusCode} in {ElapsedMilliseconds} ms", _skipEnabledCheckLogDefineOptions); - - public static void RequestEnd(ILogger logger, HttpRequestMessage request, HttpResponseMessage response, TimeSpan duration) - { - if (logger.IsEnabled(LogLevel.Information)) - { - _requestEnd(logger, request.Method, GetUriString(request.RequestUri), (int)response.StatusCode, duration.TotalMilliseconds, null); - } - } - - private static string? GetUriString(Uri? requestUri) - { - return requestUri?.IsAbsoluteUri == true - ? requestUri.AbsoluteUri - : requestUri?.ToString(); - } - } -} \ No newline at end of file diff --git a/src/Entities/FergunLoggingHttpMessageHandlerBuilderFilter.cs b/src/Entities/FergunLoggingHttpMessageHandlerBuilderFilter.cs deleted file mode 100644 index 4c3ad09..0000000 --- a/src/Entities/FergunLoggingHttpMessageHandlerBuilderFilter.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Microsoft.Extensions.Http; -using Microsoft.Extensions.Logging; - -namespace Fergun; - -internal sealed class FergunLoggingHttpMessageHandlerBuilderFilter : IHttpMessageHandlerBuilderFilter -{ - private readonly ILoggerFactory _loggerFactory; - - public FergunLoggingHttpMessageHandlerBuilderFilter(ILoggerFactory loggerFactory) - { - ArgumentNullException.ThrowIfNull(loggerFactory); - - _loggerFactory = loggerFactory; - } - - /// - public Action Configure(Action next) - { - ArgumentNullException.ThrowIfNull(next); - - return builder => - { - next(builder); - - string loggerName = !string.IsNullOrEmpty(builder.Name) ? builder.Name : "Default"; - var innerLogger = _loggerFactory.CreateLogger($"System.Net.Http.HttpClient.{loggerName}.ClientHandler"); - - builder.AdditionalHandlers.Add(new FergunLoggingHttpMessageHandler(innerLogger)); - }; - } -} \ No newline at end of file diff --git a/src/Entities/IFergunLocalizer.cs b/src/Entities/IFergunLocalizer.cs index 136d23c..79d3783 100644 --- a/src/Entities/IFergunLocalizer.cs +++ b/src/Entities/IFergunLocalizer.cs @@ -19,6 +19,4 @@ public interface IFergunLocalizer : IStringLocalizer /// Represents the generic variant of . /// /// The to provide strings for. -public interface IFergunLocalizer : IFergunLocalizer, IStringLocalizer -{ -} \ No newline at end of file +public interface IFergunLocalizer : IFergunLocalizer, IStringLocalizer; \ No newline at end of file diff --git a/src/Entities/SharedResource.cs b/src/Entities/SharedResource.cs index 0b28b41..2f56bea 100644 --- a/src/Entities/SharedResource.cs +++ b/src/Entities/SharedResource.cs @@ -3,6 +3,4 @@ /// /// Used for shared localization. /// -public class SharedResource -{ -} \ No newline at end of file +public class SharedResource; \ No newline at end of file diff --git a/src/Extensions/Extensions.cs b/src/Extensions/Extensions.cs index 5c94064..323b451 100644 --- a/src/Extensions/Extensions.cs +++ b/src/Extensions/Extensions.cs @@ -34,7 +34,8 @@ public static string Display(this IInteractionContext context) public static string Dump(this T obj, int maxDepth = 2) { - using var strWriter = new StringWriter { NewLine = "\n" }; + using var strWriter = new StringWriter(); + strWriter.NewLine = "\n"; using var jsonWriter = new CustomJsonTextWriter(strWriter); var resolver = new CustomContractResolver(jsonWriter, maxDepth); var serializer = new JsonSerializer diff --git a/src/Extensions/HostBuilderExtensions.cs b/src/Extensions/HostBuilderExtensions.cs deleted file mode 100644 index f713be1..0000000 --- a/src/Extensions/HostBuilderExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Http; - -namespace Fergun.Extensions; - -/// -/// Contains extension methods for . -/// -public static class HostBuilderExtensions -{ - /// - /// Replaces the default HTTP logging with a simpler one. - /// - /// The host builder. - /// The host builder. - public static IHostBuilder UseFergunRequestLogging(this IHostBuilder builder) - { - return builder.ConfigureServices(services => - { - services.RemoveAll(); - services.AddSingleton(); - }); - } -} \ No newline at end of file diff --git a/src/Extensions/PaginatorExtensions.cs b/src/Extensions/PaginatorExtensions.cs index a80ea00..62cff2b 100644 --- a/src/Extensions/PaginatorExtensions.cs +++ b/src/Extensions/PaginatorExtensions.cs @@ -9,12 +9,12 @@ namespace Fergun.Extensions; public static class PaginatorExtensions { private static readonly PaginatorAction[] _defaultActions = - { + [ PaginatorAction.Backward, PaginatorAction.Forward, PaginatorAction.Jump, PaginatorAction.Exit - }; + ]; /// /// Adds Fergun emotes. @@ -61,7 +61,7 @@ public static TBuilder WithLocalizedPrompts(this Paginator }; builder.WithJumpInputPrompt(localizer["JumpInputPrompt"]); - builder.WithJumpInputTextLabel(localizer["JumptInputTextLabel", 1, pageCount]); + builder.WithJumpInputTextLabel(localizer["JumpInputTextLabel", 1, pageCount]); builder.WithInvalidJumpInputMessage(localizer["InvalidJumpInput", 1, pageCount]); builder.WithJumpInputInUseMessage(localizer["JumpInputInUse"]); builder.WithExpiredJumpInputMessage(localizer["ExpiredJumpInput", builder.JumpInputTimeout.TotalSeconds]); diff --git a/src/Fergun.csproj b/src/Fergun.csproj index 82e4c3f..719fb89 100644 --- a/src/Fergun.csproj +++ b/src/Fergun.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 disable enable True @@ -10,6 +10,7 @@ + @@ -17,23 +18,24 @@ - + - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + - - - + + + - + diff --git a/src/Hardware/LinuxHardwareInfo.cs b/src/Hardware/LinuxHardwareInfo.cs index e77135d..49b189f 100644 --- a/src/Hardware/LinuxHardwareInfo.cs +++ b/src/Hardware/LinuxHardwareInfo.cs @@ -140,12 +140,12 @@ private static bool TryReadFileLines(string path, out string[] lines) } catch { - lines = Array.Empty(); + lines = []; return false; } } - lines = Array.Empty(); + lines = []; return false; } diff --git a/src/Hardware/MemoryStatus.cs b/src/Hardware/MemoryStatus.cs index 648d17b..cdddbbb 100644 --- a/src/Hardware/MemoryStatus.cs +++ b/src/Hardware/MemoryStatus.cs @@ -6,7 +6,7 @@ public readonly record struct MemoryStatus { /// - /// Gets the total physical, memory, in bytes. + /// Gets the total physical memory, in bytes. /// public long TotalPhysicalMemory { get; init; } diff --git a/src/Program.cs b/src/Program.cs index 9fc31a0..4eda3f8 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -77,7 +77,7 @@ FormatUsersInBidirectionalUnicode = false }; - config.Token = context.Configuration.GetSection(StartupOptions.Startup).Get().Token; + config.Token = context.Configuration.GetSection(StartupOptions.Startup).Get()!.Token; }) .UseInteractionService((_, config) => { @@ -102,8 +102,14 @@ services.AddSingleton(); services.AddHostedService(); services.AddHostedService(); + services.ConfigureHttpClientDefaults(b => + { + b.RemoveAllLoggers(); + b.AddLogger(); + }); services.AddSingleton(new InteractiveConfig { ReturnAfterSendingPaginator = true, DeferStopSelectionInteractions = false }); services.AddSingleton(); + services.AddSingleton(); services.AddHostedService(); services.AddSingleton(); services.AddFergunPolicies(); @@ -196,7 +202,6 @@ services.AddTransient(s => new DuckDuckGoScraper(s.GetRequiredService().CreateClient(nameof(DuckDuckGoScraper)))); services.AddTransient(); }) - .UseFergunRequestLogging() .Build(); // Semi-automatic migration diff --git a/src/Resources/SharedResource.es.resx b/src/Resources/SharedResource.es.resx index c48a600..4a4e68a 100644 --- a/src/Resources/SharedResource.es.resx +++ b/src/Resources/SharedResource.es.resx @@ -168,7 +168,7 @@ Ingresa un número de página - + Número de página ({0}-{1}) diff --git a/src/Resources/SharedResource.resx b/src/Resources/SharedResource.resx index 5b45ce7..f64c09b 100644 --- a/src/Resources/SharedResource.resx +++ b/src/Resources/SharedResource.resx @@ -168,7 +168,7 @@ Enter a page number - + Page number ({0}-{1}) diff --git a/src/Services/InteractionHandlingService.cs b/src/Services/InteractionHandlingService.cs index 2ce3334..dc1c97e 100644 --- a/src/Services/InteractionHandlingService.cs +++ b/src/Services/InteractionHandlingService.cs @@ -44,7 +44,7 @@ public InteractionHandlingService(DiscordShardedClient client, InteractionServic _services = services; _testingGuildId = options.Value.TestingGuildId; _ownerCommandsGuildId = options.Value.OwnerCommandsGuildId; - _interactionService.LocalizationManager = _localizationManager; // Should be set while configuring the services but it's not possible + _interactionService.LocalizationManager = _localizationManager; // Should be set while configuring the services, but it's not possible } /// @@ -303,7 +303,7 @@ private Task AutocompleteHandlerExecutedAsync(IAutocompleteHandler autocompleteC private async Task HandleCommandExecutedAsync(string commandName, ApplicationCommandType? type, IInteractionContext context, IResult result) { - EnsureNotDisposed(); + ObjectDisposedException.ThrowIf(_disposed, this); if (type is not null) { @@ -385,12 +385,4 @@ private async Task HandleCommandExecutedAsync(string commandName, ApplicationCom await interaction.RespondAsync(embed: embed, ephemeral: ephemeral); } } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(InteractionHandlingService)); - } - } } \ No newline at end of file diff --git a/src/Utils/CommandUtils.cs b/src/Utils/CommandUtils.cs index 137a86f..b0712ad 100644 --- a/src/Utils/CommandUtils.cs +++ b/src/Utils/CommandUtils.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Runtime.InteropServices; namespace Fergun.Utils; @@ -8,8 +7,8 @@ public static class CommandUtils { public static string? RunCommand(string command) { - bool isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + bool isLinux = OperatingSystem.IsLinux(); + bool isWindows = OperatingSystem.IsWindows(); if (!isLinux && !isWindows) return null; @@ -25,7 +24,8 @@ public static class CommandUtils WorkingDirectory = isLinux ? "/home" : string.Empty }; - using var process = new Process { StartInfo = startInfo }; + using var process = new Process(); + process.StartInfo = startInfo; process.Start(); process.WaitForExit(10000); diff --git a/tests/Fergun.Tests/Apis/GoogleLensTests.cs b/tests/Fergun.Tests/Apis/GoogleLensTests.cs index c3bd66e..1aab685 100644 --- a/tests/Fergun.Tests/Apis/GoogleLensTests.cs +++ b/tests/Fergun.Tests/Apis/GoogleLensTests.cs @@ -16,7 +16,7 @@ public class GoogleLensTests [InlineData("https://upload.wikimedia.org/wikipedia/commons/5/57/Lorem_Ipsum_Helvetica.png")] public async Task OcrAsync_Returns_Text(string url) { - string? text = await _googleLens.OcrAsync(url); + string text = await _googleLens.OcrAsync(url); Assert.NotNull(text); Assert.NotEmpty(text); diff --git a/tests/Fergun.Tests/Apis/WolframAlphaTests.cs b/tests/Fergun.Tests/Apis/WolframAlphaTests.cs index 02d3eac..17f1259 100644 --- a/tests/Fergun.Tests/Apis/WolframAlphaTests.cs +++ b/tests/Fergun.Tests/Apis/WolframAlphaTests.cs @@ -178,7 +178,7 @@ public void ArrayOrObjectConverter_Throws_Exceptions() { { "true", null }, { "false", null }, - { "{\"code\":\"1000\",\"msg\":\"error\"}", new WolframAlphaErrorInfo(1000, "error") } + { """{"code":"1000","msg":"error"}""", new WolframAlphaErrorInfo(1000, "error") } }; } diff --git a/tests/Fergun.Tests/Converters/MicrosoftVoiceConverterTests.cs b/tests/Fergun.Tests/Converters/MicrosoftVoiceConverterTests.cs index a29d54b..f3f6cdc 100644 --- a/tests/Fergun.Tests/Converters/MicrosoftVoiceConverterTests.cs +++ b/tests/Fergun.Tests/Converters/MicrosoftVoiceConverterTests.cs @@ -16,13 +16,12 @@ using System; using Bogus.DataSets; using Discord.Interactions; -using System.Text; namespace Fergun.Tests.Converters; public class MicrosoftVoiceConverterTests { - private static readonly byte[] _microsoftTokenResponse = Encoding.UTF8.GetBytes("{\"r\":\"westus\",\"t\":\"dGVzdA==.eyJleHAiOjIxNDc0ODM2NDd9.dGVzdA==\"}"); + private static readonly byte[] _microsoftTokenResponse = """{"r":"westus","t":"dGVzdA==.eyJleHAiOjIxNDc0ODM2NDd9.dGVzdA=="}"""u8.ToArray(); [Fact] public void MicrosoftVoiceConverter_GetDiscordType_Returns_String() @@ -119,7 +118,7 @@ private static MicrosoftTranslator CreateMockedMicrosoftTranslator(Func new(HttpStatusCode.OK) { Content = new ByteArrayContent(data) }; + private static HttpResponseMessage GetResponseMessage(byte[] data) => new(HttpStatusCode.OK) { Content = new ReadOnlyMemoryContent(data) }; public static TheoryData GetMicrosoftVoiceTestData() { diff --git a/tests/Fergun.Tests/Extensions/ListExtensionsTests.cs b/tests/Fergun.Tests/Extensions/ListExtensionsTests.cs index f3575a7..ce2f73d 100644 --- a/tests/Fergun.Tests/Extensions/ListExtensionsTests.cs +++ b/tests/Fergun.Tests/Extensions/ListExtensionsTests.cs @@ -10,8 +10,8 @@ public class ListExtensionsTests [Fact] public void List_Shuffle_Returns_Expected_Order() { - int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - int[] shuffled = { 1, 5, 6, 9, 3, 2, 4, 7, 10, 8 }; + int[] arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + int[] shuffled = [1, 5, 6, 9, 3, 2, 4, 7, 10, 8]; var rng = new Random(0); arr.Shuffle(rng); diff --git a/tests/Fergun.Tests/Extensions/TimestampExtensionsTests.cs b/tests/Fergun.Tests/Extensions/TimestampExtensionsTests.cs index 72bcedf..8064a8b 100644 --- a/tests/Fergun.Tests/Extensions/TimestampExtensionsTests.cs +++ b/tests/Fergun.Tests/Extensions/TimestampExtensionsTests.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using Bogus; using Fergun.Extensions; using Xunit; @@ -20,10 +18,10 @@ public void DateTimeOffset_ToDiscordTimestamp_Should_Return_Expected(DateTimeOff Assert.Equal(timestamp, $""); } - public static IEnumerable GetDatesAndStyles() + public static TheoryData GetDatesAndStyles() { var faker = new Faker(); return faker.MakeLazy(10, () => (faker.Date.BetweenOffset(DateTimeOffset.MinValue, DateTimeOffset.MaxValue), faker.Random.Char())) - .Select(x => new object[] { x.Item1, x.Item2 }); + .ToTheoryData(); } } \ No newline at end of file diff --git a/tests/Fergun.Tests/Fergun.Tests.csproj b/tests/Fergun.Tests/Fergun.Tests.csproj index ab62bb4..d4cecf1 100644 --- a/tests/Fergun.Tests/Fergun.Tests.csproj +++ b/tests/Fergun.Tests/Fergun.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 disable enable True @@ -10,10 +10,10 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all