diff --git a/.editorconfig b/.editorconfig index c38b46fc6..3bbd89e48 100644 --- a/.editorconfig +++ b/.editorconfig @@ -493,7 +493,7 @@ dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace, dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = * dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * -dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance dotnet_separate_import_directive_groups = false @@ -508,7 +508,7 @@ dotnet_style_qualification_for_field = false:warning dotnet_style_qualification_for_method = false:warning dotnet_style_qualification_for_property = false:warning dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion -file_header_template = +file_header_template = fsharp_align_function_signature_to_indentation = false fsharp_alternative_long_member_definitions = false fsharp_indent_on_try_with = false @@ -659,6 +659,7 @@ resharper_cpp_wrap_lines = true resharper_cpp_wrap_multiple_type_parameter_constraints_style = chop_if_long resharper_cpp_wrap_parameters_style = wrap_if_long resharper_csharp_align_multiline_binary_expressions_chain = true +resharper_csharp_align_multiline_calls_chain = false resharper_csharp_align_multiline_expression = false resharper_csharp_align_multiline_for_stmt = false resharper_csharp_blank_lines_around_single_line_invocable = 1 @@ -666,6 +667,7 @@ resharper_csharp_case_block_braces = next_line_shifted_2 resharper_csharp_empty_block_style = multiline resharper_csharp_keep_blank_lines_in_code = 1 resharper_csharp_keep_blank_lines_in_declarations = 1 +resharper_csharp_keep_existing_enum_arrangement = false resharper_csharp_keep_nontrivial_alias = false resharper_csharp_max_line_length = 3073 resharper_csharp_naming_rule.enum_member = AaBb @@ -676,13 +678,14 @@ resharper_csharp_space_after_unary_operator = false resharper_csharp_space_around_alias_eq = false resharper_csharp_stick_comment = false resharper_csharp_wrap_after_declaration_lpar = true -resharper_csharp_wrap_after_invocation_lpar = true +resharper_csharp_wrap_after_invocation_lpar = false resharper_csharp_wrap_arguments_style = chop_if_long resharper_csharp_wrap_before_binary_opsign = true resharper_csharp_wrap_before_first_type_parameter_constraint = true resharper_csharp_wrap_before_ternary_opsigns = false resharper_csharp_wrap_extends_list_style = chop_if_long resharper_csharp_wrap_lines = true +resharper_csharp_wrap_multiple_declaration_style = chop_always resharper_csharp_wrap_multiple_type_parameter_constraints_style = chop_always resharper_csharp_wrap_parameters_style = chop_if_long resharper_cxxcli_property_declaration_braces = next_line @@ -712,8 +715,8 @@ resharper_force_attribute_style = separate resharper_force_chop_compound_do_expression = false resharper_force_chop_compound_if_expression = false resharper_force_chop_compound_while_expression = false -resharper_formatter_off_tag = -resharper_formatter_on_tag = +resharper_formatter_off_tag = +resharper_formatter_on_tag = resharper_formatter_tags_accept_regexp = false resharper_formatter_tags_enabled = false resharper_format_leading_spaces_decl = false @@ -757,7 +760,7 @@ resharper_indent_nested_while_stmt = false resharper_indent_pars = inside resharper_indent_preprocessor_directives = none resharper_indent_preprocessor_if = no_indent -resharper_indent_preprocessor_other = do_not_change +resharper_indent_preprocessor_other = no_indent resharper_indent_preprocessor_region = usual_indent resharper_indent_primary_constructor_decl_pars = inside resharper_indent_raw_literal_string = align @@ -776,13 +779,14 @@ resharper_int_align_enum_initializers = false resharper_int_align_eq = false resharper_int_align_fix_in_adjacent = true resharper_keep_existing_attribute_arrangement = false +resharper_keep_existing_declaration_block_arrangement = false resharper_keep_existing_declaration_parens_arrangement = false resharper_keep_existing_embedded_arrangement = false resharper_keep_existing_expr_member_arrangement = false resharper_keep_existing_invocation_parens_arrangement = false resharper_keep_existing_linebreaks = false resharper_keep_existing_line_break_before_declaration_body = true -resharper_keep_existing_list_patterns_arrangement = false +resharper_keep_existing_list_patterns_arrangement = true resharper_keep_existing_primary_constructor_declaration_parens_arrangement = false resharper_keep_existing_property_patterns_arrangement = false resharper_keep_existing_switch_expression_arrangement = false @@ -806,19 +810,19 @@ resharper_line_break_before_requires_clause = do_not_change resharper_linkage_specification_braces = end_of_line resharper_linkage_specification_indentation = none resharper_local_function_body = block_body -resharper_macro_block_begin = -resharper_macro_block_end = +resharper_macro_block_begin = +resharper_macro_block_end = resharper_max_array_initializer_elements_on_line = 1 resharper_max_attribute_length_for_same_line = 10000 resharper_max_enum_members_on_line = 1 -resharper_max_formal_parameters_on_line = 1 +resharper_max_formal_parameters_on_line = 6 resharper_max_initializer_elements_on_line = 1 -resharper_max_invocation_arguments_on_line = 3 +resharper_max_invocation_arguments_on_line = 6 resharper_max_primary_constructor_parameters_on_line = 1 resharper_member_initializer_list_style = do_not_change resharper_namespace_declaration_braces = next_line resharper_namespace_indentation = all -resharper_nested_ternary_style = autodetect +resharper_nested_ternary_style = compact resharper_never_outdent_pipe_operators = true resharper_new_line_before_catch = true resharper_new_line_before_else = true @@ -848,12 +852,12 @@ resharper_place_accessor_attribute_on_same_line = if_owner_is_single_line resharper_place_comments_at_first_column = false resharper_place_constructor_initializer_on_same_line = false resharper_place_event_attribute_on_same_line = false -resharper_place_expr_accessor_on_single_line = if_owner_is_single_line +resharper_place_expr_accessor_on_single_line = true resharper_place_expr_method_on_single_line = true resharper_place_expr_property_on_single_line = true resharper_place_field_attribute_on_same_line = false -resharper_place_linq_into_on_new_line = true -resharper_place_method_attribute_on_same_line = if_owner_is_single_line +resharper_place_linq_into_on_new_line = false +resharper_place_method_attribute_on_same_line = false resharper_place_namespace_definitions_on_same_line = false resharper_place_property_attribute_on_same_line = false resharper_place_record_field_attribute_on_same_line = if_owner_is_single_line @@ -880,7 +884,7 @@ resharper_remove_unused_only_aliases = false resharper_requires_expression_braces = next_line resharper_resx_attribute_indent = single_indent resharper_resx_keep_user_linebreaks = true -resharper_resx_linebreak_before_elements = +resharper_resx_linebreak_before_elements = resharper_resx_max_blank_lines_between_tags = 0 resharper_resx_max_line_length = 2147483647 resharper_resx_pi_attribute_style = do_not_touch @@ -1091,23 +1095,23 @@ resharper_wrap_before_declaration_rpar = false resharper_wrap_before_eq = false resharper_wrap_before_expression_rbrace = true resharper_wrap_before_extends_colon = true -resharper_wrap_before_first_method_call = true +resharper_wrap_before_first_method_call = false resharper_wrap_before_invocation_lpar = false resharper_wrap_before_invocation_rpar = false resharper_wrap_before_linq_expression = false -resharper_wrap_before_primary_constructor_declaration_lpar = false -resharper_wrap_before_primary_constructor_declaration_rpar = false +resharper_wrap_before_primary_constructor_declaration_lpar = true +resharper_wrap_before_primary_constructor_declaration_rpar = true resharper_wrap_before_type_parameter_langle = false resharper_wrap_braced_init_list_style = wrap_if_long resharper_wrap_chained_binary_expressions = chop_if_long resharper_wrap_chained_binary_patterns = chop_if_long -resharper_wrap_chained_method_calls = chop_always +resharper_wrap_chained_method_calls = chop_if_long resharper_wrap_ctor_initializer_style = wrap_if_long resharper_wrap_enumeration_style = chop_if_long resharper_wrap_enum_declaration = chop_always resharper_wrap_for_stmt_header_style = chop_if_long resharper_wrap_linq_expressions = chop_always -resharper_wrap_list_pattern = chop_always +resharper_wrap_list_pattern = chop_if_long resharper_wrap_multiple_declaration_style = chop_if_long resharper_wrap_primary_constructor_parameters_style = chop_if_long resharper_wrap_property_pattern = chop_always @@ -1126,7 +1130,7 @@ resharper_xmldoc_wrap_tags_and_pi = true resharper_xmldoc_wrap_text = true resharper_xml_attribute_indent = align_by_first_attribute resharper_xml_keep_user_linebreaks = true -resharper_xml_linebreak_before_elements = +resharper_xml_linebreak_before_elements = resharper_xml_max_blank_lines_between_tags = 2 resharper_xml_max_line_length = 120 resharper_xml_pi_attribute_style = do_not_touch @@ -3897,7 +3901,7 @@ dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_style.static_field_style.capitalization = camel_case -dotnet_naming_style.static_field_style.required_prefix = +dotnet_naming_style.static_field_style.required_prefix = # Instance fields are camelCase dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion @@ -3907,7 +3911,7 @@ dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_s dotnet_naming_symbols.instance_fields.applicable_kinds = field dotnet_naming_style.instance_field_style.capitalization = camel_case -dotnet_naming_style.instance_field_style.required_prefix = +dotnet_naming_style.instance_field_style.required_prefix = # Locals and parameters are camelCase dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion diff --git a/Game Localization.xlsm b/Game Localization.xlsm new file mode 100644 index 000000000..a4b5bb27b Binary files /dev/null and b/Game Localization.xlsm differ diff --git a/Game Localiztion.xlsm b/Game Localiztion.xlsm deleted file mode 100644 index 1fd8840aa..000000000 Binary files a/Game Localiztion.xlsm and /dev/null differ diff --git a/GitVersion.yml b/GitVersion.yml index 54556708d..7fcc525d4 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 3.0 +next-version: 4.0 assembly-versioning-scheme: MajorMinorPatch assembly-file-versioning-scheme: MajorMinorPatchTag assembly-informational-format: '{Major}.{Minor}.{Patch}.{BuildMetaData}' diff --git a/src/Sidekick.Apis.Poe/Authentication/AuthenticationService.cs b/src/Sidekick.Apis.Poe/Authentication/AuthenticationService.cs index a911d59e6..4ba2cbab1 100644 --- a/src/Sidekick.Apis.Poe/Authentication/AuthenticationService.cs +++ b/src/Sidekick.Apis.Poe/Authentication/AuthenticationService.cs @@ -3,7 +3,6 @@ using System.Text.Json; using Sidekick.Apis.Poe.Authentication.Models; using Sidekick.Common.Browser; -using Sidekick.Common.Initialization; using Sidekick.Common.Platform.Interprocess; using Sidekick.Common.Settings; @@ -46,13 +45,6 @@ public AuthenticationService( private TaskCompletionSource? AuthenticateTask { get; set; } - public InitializationPriority Priority => InitializationPriority.Low; - - public Task Initialize() - { - return Task.CompletedTask; - } - public async Task GetCurrentState() { if (AuthenticateTask != null && AuthenticateTask.Task.Status != TaskStatus.RanToCompletion) diff --git a/src/Sidekick.Apis.Poe/Authentication/IAuthenticationService.cs b/src/Sidekick.Apis.Poe/Authentication/IAuthenticationService.cs index 6537edf25..0151649a3 100644 --- a/src/Sidekick.Apis.Poe/Authentication/IAuthenticationService.cs +++ b/src/Sidekick.Apis.Poe/Authentication/IAuthenticationService.cs @@ -2,7 +2,7 @@ namespace Sidekick.Apis.Poe.Authentication { - public interface IAuthenticationService : IInitializableService + public interface IAuthenticationService { event Action? OnStateChanged; diff --git a/src/Sidekick.Apis.Poe/Bulk/BulkTradeService.cs b/src/Sidekick.Apis.Poe/Bulk/BulkTradeService.cs index a29f6a2a7..ca7bc2f31 100644 --- a/src/Sidekick.Apis.Poe/Bulk/BulkTradeService.cs +++ b/src/Sidekick.Apis.Poe/Bulk/BulkTradeService.cs @@ -9,6 +9,7 @@ using Sidekick.Apis.Poe.Trade.Results; using Sidekick.Common.Enums; using Sidekick.Common.Exceptions; +using Sidekick.Common.Extensions; using Sidekick.Common.Game.Items; using Sidekick.Common.Game.Languages; using Sidekick.Common.Settings; @@ -33,13 +34,8 @@ public async Task SearchBulk(Item item, TradeCurrency currenc { logger.LogInformation("[Trade API] Querying Exchange API."); - if (gameLanguageProvider.Language == null) - { - throw new ApiErrorException("[Trade API] Could not find a valid language."); - } - var leagueId = await settingsService.GetString(SettingKeys.LeagueId); - var uri = $"{gameLanguageProvider.Language.PoeTradeApiBaseUrl}exchange/{leagueId}"; + var uri = $"{gameLanguageProvider.Language.GetTradeApiBaseUrl(item.Metadata.Game)}exchange/{leagueId.GetUrlSlugForLeague()}"; var itemId = itemStaticDataProvider.GetId(item.Metadata); if (itemId == null) @@ -102,14 +98,10 @@ public async Task SearchBulk(Item item, TradeCurrency currenc public async Task GetTradeUri(Item item, string queryId) { - var baseUri = gameLanguageProvider.Language?.PoeTradeExchangeBaseUrl; - if (baseUri == null) - { - throw new Exception("[Trade API] Could not find the trade uri."); - } - + var baseUrl = gameLanguageProvider.Language.GetTradeBaseUrl(item.Metadata.Game); + var baseUri = new Uri(baseUrl + "exchange/"); var leagueId = await settingsService.GetString(SettingKeys.LeagueId); - return new Uri(baseUri, $"{leagueId}/{queryId}"); + return new Uri(baseUri, $"{leagueId.GetUrlSlugForLeague()}/{queryId}"); } } } diff --git a/src/Sidekick.Apis.Poe/Clients/ClientNames.cs b/src/Sidekick.Apis.Poe/Clients/ClientNames.cs deleted file mode 100644 index eaae0878b..000000000 --- a/src/Sidekick.Apis.Poe/Clients/ClientNames.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Sidekick.Apis.Poe.Clients -{ - public static class ClientNames - { - public const string POECLIENT = "PoeClient"; - public const string TRADECLIENT = "PoeTradeClient"; - } -} diff --git a/src/Sidekick.Apis.Poe/Clients/PoeApiException.cs b/src/Sidekick.Apis.Poe/Clients/Exceptions/PoeApiException.cs similarity index 88% rename from src/Sidekick.Apis.Poe/Clients/PoeApiException.cs rename to src/Sidekick.Apis.Poe/Clients/Exceptions/PoeApiException.cs index 93afbb5bb..90454e607 100644 --- a/src/Sidekick.Apis.Poe/Clients/PoeApiException.cs +++ b/src/Sidekick.Apis.Poe/Clients/Exceptions/PoeApiException.cs @@ -1,4 +1,4 @@ -namespace Sidekick.Apis.Poe.Clients +namespace Sidekick.Apis.Poe.Clients.Exceptions { [Serializable] public class PoeApiException : Exception diff --git a/src/Sidekick.Apis.Poe/Clients/FetchResult.cs b/src/Sidekick.Apis.Poe/Clients/FetchResult.cs deleted file mode 100644 index a1b04c41d..000000000 --- a/src/Sidekick.Apis.Poe/Clients/FetchResult.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Sidekick.Apis.Poe.Clients -{ - public class FetchResult - { - public List Result { get; set; } = new(); - } -} diff --git a/src/Sidekick.Apis.Poe/Clients/IPoeApiClient.cs b/src/Sidekick.Apis.Poe/Clients/IPoeApiClient.cs index 1c8f454f2..38f5b5773 100644 --- a/src/Sidekick.Apis.Poe/Clients/IPoeApiClient.cs +++ b/src/Sidekick.Apis.Poe/Clients/IPoeApiClient.cs @@ -1,11 +1,6 @@ -using System.Text.Json; +namespace Sidekick.Apis.Poe.Clients; -namespace Sidekick.Apis.Poe.Clients +public interface IPoeApiClient { - public interface IPoeApiClient - { - JsonSerializerOptions Options { get; } - - Task Fetch(string path); - } + Task Fetch(string path); } diff --git a/src/Sidekick.Apis.Poe/Clients/IPoeTradeClient.cs b/src/Sidekick.Apis.Poe/Clients/IPoeTradeClient.cs index 8da067786..0ee987d50 100644 --- a/src/Sidekick.Apis.Poe/Clients/IPoeTradeClient.cs +++ b/src/Sidekick.Apis.Poe/Clients/IPoeTradeClient.cs @@ -1,13 +1,15 @@ using System.Text.Json; +using Sidekick.Apis.Poe.Clients.Models; +using Sidekick.Common.Game; +using Sidekick.Common.Game.Languages; -namespace Sidekick.Apis.Poe.Clients +namespace Sidekick.Apis.Poe.Clients; + +public interface IPoeTradeClient { - public interface IPoeTradeClient - { - HttpClient HttpClient { get; set; } + HttpClient HttpClient { get; } - JsonSerializerOptions Options { get; } + JsonSerializerOptions Options { get; } - Task> Fetch(string path, bool useDefaultLanguage = false); - } + Task> Fetch(GameType game, IGameLanguage language, string path); } diff --git a/src/Sidekick.Apis.Poe/Clients/Models/ClientNames.cs b/src/Sidekick.Apis.Poe/Clients/Models/ClientNames.cs new file mode 100644 index 000000000..fd156f8e6 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Clients/Models/ClientNames.cs @@ -0,0 +1,8 @@ +namespace Sidekick.Apis.Poe.Clients.Models +{ + public static class ClientNames + { + public const string PoeClient = "PoeClient"; + public const string TradeClient = "PoeTradeClient"; + } +} diff --git a/src/Sidekick.Apis.Poe/Clients/Models/FetchResult.cs b/src/Sidekick.Apis.Poe/Clients/Models/FetchResult.cs new file mode 100644 index 000000000..f814cb9c1 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Clients/Models/FetchResult.cs @@ -0,0 +1,7 @@ +namespace Sidekick.Apis.Poe.Clients.Models +{ + public class FetchResult + { + public List Result { get; init; } = new(); + } +} diff --git a/src/Sidekick.Apis.Poe/Clients/PoeApiClient.cs b/src/Sidekick.Apis.Poe/Clients/PoeApiClient.cs index fc5229cf1..2a912ffc1 100644 --- a/src/Sidekick.Apis.Poe/Clients/PoeApiClient.cs +++ b/src/Sidekick.Apis.Poe/Clients/PoeApiClient.cs @@ -1,75 +1,76 @@ using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Logging; +using Sidekick.Apis.Poe.Clients.Exceptions; +using Sidekick.Apis.Poe.Clients.Models; using Sidekick.Common.Settings; -namespace Sidekick.Apis.Poe.Clients +namespace Sidekick.Apis.Poe.Clients; + +public class PoeApiClient : IPoeApiClient { - public class PoeApiClient : IPoeApiClient - { - private const string PoeApiUrl = "https://api.pathofexile.com/"; + private const string PoeApiUrl = "https://api.pathofexile.com/"; - private readonly ILogger logger; - private readonly ISettingsService settingsService; + private readonly ILogger logger; + private readonly ISettingsService settingsService; - public PoeApiClient( - ILogger logger, - IHttpClientFactory httpClientFactory, - ISettingsService settingsService) - { - this.logger = logger; - this.settingsService = settingsService; + public PoeApiClient( + ILogger logger, + IHttpClientFactory httpClientFactory, + ISettingsService settingsService) + { + this.logger = logger; + this.settingsService = settingsService; - HttpClient = httpClientFactory.CreateClient(ClientNames.POECLIENT); - HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Powered-By", "Sidekick"); - HttpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("Sidekick"); - HttpClient.BaseAddress = new Uri(PoeApiUrl); - HttpClient.Timeout = TimeSpan.FromHours(1); + HttpClient = httpClientFactory.CreateClient(ClientNames.PoeClient); + HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Powered-By", "Sidekick"); + HttpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("Sidekick"); + HttpClient.BaseAddress = new Uri(PoeApiUrl); + HttpClient.Timeout = TimeSpan.FromHours(1); - Options = new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - }; - Options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); - } + Options = new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + Options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); + } - public JsonSerializerOptions Options { get; } + private JsonSerializerOptions Options { get; } - private HttpClient HttpClient { get; set; } + private HttpClient HttpClient { get; set; } - public async Task Fetch(string path) + public async Task Fetch(string path) + { + try { - try - { - var response = await HttpClient.GetAsync(path); + var response = await HttpClient.GetAsync(path); - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - await settingsService.Set(SettingKeys.BearerToken, null); - await settingsService.Set(SettingKeys.BearerExpiration, null); - throw new PoeApiException("Poe API: Unauthorized."); - } - - var content = await response.Content.ReadAsStreamAsync(); - var result = await JsonSerializer.DeserializeAsync(content, Options); - if (result != null) - { - return result; - } - } - catch (TimeoutException e) + if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) { - logger.LogError(e, "[Poe Api Client] The API is timed out due to too many requests."); - return default; + await settingsService.Set(SettingKeys.BearerToken, null); + await settingsService.Set(SettingKeys.BearerExpiration, null); + throw new PoeApiException("Poe API: Unauthorized."); } - catch (Exception e) + + var content = await response.Content.ReadAsStreamAsync(); + var result = await JsonSerializer.DeserializeAsync(content, Options); + if (result != null) { - logger.LogError($"[Poe Api Client] Could not fetch {typeof(TReturn).Name} at {HttpClient.BaseAddress + path}.", e); - throw; + return result; } - - throw new PoeApiException("[Poe Api Client] Could not understand the API response."); } + catch (TimeoutException e) + { + logger.LogError(e, "[Poe Api Client] The API is timed out due to too many requests."); + return default; + } + catch (Exception e) + { + logger.LogError($"[Poe Api Client] Could not fetch {typeof(TReturn).Name} at {HttpClient.BaseAddress + path}.", e); + throw; + } + + throw new PoeApiException("[Poe Api Client] Could not understand the API response."); } } diff --git a/src/Sidekick.Apis.Poe/Clients/PoeApiHandler.cs b/src/Sidekick.Apis.Poe/Clients/PoeApiHandler.cs index 66c8288c7..778a85069 100644 --- a/src/Sidekick.Apis.Poe/Clients/PoeApiHandler.cs +++ b/src/Sidekick.Apis.Poe/Clients/PoeApiHandler.cs @@ -2,6 +2,7 @@ using System.Threading.RateLimiting; using Sidekick.Apis.Poe.Authentication; using Sidekick.Apis.Poe.Clients.Limiter; +using Sidekick.Apis.Poe.Clients.Models; using Sidekick.Apis.Poe.Clients.States; namespace Sidekick.Apis.Poe.Clients @@ -24,7 +25,7 @@ protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { - apiStateProvider.Update(ClientNames.POECLIENT, ApiState.Throttled); + apiStateProvider.Update(ClientNames.PoeClient, ApiState.Throttled); using var concurrencyLease = await concurrencyLimiter.AcquireAsync(cancellationToken: cancellationToken); @@ -34,7 +35,7 @@ protected override async Task SendAsync( leases.Add(await limitRule.Limiter.AcquireAsync(cancellationToken: cancellationToken)); } - apiStateProvider.Update(ClientNames.POECLIENT, ApiState.Working); + apiStateProvider.Update(ClientNames.PoeClient, ApiState.Working); var token = await authenticationService.GetToken(); if (string.IsNullOrEmpty(token)) @@ -115,7 +116,7 @@ private async Task ParseRateLimitHeaders(HttpResponseMessage response) if (response.Headers.TryGetValues("Retry-After", out var retryAfter)) { - apiStateProvider.Update(ClientNames.POECLIENT, ApiState.TimedOut); + apiStateProvider.Update(ClientNames.PoeClient, ApiState.TimedOut); await Task.Delay(TimeSpan.FromSeconds(int.Parse(retryAfter.First()) + 5)); throw new TimeoutException(); } diff --git a/src/Sidekick.Apis.Poe/Clients/PoeTradeClient.cs b/src/Sidekick.Apis.Poe/Clients/PoeTradeClient.cs index 0c773824b..0fe9b8b67 100644 --- a/src/Sidekick.Apis.Poe/Clients/PoeTradeClient.cs +++ b/src/Sidekick.Apis.Poe/Clients/PoeTradeClient.cs @@ -1,6 +1,8 @@ using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Logging; +using Sidekick.Apis.Poe.Clients.Models; +using Sidekick.Common.Game; using Sidekick.Common.Game.Languages; namespace Sidekick.Apis.Poe.Clients @@ -8,16 +10,13 @@ namespace Sidekick.Apis.Poe.Clients public class PoeTradeClient : IPoeTradeClient { private readonly ILogger logger; - private readonly IGameLanguageProvider gameLanguageProvider; public PoeTradeClient( ILogger logger, - IGameLanguageProvider gameLanguageProvider, IHttpClientFactory httpClientFactory) { this.logger = logger; - this.gameLanguageProvider = gameLanguageProvider; - HttpClient = httpClientFactory.CreateClient(ClientNames.TRADECLIENT); + HttpClient = httpClientFactory.CreateClient(ClientNames.TradeClient); HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Powered-By", "Sidekick"); HttpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("Sidekick"); @@ -31,22 +30,15 @@ public PoeTradeClient( public JsonSerializerOptions Options { get; } - public HttpClient HttpClient { get; set; } + public HttpClient HttpClient { get; } - public async Task> Fetch(string path, bool useDefaultLanguage = false) + public async Task> Fetch(GameType game, IGameLanguage language, string path) { var name = typeof(TReturn).Name; - var language = gameLanguageProvider.Language; - - if (useDefaultLanguage || language == null) - { - language = gameLanguageProvider.Get("en"); - } - try { - var response = await HttpClient.GetAsync(language?.PoeTradeApiBaseUrl + path); + var response = await HttpClient.GetAsync(language.GetTradeApiBaseUrl(game) + path); var content = await response.Content.ReadAsStreamAsync(); var result = await JsonSerializer.DeserializeAsync>(content, Options); if (result != null) @@ -56,7 +48,7 @@ public async Task> Fetch(string path, bool useDefa } catch (Exception) { - logger.LogInformation($"[Trade Client] Could not fetch {name} at {language?.PoeTradeApiBaseUrl + path}."); + logger.LogInformation($"[Trade Client] Could not fetch {name} at {language.GetTradeApiBaseUrl(game) + path}."); throw; } diff --git a/src/Sidekick.Apis.Poe/ITradeSearchService.cs b/src/Sidekick.Apis.Poe/ITradeSearchService.cs index 76b7a8e9a..bde2eabc4 100644 --- a/src/Sidekick.Apis.Poe/ITradeSearchService.cs +++ b/src/Sidekick.Apis.Poe/ITradeSearchService.cs @@ -1,5 +1,6 @@ using Sidekick.Apis.Poe.Trade.Models; using Sidekick.Apis.Poe.Trade.Results; +using Sidekick.Common.Game; using Sidekick.Common.Game.Items; namespace Sidekick.Apis.Poe @@ -8,8 +9,8 @@ public interface ITradeSearchService { Task> Search(Item item, TradeCurrency currency, PropertyFilters? propertyFilters = null, List? modifierFilters = null, List? pseudoFilters = null); - Task> GetResults(string queryId, List ids, List? pseudoFilters = null); + Task> GetResults(GameType game, string queryId, List ids, List? pseudoFilters = null); - Task GetTradeUri(string queryId); + Task GetTradeUri(GameType game, string queryId); } } diff --git a/src/Sidekick.Apis.Poe/Leagues/League.cs b/src/Sidekick.Apis.Poe/Leagues/League.cs index 7d31dd39c..39d643a76 100644 --- a/src/Sidekick.Apis.Poe/Leagues/League.cs +++ b/src/Sidekick.Apis.Poe/Leagues/League.cs @@ -1,26 +1,27 @@ -namespace Sidekick.Apis.Poe.Leagues +using Sidekick.Common.Game; + +namespace Sidekick.Apis.Poe.Leagues; + +/// +/// A Path of Exile league +/// +public class League( + GameType game, + string id, + string text) { /// - /// A Path of Exile league + /// The game this league belongs to /// - public class League - { - public League( - string id, - string text) - { - Id = id; - Text = text; - } + public GameType Game { get; set; } = game; - /// - /// The identifier of the league - /// - public string Id { get; set; } + /// + /// The identifier of the league + /// + public string Id { get; set; } = id; - /// - /// The label of the league - /// - public string Text { get; set; } - } + /// + /// The label of the league + /// + public string Text { get; set; } = text; } diff --git a/src/Sidekick.Apis.Poe/Leagues/LeagueProvider.cs b/src/Sidekick.Apis.Poe/Leagues/LeagueProvider.cs index c67a421da..b92699b03 100644 --- a/src/Sidekick.Apis.Poe/Leagues/LeagueProvider.cs +++ b/src/Sidekick.Apis.Poe/Leagues/LeagueProvider.cs @@ -1,48 +1,74 @@ using Sidekick.Apis.Poe.Clients; using Sidekick.Common.Cache; +using Sidekick.Common.Game; +using Sidekick.Common.Game.Languages; -namespace Sidekick.Apis.Poe.Leagues +namespace Sidekick.Apis.Poe.Leagues; + +public class LeagueProvider( + ICacheProvider cacheProvider, + IPoeTradeClient poeTradeClient, + IGameLanguageProvider gameLanguageProvider) : ILeagueProvider { - public class LeagueProvider : ILeagueProvider + public async Task> GetList(bool fromCache) { - private readonly ICacheProvider cacheProvider; - private readonly IPoeTradeClient poeTradeClient; - - public LeagueProvider( - ICacheProvider cacheProvider, - IPoeTradeClient poeTradeClient) + if (fromCache) { - this.cacheProvider = cacheProvider; - this.poeTradeClient = poeTradeClient; + return await cacheProvider.GetOrSet("Leagues", FetchAll); } - public async Task> GetList(bool fromCache) + var result = await FetchAll(); + await cacheProvider.Set("Leagues", result); + return result; + } + + private async Task> FetchAll() + { + await gameLanguageProvider.Initialize(); + + List>> tasks = + [ + Fetch(GameType.PathOfExile2), + Fetch(GameType.PathOfExile), + ]; + + var allLeagues = await Task.WhenAll(tasks); + return allLeagues + .SelectMany(leagues => leagues) + .ToList(); + } + + private async Task> Fetch(GameType game) + { + var response = await poeTradeClient.Fetch(game, gameLanguageProvider.InvariantLanguage, "data/leagues"); + var leagues = new List(); + foreach (var apiLeague in response.Result) { - if (fromCache) + if (apiLeague.Id == null || apiLeague.Text == null) { - return await cacheProvider.GetOrSet("Leagues", GetList); + continue; } - var result = await GetList(); - await cacheProvider.Set("Leagues", result); - return result; - } + if (apiLeague.Realm != LeagueRealm.PC && apiLeague.Realm != LeagueRealm.Poe2) + { + continue; + } - private async Task> GetList() - { - var response = await poeTradeClient.Fetch("data/leagues"); - var leagues = new List(); - foreach (var apiLeague in response.Result) + var text = game switch { - if (apiLeague.Id == null || apiLeague.Text == null || apiLeague.Realm != LeagueRealm.PC) - { - continue; - } + GameType.PathOfExile2 => $"PoE2 - {apiLeague.Text}", + _ => $"PoE1 - {apiLeague.Text}", + }; - leagues.Add(new(apiLeague.Id, apiLeague.Text)); - } + var id = game switch + { + GameType.PathOfExile2 => $"poe2.{apiLeague.Id}", + _ => $"poe1.{apiLeague.Id}", + }; - return leagues; + leagues.Add(new(game, id, text)); } + + return leagues; } } diff --git a/src/Sidekick.Apis.Poe/Leagues/LeagueRealm.cs b/src/Sidekick.Apis.Poe/Leagues/LeagueRealm.cs index 98b896d1d..36d0ac869 100644 --- a/src/Sidekick.Apis.Poe/Leagues/LeagueRealm.cs +++ b/src/Sidekick.Apis.Poe/Leagues/LeagueRealm.cs @@ -7,6 +7,7 @@ public enum LeagueRealm { PC, Xbox, - Sony + Sony, + Poe2, } } diff --git a/src/Sidekick.Apis.Poe/Metadatas/IInvariantMetadataProvider.cs b/src/Sidekick.Apis.Poe/Metadata/IInvariantMetadataProvider.cs similarity index 85% rename from src/Sidekick.Apis.Poe/Metadatas/IInvariantMetadataProvider.cs rename to src/Sidekick.Apis.Poe/Metadata/IInvariantMetadataProvider.cs index 8c5b8b0d8..ae7261663 100644 --- a/src/Sidekick.Apis.Poe/Metadatas/IInvariantMetadataProvider.cs +++ b/src/Sidekick.Apis.Poe/Metadata/IInvariantMetadataProvider.cs @@ -1,7 +1,7 @@ using Sidekick.Common.Game.Items; using Sidekick.Common.Initialization; -namespace Sidekick.Apis.Poe.Metadatas +namespace Sidekick.Apis.Poe.Metadata { public interface IInvariantMetadataProvider : IInitializableService { diff --git a/src/Sidekick.Apis.Poe/Metadatas/IMetadataProvider.cs b/src/Sidekick.Apis.Poe/Metadata/IMetadataProvider.cs similarity index 70% rename from src/Sidekick.Apis.Poe/Metadatas/IMetadataProvider.cs rename to src/Sidekick.Apis.Poe/Metadata/IMetadataProvider.cs index f5dcf3258..ceff74fcf 100644 --- a/src/Sidekick.Apis.Poe/Metadatas/IMetadataProvider.cs +++ b/src/Sidekick.Apis.Poe/Metadata/IMetadataProvider.cs @@ -1,13 +1,16 @@ using System.Text.RegularExpressions; +using Sidekick.Apis.Poe.Metadata.Models; using Sidekick.Common.Game.Items; using Sidekick.Common.Initialization; -namespace Sidekick.Apis.Poe.Metadatas +namespace Sidekick.Apis.Poe.Metadata { public interface IMetadataProvider : IInitializableService { Dictionary> NameAndTypeDictionary { get; } List<(Regex Regex, ItemMetadata Item)> NameAndTypeRegex { get; } + + List ApiItemCategories { get; set; } } } diff --git a/src/Sidekick.Apis.Poe/Metadata/InvariantMetadataProvider.cs b/src/Sidekick.Apis.Poe/Metadata/InvariantMetadataProvider.cs new file mode 100644 index 000000000..6c399eac5 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Metadata/InvariantMetadataProvider.cs @@ -0,0 +1,97 @@ +using Microsoft.Extensions.Logging; +using Sidekick.Apis.Poe.Clients; +using Sidekick.Apis.Poe.Metadata.Models; +using Sidekick.Common.Cache; +using Sidekick.Common.Enums; +using Sidekick.Common.Extensions; +using Sidekick.Common.Game; +using Sidekick.Common.Game.Items; +using Sidekick.Common.Game.Languages; +using Sidekick.Common.Initialization; +using Sidekick.Common.Settings; + +namespace Sidekick.Apis.Poe.Metadata +{ + public class InvariantMetadataProvider + ( + ICacheProvider cacheProvider, + IPoeTradeClient poeTradeClient, + ILogger logger, + IGameLanguageProvider gameLanguageProvider, + ISettingsService settingsService + ) : IInvariantMetadataProvider + { + public Dictionary IdDictionary { get; } = new(); + + /// + public int Priority => 100; + + /// + public async Task Initialize() + { + IdDictionary.Clear(); + + var leagueId = await settingsService.GetString(SettingKeys.LeagueId); + var game = leagueId.GetGameFromLeagueId(); + var cacheKey = $"{game.GetValueAttribute()}_InvariantMetadata"; + + var result = await cacheProvider.GetOrSet(cacheKey, () => poeTradeClient.Fetch(GameType.PathOfExile, gameLanguageProvider.InvariantLanguage, "data/items")); + + var categories = game switch + { + GameType.PathOfExile2 => MetadataConstants.Poe2Categories, + _ => MetadataConstants.Poe1Categories, + }; + foreach (var category in categories) + { + FillPattern(game, result.Result, category.Key, category.Value.Category); + } + } + + private void FillPattern(GameType game, List categories, string id, Category category) + { + var categoryItems = categories.SingleOrDefault(x => x.Id == id); + + if (categoryItems == null) + { + logger.LogWarning($"[MetadataProvider] The category '{id}' could not be found in the metadata from the API."); + return; + } + + var items = categoryItems.Entries; + + for (var i = 0; i < items.Count; i++) + { + var item = items[i]; + + IdDictionary.Add($"{category}.{i}", + new ItemMetadata() + { + Id = $"{category}.{i}", + Name = item.Name, + Type = item.Text ?? item.Type, + ApiType = item.Type, + Rarity = GetRarityForCategory(category, item), + Category = category, + Game = game, + }); + } + } + + private static Rarity GetRarityForCategory(Category category, ApiItem item) + { + if (item.Flags?.Unique ?? false) + { + return Rarity.Unique; + } + + return category switch + { + Category.DivinationCard => Rarity.DivinationCard, + Category.Gem => Rarity.Gem, + Category.Currency => Rarity.Currency, + _ => Rarity.Unknown + }; + } + } +} diff --git a/src/Sidekick.Apis.Poe/Metadata/MetadataConstants.cs b/src/Sidekick.Apis.Poe/Metadata/MetadataConstants.cs new file mode 100644 index 000000000..7bc36e6a9 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Metadata/MetadataConstants.cs @@ -0,0 +1,41 @@ +using Sidekick.Common.Game.Items; + +namespace Sidekick.Apis.Poe.Metadata; + +public class MetadataConstants +{ + public static Dictionary Poe1Categories = new() + { + { "accessory", (Category.Accessory, true) }, + { "armour", (Category.Armour, true) }, + { "card", (Category.DivinationCard, false) }, + { "currency", (Category.Currency, false) }, + { "flask", (Category.Flask, true) }, + { "gem", (Category.Gem, false) }, + { "jewel", (Category.Jewel, true) }, + { "map", (Category.Map, true) }, + { "weapon", (Category.Weapon, true) }, + { "leaguestone", (Category.Leaguestone, false) }, + { "monster", (Category.ItemisedMonster, true) }, + { "heistequipment", (Category.HeistEquipment, true) }, + { "heistmission", (Category.Contract, true) }, + { "logbook", (Category.Logbook, true) }, + { "sanctum", (Category.Sanctum, true) }, + { "memoryline", (Category.MemoryLine, true) }, + { "tincture", (Category.Tincture, true) }, + { "corpse", (Category.Corpse, true) }, + }; + + public static Dictionary Poe2Categories = new() + { + { "accessory", (Category.Accessory, true) }, + { "armour", (Category.Armour, true) }, + { "currency", (Category.Currency, false) }, + { "flask", (Category.Flask, true) }, + { "gem", (Category.Gem, false) }, + { "jewel", (Category.Jewel, true) }, + { "map", (Category.Map, true) }, + { "weapon", (Category.Weapon, true) }, + { "sanctum", (Category.Sanctum, true) }, + }; +} diff --git a/src/Sidekick.Apis.Poe/Metadata/MetadataProvider.cs b/src/Sidekick.Apis.Poe/Metadata/MetadataProvider.cs new file mode 100644 index 000000000..86275d84f --- /dev/null +++ b/src/Sidekick.Apis.Poe/Metadata/MetadataProvider.cs @@ -0,0 +1,130 @@ +using System.Text.RegularExpressions; +using Microsoft.Extensions.Logging; +using Sidekick.Apis.Poe.Clients; +using Sidekick.Apis.Poe.Metadata.Models; +using Sidekick.Common.Cache; +using Sidekick.Common.Enums; +using Sidekick.Common.Extensions; +using Sidekick.Common.Game; +using Sidekick.Common.Game.Items; +using Sidekick.Common.Game.Languages; +using Sidekick.Common.Settings; + +namespace Sidekick.Apis.Poe.Metadata +{ + public class MetadataProvider + ( + ICacheProvider cacheProvider, + IPoeTradeClient poeTradeClient, + ILogger logger, + IGameLanguageProvider gameLanguageProvider, + ISettingsService settingsService + ) : IMetadataProvider + { + public Dictionary> NameAndTypeDictionary { get; } = new(); + + public List<(Regex Regex, ItemMetadata Item)> NameAndTypeRegex { get; } = new(); + + public List ApiItemCategories { get; set; } = new(); + + /// + public int Priority => 100; + + /// + public async Task Initialize() + { + NameAndTypeDictionary.Clear(); + NameAndTypeRegex.Clear(); + + var leagueId = await settingsService.GetString(SettingKeys.LeagueId); + var game = leagueId.GetGameFromLeagueId(); + var cacheKey = $"{game.GetValueAttribute()}_Metadata"; + + var result = await cacheProvider.GetOrSet(cacheKey, () => poeTradeClient.Fetch(game, gameLanguageProvider.Language, "data/items")); + + var categories = game switch + { + GameType.PathOfExile2 => MetadataConstants.Poe2Categories, + _ => MetadataConstants.Poe1Categories, + }; + foreach (var category in categories) + { + FillPattern(game, result.Result, category.Key, category.Value.Category, category.Value.UseRegex); + } + + // The /filters API endpoint provides all the item categories. + var filters = await poeTradeClient.Fetch(game, gameLanguageProvider.Language, "data/filters"); + ApiItemCategories = filters.Result.First(x => x.Id == "type_filters").Filters + .First(x => x.Id == "category").Option!.Options; + } + + private void FillPattern(GameType game, List categories, string id, Category category, bool useRegex = false) + { + var categoryItems = categories.SingleOrDefault(x => x.Id == id); + + if (categoryItems == null) + { + logger.LogWarning($"[MetadataProvider] The category '{id}' could not be found in the metadata from the API."); + return; + } + + var items = categoryItems.Entries; + + for (var i = 0; i < items.Count; i++) + { + var item = items[i]; + var header = new ItemMetadata() + { + Id = $"{category}.{i}", + Name = item.Name, + Type = item.Text ?? item.Type, + ApiType = item.Type, + ApiTypeDiscriminator = item.Discriminator, + Rarity = GetRarityForCategory(category, item), + Category = category, + Game = game, + }; + + var key = header.Name ?? header.Type ?? header.ApiType; + if (key == null) + { + continue; + } + + FillDictionary(header, key); + + if (header.Rarity != Rarity.Unique && useRegex) + { + NameAndTypeRegex.Add((new Regex(Regex.Escape(key)), header)); + } + } + } + + private void FillDictionary(ItemMetadata metadata, string key) + { + if (!NameAndTypeDictionary.TryGetValue(key, out var dictionaryEntry)) + { + dictionaryEntry = new List(); + NameAndTypeDictionary.Add(key, dictionaryEntry); + } + + dictionaryEntry.Add(metadata); + } + + private static Rarity GetRarityForCategory(Category category, ApiItem item) + { + if (item.Flags?.Unique ?? false) + { + return Rarity.Unique; + } + + return category switch + { + Category.DivinationCard => Rarity.DivinationCard, + Category.Gem => Rarity.Gem, + Category.Currency => Rarity.Currency, + _ => Rarity.Unknown + }; + } + } +} diff --git a/src/Sidekick.Apis.Poe/Metadatas/Models/ApiCategory.cs b/src/Sidekick.Apis.Poe/Metadata/Models/ApiCategory.cs similarity index 88% rename from src/Sidekick.Apis.Poe/Metadatas/Models/ApiCategory.cs rename to src/Sidekick.Apis.Poe/Metadata/Models/ApiCategory.cs index c780dad6e..3eb333550 100644 --- a/src/Sidekick.Apis.Poe/Metadatas/Models/ApiCategory.cs +++ b/src/Sidekick.Apis.Poe/Metadata/Models/ApiCategory.cs @@ -1,4 +1,4 @@ -namespace Sidekick.Apis.Poe.Metadatas.Models +namespace Sidekick.Apis.Poe.Metadata.Models { /// /// Items from /trade/data/items. diff --git a/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilter.cs b/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilter.cs new file mode 100644 index 000000000..487869ac2 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilter.cs @@ -0,0 +1,8 @@ +namespace Sidekick.Apis.Poe.Metadata.Models; + +public class ApiFilter +{ + public string? Id { get; set; } + public string? Title { get; set; } + public List Filters { get; set; } = new(); +} diff --git a/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilterFilters.cs b/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilterFilters.cs new file mode 100644 index 000000000..799e3f9af --- /dev/null +++ b/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilterFilters.cs @@ -0,0 +1,8 @@ +namespace Sidekick.Apis.Poe.Metadata.Models; + +public class ApiFilterFilters +{ + public string? Id { get; set; } + public string? Text { get; set; } + public ApiFilterOptions? Option { get; set; } +} diff --git a/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilterOption.cs b/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilterOption.cs new file mode 100644 index 000000000..4cfb81c09 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilterOption.cs @@ -0,0 +1,7 @@ +namespace Sidekick.Apis.Poe.Metadata.Models; + +public class ApiFilterOption +{ + public string? Id { get; set; } + public string? Text { get; set; } +} diff --git a/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilterOptions.cs b/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilterOptions.cs new file mode 100644 index 000000000..a18707caf --- /dev/null +++ b/src/Sidekick.Apis.Poe/Metadata/Models/ApiFilterOptions.cs @@ -0,0 +1,6 @@ +namespace Sidekick.Apis.Poe.Metadata.Models; + +public class ApiFilterOptions +{ + public List Options { get; set; } = new(); +} diff --git a/src/Sidekick.Apis.Poe/Metadatas/Models/ApiItem.cs b/src/Sidekick.Apis.Poe/Metadata/Models/ApiItem.cs similarity index 88% rename from src/Sidekick.Apis.Poe/Metadatas/Models/ApiItem.cs rename to src/Sidekick.Apis.Poe/Metadata/Models/ApiItem.cs index b32f231c2..9e999f95c 100644 --- a/src/Sidekick.Apis.Poe/Metadatas/Models/ApiItem.cs +++ b/src/Sidekick.Apis.Poe/Metadata/Models/ApiItem.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Sidekick.Apis.Poe.Metadatas.Models +namespace Sidekick.Apis.Poe.Metadata.Models { public class ApiItem { diff --git a/src/Sidekick.Apis.Poe/Metadatas/Models/ApiItemFlags.cs b/src/Sidekick.Apis.Poe/Metadata/Models/ApiItemFlags.cs similarity index 65% rename from src/Sidekick.Apis.Poe/Metadatas/Models/ApiItemFlags.cs rename to src/Sidekick.Apis.Poe/Metadata/Models/ApiItemFlags.cs index 154fb18b0..465c36be6 100644 --- a/src/Sidekick.Apis.Poe/Metadatas/Models/ApiItemFlags.cs +++ b/src/Sidekick.Apis.Poe/Metadata/Models/ApiItemFlags.cs @@ -1,4 +1,4 @@ -namespace Sidekick.Apis.Poe.Metadatas.Models +namespace Sidekick.Apis.Poe.Metadata.Models { public class ApiItemFlags { diff --git a/src/Sidekick.Apis.Poe/Metadatas/InvariantMetadataProvider.cs b/src/Sidekick.Apis.Poe/Metadatas/InvariantMetadataProvider.cs deleted file mode 100644 index 864476f07..000000000 --- a/src/Sidekick.Apis.Poe/Metadatas/InvariantMetadataProvider.cs +++ /dev/null @@ -1,104 +0,0 @@ -using Microsoft.Extensions.Logging; -using Sidekick.Apis.Poe.Clients; -using Sidekick.Apis.Poe.Metadatas.Models; -using Sidekick.Common.Cache; -using Sidekick.Common.Game.Items; -using Sidekick.Common.Initialization; - -namespace Sidekick.Apis.Poe.Metadatas -{ - public class InvariantMetadataProvider : IInvariantMetadataProvider - { - private readonly ICacheProvider cacheProvider; - private readonly IPoeTradeClient poeTradeClient; - private readonly ILogger logger; - - public InvariantMetadataProvider( - ICacheProvider cacheProvider, - IPoeTradeClient poeTradeClient, - ILogger logger) - { - this.cacheProvider = cacheProvider; - this.poeTradeClient = poeTradeClient; - this.logger = logger; - } - - public Dictionary IdDictionary { get; } = new(); - - /// - public InitializationPriority Priority => InitializationPriority.Medium; - - /// - public async Task Initialize() - { - IdDictionary.Clear(); - - var result = await cacheProvider.GetOrSet( - "InvariantMetadata", - () => poeTradeClient.Fetch("data/items", useDefaultLanguage: true)); - - FillPattern(result.Result, "accessory", Category.Accessory, useRegex: true); - FillPattern(result.Result, "armour", Category.Armour, useRegex: true); - FillPattern(result.Result, "card", Category.DivinationCard); - FillPattern(result.Result, "currency", Category.Currency); - FillPattern(result.Result, "flask", Category.Flask, useRegex: true); - FillPattern(result.Result, "gem", Category.Gem); - FillPattern(result.Result, "jewel", Category.Jewel, useRegex: true); - FillPattern(result.Result, "map", Category.Map, useRegex: true); - FillPattern(result.Result, "weapon", Category.Weapon, useRegex: true); - FillPattern(result.Result, "leaguestone", Category.Leaguestone); - FillPattern(result.Result, "monster", Category.ItemisedMonster, useRegex: true); - FillPattern(result.Result, "heistequipment", Category.HeistEquipment, useRegex: true); - FillPattern(result.Result, "heistmission", Category.Contract, useRegex: true); - FillPattern(result.Result, "logbook", Category.Logbook, useRegex: true); - FillPattern(result.Result, "sanctum", Category.Sanctum, useRegex: true); - FillPattern(result.Result, "memoryline", Category.MemoryLine, useRegex: true); - FillPattern(result.Result, "tincture", Category.Tincture, useRegex: true); - FillPattern(result.Result, "corpse", Category.Corpse, useRegex: true); - } - - private void FillPattern(List categories, string id, Category category, bool useRegex = false) - { - var categoryItems = categories.SingleOrDefault(x => x.Id == id); - - if (categoryItems == null) - { - logger.LogWarning($"[MetadataProvider] The category '{id}' could not be found in the metadata from the API."); - return; - } - - var items = categoryItems.Entries; - - for (var i = 0; i < items.Count; i++) - { - var item = items[i]; - - IdDictionary.Add($"{category}.{i}", new ItemMetadata() - { - Id = $"{category}.{i}", - Name = item.Name, - Type = item.Text ?? item.Type, - ApiType = item.Type, - Rarity = GetRarityForCategory(category, item), - Category = category, - }); - } - } - - private static Rarity GetRarityForCategory(Category category, ApiItem item) - { - if (item.Flags?.Unique ?? false) - { - return Rarity.Unique; - } - - return category switch - { - Category.DivinationCard => Rarity.DivinationCard, - Category.Gem => Rarity.Gem, - Category.Currency => Rarity.Currency, - _ => Rarity.Unknown - }; - } - } -} diff --git a/src/Sidekick.Apis.Poe/Metadatas/MetadataProvider.cs b/src/Sidekick.Apis.Poe/Metadatas/MetadataProvider.cs deleted file mode 100644 index 859392159..000000000 --- a/src/Sidekick.Apis.Poe/Metadatas/MetadataProvider.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Text.RegularExpressions; -using Microsoft.Extensions.Logging; -using Sidekick.Apis.Poe.Clients; -using Sidekick.Apis.Poe.Metadatas.Models; -using Sidekick.Common.Cache; -using Sidekick.Common.Game.Items; -using Sidekick.Common.Initialization; - -namespace Sidekick.Apis.Poe.Metadatas -{ - public class MetadataProvider( - ICacheProvider cacheProvider, - IPoeTradeClient poeTradeClient, - ILogger logger) : IMetadataProvider - { - public Dictionary> NameAndTypeDictionary { get; } = new(); - - public List<(Regex Regex, ItemMetadata Item)> NameAndTypeRegex { get; } = new(); - - /// - public InitializationPriority Priority => InitializationPriority.Medium; - - /// - public async Task Initialize() - { - NameAndTypeDictionary.Clear(); - NameAndTypeRegex.Clear(); - - var result = await cacheProvider.GetOrSet("Metadata", () => poeTradeClient.Fetch("data/items")); - - FillPattern(result.Result, "accessory", Category.Accessory, useRegex: true); - FillPattern(result.Result, "armour", Category.Armour, useRegex: true); - FillPattern(result.Result, "card", Category.DivinationCard); - FillPattern(result.Result, "currency", Category.Currency); - FillPattern(result.Result, "flask", Category.Flask, useRegex: true); - FillPattern(result.Result, "gem", Category.Gem); - FillPattern(result.Result, "jewel", Category.Jewel, useRegex: true); - FillPattern(result.Result, "map", Category.Map, useRegex: true); - FillPattern(result.Result, "weapon", Category.Weapon, useRegex: true); - FillPattern(result.Result, "leaguestone", Category.Leaguestone); - FillPattern(result.Result, "monster", Category.ItemisedMonster, useRegex: true); - FillPattern(result.Result, "heistequipment", Category.HeistEquipment, useRegex: true); - FillPattern(result.Result, "heistmission", Category.Contract, useRegex: true); - FillPattern(result.Result, "logbook", Category.Logbook, useRegex: true); - FillPattern(result.Result, "sanctum", Category.Sanctum, useRegex: true); - FillPattern(result.Result, "memoryline", Category.MemoryLine, useRegex: true); - FillPattern(result.Result, "tincture", Category.Tincture, useRegex: true); - FillPattern(result.Result, "corpse", Category.Corpse, useRegex: true); - - //FillPattern(result.Result, "azmeri", Category.Affliction, useRegex: true); - //FillPattern(result.Result, "necropolis", Category.EmbersOfTheAllflame, useRegex: true); - } - - private void FillPattern( - List categories, - string id, - Category category, - bool useRegex = false) - { - var categoryItems = categories.SingleOrDefault(x => x.Id == id); - - if (categoryItems == null) - { - logger.LogWarning($"[MetadataProvider] The category '{id}' could not be found in the metadata from the API."); - return; - } - - var items = categoryItems.Entries; - - for (var i = 0; i < items.Count; i++) - { - var item = items[i]; - - var header = new ItemMetadata() - { - Id = $"{category}.{i}", - Name = item.Name, - Type = item.Text ?? item.Type, - ApiType = item.Type, - ApiTypeDiscriminator = item.Discriminator, - Rarity = GetRarityForCategory(category, item), - Category = category, - }; - - var key = header.Name ?? header.Type ?? header.ApiType; - if (key == null) - { - continue; - } - - // If the item is unique, exclude it from the regex dictionary - if (header.Rarity == Rarity.Unique) - { - FillDictionary(header, key); - continue; - } - - if (useRegex) - { - NameAndTypeRegex.Add((new Regex(Regex.Escape(key)), header)); - } - - FillDictionary(header, key); - } - } - - private void FillDictionary( - ItemMetadata metadata, - string key) - { - if (!NameAndTypeDictionary.TryGetValue(key, out var dictionaryEntry)) - { - dictionaryEntry = new List(); - NameAndTypeDictionary.Add(key, dictionaryEntry); - } - - dictionaryEntry.Add(metadata); - } - - private static Rarity GetRarityForCategory( - Category category, - ApiItem item) - { - if (item.Flags?.Unique ?? false) - { - return Rarity.Unique; - } - - return category switch - { - Category.DivinationCard => Rarity.DivinationCard, - Category.Gem => Rarity.Gem, - Category.Currency => Rarity.Currency, - _ => Rarity.Unknown - }; - } - } -} diff --git a/src/Sidekick.Apis.Poe/Modifiers/InvariantModifierProvider.cs b/src/Sidekick.Apis.Poe/Modifiers/InvariantModifierProvider.cs index 09e0eac30..5bf72c73a 100644 --- a/src/Sidekick.Apis.Poe/Modifiers/InvariantModifierProvider.cs +++ b/src/Sidekick.Apis.Poe/Modifiers/InvariantModifierProvider.cs @@ -1,118 +1,111 @@ using Sidekick.Apis.Poe.Clients; using Sidekick.Apis.Poe.Modifiers.Models; using Sidekick.Common.Cache; -using Sidekick.Common.Initialization; - -namespace Sidekick.Apis.Poe.Modifiers +using Sidekick.Common.Enums; +using Sidekick.Common.Extensions; +using Sidekick.Common.Game.Languages; +using Sidekick.Common.Settings; + +namespace Sidekick.Apis.Poe.Modifiers; + +public class InvariantModifierProvider +( + ICacheProvider cacheProvider, + IPoeTradeClient poeTradeClient, + IGameLanguageProvider gameLanguageProvider, + ISettingsService settingsService +) : IInvariantModifierProvider { - public class InvariantModifierProvider : IInvariantModifierProvider - { - private readonly ICacheProvider cacheProvider; - private readonly IPoeTradeClient poeTradeClient; - - public InvariantModifierProvider( - ICacheProvider cacheProvider, - IPoeTradeClient poeTradeClient) - { - this.cacheProvider = cacheProvider; - this.poeTradeClient = poeTradeClient; - } + public List IncursionRoomModifierIds { get; } = new(); - public List IncursionRoomModifierIds { get; } = new(); + public List LogbookFactionModifierIds { get; } = new(); - public List LogbookFactionModifierIds { get; } = new(); + public string ClusterJewelSmallPassiveCountModifierId { get; private set; } = null!; - public string ClusterJewelSmallPassiveCountModifierId { get; private set; } = null!; + public string ClusterJewelSmallPassiveGrantModifierId { get; private set; } = null!; - public string ClusterJewelSmallPassiveGrantModifierId { get; private set; } = null!; + public Dictionary ClusterJewelSmallPassiveGrantOptions { get; private set; } = null!; - public Dictionary ClusterJewelSmallPassiveGrantOptions { get; private set; } = null!; + /// + public int Priority => 100; - /// - public InitializationPriority Priority => InitializationPriority.Medium; + /// + public async Task Initialize() + { + var result = await GetList(); + InitializeIncursionRooms(result); + InitializeLogbookFactions(result); + InitializeClusterJewel(result); + } - /// - public async Task Initialize() + private void InitializeIncursionRooms(List apiCategories) + { + IncursionRoomModifierIds.Clear(); + foreach (var apiCategory in apiCategories) { - var result = await GetList(); - InitializeIncursionRooms(result); - InitializeLogbookFactions(result); - InitializeClusterJewel(result); + if (!IsCategory(apiCategory, "pseudo")) { continue; } + + IncursionRoomModifierIds.AddRange(apiCategory.Entries.Where(x => x.Text.StartsWith("Has Room: ")).Select(x => x.Id).ToList()); } + } - private void InitializeIncursionRooms(List apiCategories) + private void InitializeLogbookFactions(List apiCategories) + { + LogbookFactionModifierIds.Clear(); + foreach (var apiCategory in apiCategories) { - IncursionRoomModifierIds.Clear(); - foreach (var apiCategory in apiCategories) - { - var first = apiCategory.Entries.FirstOrDefault(); - if (first?.Id?.Split('.')[0] != "pseudo") - { - return; - } + if (!IsCategory(apiCategory, "pseudo")) { continue; } - IncursionRoomModifierIds.AddRange( - apiCategory.Entries - .Where(x => x.Text?.StartsWith("Has Room: ") ?? false) - .Select(x => x.Id ?? string.Empty) - .ToList()); - } + LogbookFactionModifierIds.AddRange(apiCategory.Entries.Where(x => x.Text.StartsWith("Has Logbook Faction: ")).Select(x => x.Id).ToList()); } + } - private void InitializeLogbookFactions(List apiCategories) + private void InitializeClusterJewel(List apiCategories) + { + foreach (var apiCategory in apiCategories) { - LogbookFactionModifierIds.Clear(); - foreach (var apiCategory in apiCategories) + if (!IsCategory(apiCategory, "enchant")) { continue; } + + foreach (var apiModifier in apiCategory.Entries) { - var first = apiCategory.Entries.FirstOrDefault(); - if (first?.Id?.Split('.')[0] != "pseudo") + if (apiModifier.Text == "Adds # Passive Skills") { - return; + ClusterJewelSmallPassiveCountModifierId = apiModifier.Id; } - LogbookFactionModifierIds.AddRange( - apiCategory.Entries - .Where(x => x.Text?.StartsWith("Has Logbook Faction: ") ?? false) - .Select(x => x.Id ?? string.Empty) - .ToList()); - } - } - - private void InitializeClusterJewel(List apiCategories) - { - foreach (var apiCategory in apiCategories) - { - var first = apiCategory.Entries.FirstOrDefault(); - if (first?.Id?.Split('.')[0] != "enchant") + if (apiModifier.Text != "Added Small Passive Skills grant: #") { continue; } - foreach (var apiModifier in apiCategory.Entries) + ClusterJewelSmallPassiveGrantModifierId = apiModifier.Id; + if (apiModifier.Option == null) { - if (apiModifier.Text == "Adds # Passive Skills") - { - ClusterJewelSmallPassiveCountModifierId = apiModifier.Id; - } - - if (apiModifier.Text == "Added Small Passive Skills grant: #") - { - ClusterJewelSmallPassiveGrantModifierId = apiModifier.Id; - if (apiModifier.Option == null) - { - return; - } - - ClusterJewelSmallPassiveGrantOptions = apiModifier.Option.Options.ToDictionary(x => x.Id, x => x.Text!); - } + return; } + + ClusterJewelSmallPassiveGrantOptions = apiModifier.Option.Options.ToDictionary(x => x.Id, x => x.Text!); } } + } - public Task> GetList() => cacheProvider.GetOrSet("InvariantModifiers", async () => - { - var result = await poeTradeClient.Fetch("data/stats", useDefaultLanguage: true); - return result.Result; - }); + private bool IsCategory(ApiCategory apiCategory, string? key) + { + var first = apiCategory.Entries.FirstOrDefault(); + return first?.Id.Split('.')[0] == key; + } + + public async Task> GetList() + { + var leagueId = await settingsService.GetString(SettingKeys.LeagueId); + var game = leagueId.GetGameFromLeagueId(); + var cacheKey = $"{game.GetValueAttribute()}_InvariantModifiers"; + + return await cacheProvider.GetOrSet(cacheKey, + async () => + { + var result = await poeTradeClient.Fetch(game, gameLanguageProvider.InvariantLanguage, "data/stats"); + return result.Result; + }); } } diff --git a/src/Sidekick.Apis.Poe/Modifiers/Models/ApiModifier.cs b/src/Sidekick.Apis.Poe/Modifiers/Models/ApiModifier.cs index 67c8a0175..917321b1b 100644 --- a/src/Sidekick.Apis.Poe/Modifiers/Models/ApiModifier.cs +++ b/src/Sidekick.Apis.Poe/Modifiers/Models/ApiModifier.cs @@ -3,7 +3,7 @@ namespace Sidekick.Apis.Poe.Modifiers.Models public record ApiModifier { public required string Id { get; init; } - public required string Text { get; init; } + public required string Text { get; set; } public required string Type { get; init; } public ApiModifierOptions? Option { get; set; } diff --git a/src/Sidekick.Apis.Poe/Modifiers/ModifierProvider.cs b/src/Sidekick.Apis.Poe/Modifiers/ModifierProvider.cs index 00be5fac1..12b62c368 100644 --- a/src/Sidekick.Apis.Poe/Modifiers/ModifierProvider.cs +++ b/src/Sidekick.Apis.Poe/Modifiers/ModifierProvider.cs @@ -2,288 +2,295 @@ using Sidekick.Apis.Poe.Clients; using Sidekick.Apis.Poe.Modifiers.Models; using Sidekick.Common.Cache; +using Sidekick.Common.Enums; +using Sidekick.Common.Extensions; using Sidekick.Common.Game.Items; -using Sidekick.Common.Initialization; +using Sidekick.Common.Game.Languages; +using Sidekick.Common.Settings; -namespace Sidekick.Apis.Poe.Modifiers +namespace Sidekick.Apis.Poe.Modifiers; + +public class ModifierProvider +( + ICacheProvider cacheProvider, + IPoeTradeClient poeTradeClient, + IInvariantModifierProvider invariantModifierProvider, + IGameLanguageProvider gameLanguageProvider, + ISettingsService settingsService +) : IModifierProvider { - public class ModifierProvider : IModifierProvider + private readonly Regex parseHashPattern = new("\\#"); + + /// + /// A regular expression used to extract and process text within square brackets, + /// optionally separated by pipes, for parsing modifier patterns within game data. + /// + /// + /// [ItemRarity|Rarity of Items] => Rarity of Items + /// [Spell] => Spell + /// + private static readonly Regex parseSquareBracketPattern = new("\\[.*?\\|?([^\\|\\[\\]]*)\\]"); + + public static string RemoveSquareBrackets(string text) { - private readonly ICacheProvider cacheProvider; - private readonly IPoeTradeClient poeTradeClient; - private readonly IInvariantModifierProvider invariantModifierProvider; - private readonly Regex ParseHashPattern = new("\\#"); - private readonly Regex NewLinePattern = new("(?:\\\\)*[\\r\\n]+"); - private readonly Regex HashPattern = new("\\\\#"); - private readonly Regex ParenthesesPattern = new("((?:\\\\\\ )*\\\\\\([^\\(\\)]*\\\\\\))"); - private readonly Regex CleanFuzzyPattern = new("[-+0-9%#]"); - private readonly Regex TrimPattern = new(@"\s+"); - - public ModifierProvider( - ICacheProvider cacheProvider, - IPoeTradeClient poeTradeClient, - IInvariantModifierProvider invariantModifierProvider) + if (string.IsNullOrEmpty(text)) { - this.cacheProvider = cacheProvider; - this.poeTradeClient = poeTradeClient; - this.invariantModifierProvider = invariantModifierProvider; + return text; } - public Dictionary> Patterns { get; } = new(); - public Dictionary> FuzzyDictionary { get; } = new(); + return parseSquareBracketPattern.Replace(text, "$1"); + } + + private readonly Regex newLinePattern = new("(?:\\\\)*[\\r\\n]+"); + private readonly Regex hashPattern = new("\\\\#"); + private readonly Regex parenthesesPattern = new("((?:\\\\\\ )*\\\\\\([^\\(\\)]*\\\\\\))"); + private readonly Regex cleanFuzzyPattern = new("[-+0-9%#]"); + private readonly Regex trimPattern = new(@"\s+"); + + public Dictionary> Patterns { get; } = new(); + + public Dictionary> FuzzyDictionary { get; private set; } = new(); - /// - public InitializationPriority Priority => InitializationPriority.Low; + /// + public int Priority => 200; - /// - public async Task Initialize() + /// + public async Task Initialize() + { + var leagueId = await settingsService.GetString(SettingKeys.LeagueId); + var game = leagueId.GetGameFromLeagueId(); + var cacheKey = $"{game.GetValueAttribute()}_Modifiers"; + var apiCategories = await cacheProvider.GetOrSet(cacheKey, () => poeTradeClient.Fetch(game, gameLanguageProvider.Language, "data/stats")); + + foreach (var apiCategory in apiCategories.Result) { - var result = await cacheProvider.GetOrSet( - "Modifiers", - () => poeTradeClient.Fetch("data/stats")); - var categories = result.Result; + var modifierCategory = GetModifierCategory(apiCategory.Entries[0].Id); + var patterns = ComputeCategoryPatterns(apiCategory, modifierCategory); - foreach (var category in categories) + if (!Patterns.TryAdd(modifierCategory, patterns)) { - if (!category.Entries.Any()) - { - continue; - } + Patterns[modifierCategory].AddRange(patterns); + } + } - var modifierCategory = GetModifierCategory(category.Entries[0].Id); - if (modifierCategory == ModifierCategory.Undefined) - { - continue; - } + FuzzyDictionary = ComputeFuzzyDictionary(Patterns.SelectMany(x => x.Value)); + + // Prepare special pseudo patterns + var pseudoPatterns = Patterns.GetValueOrDefault(ModifierCategory.Pseudo) ?? []; + + var incursionPatterns = pseudoPatterns.Where(x => invariantModifierProvider.IncursionRoomModifierIds.Contains(x.Id)).ToList(); + ComputeSpecialPseudoPattern(pseudoPatterns, incursionPatterns); - var patterns = new List(); - foreach (var entry in category.Entries) + var logbookPatterns = pseudoPatterns.Where(x => invariantModifierProvider.LogbookFactionModifierIds.Contains(x.Id)).ToList(); + ComputeSpecialPseudoPattern(pseudoPatterns, logbookPatterns); + } + + private List ComputeCategoryPatterns(ApiCategory apiCategory, ModifierCategory modifierCategory) + { + if (apiCategory.Entries.Count == 0 || modifierCategory == ModifierCategory.Undefined) + { + return []; + } + + var patterns = new List(); + foreach (var entry in apiCategory.Entries) + { + entry.Text = RemoveSquareBrackets(entry.Text); + + var options = entry.Option?.Options ?? []; + if (options.Count > 0) + { + foreach (var option in options) { - if (entry.Text == null || entry.Id == null) + var optionText = option.Text; + if (optionText == null) { continue; } - var isOption = entry.Option?.Options?.Any() ?? false; - if (isOption) + optionText = RemoveSquareBrackets(optionText); + patterns.Add(new ModifierPattern(modifierCategory, entry.Id, options.Any(), text: ComputeOptionText(entry.Text, optionText), fuzzyText: ComputeFuzzyText(modifierCategory, entry.Text, optionText), pattern: ComputePattern(entry.Text, modifierCategory, optionText)) { - for (var i = 0; i < entry.Option?.Options.Count; i++) - { - var optionText = entry.Option.Options[i].Text; - if (optionText == null) - { - continue; - } - - patterns.Add(new ModifierPattern( - category: modifierCategory, - id: entry.Id, - isOption: entry.Option?.Options?.Any() ?? false, - text: ComputeOptionText(entry.Text, optionText), - fuzzyText: ComputeFuzzyText(modifierCategory, entry.Text, optionText), - pattern: ComputePattern(entry.Text, modifierCategory, optionText)) - { - Value = entry.Option?.Options[i].Id, - }); - } - } - else - { - patterns.Add(new ModifierPattern( - category: modifierCategory, - id: entry.Id, - isOption: entry.Option?.Options?.Any() ?? false, - text: entry.Text, - fuzzyText: ComputeFuzzyText(modifierCategory, entry.Text), - pattern: ComputePattern(entry.Text, modifierCategory))); - } + Value = option.Id, + }); } - - if (Patterns.ContainsKey(modifierCategory)) - { - Patterns[modifierCategory].AddRange(patterns); - } - else - { - Patterns.Add(modifierCategory, patterns); - } - - BuildFuzzyDictionary(patterns); } + else + { + patterns.Add(new ModifierPattern(modifierCategory, entry.Id, options.Any(), entry.Text, fuzzyText: ComputeFuzzyText(modifierCategory, entry.Text), pattern: ComputePattern(entry.Text, modifierCategory))); + } + } - // Prepare special pseudo patterns - var incursionPatterns = Patterns[ModifierCategory.Pseudo] - .Where(x => invariantModifierProvider.IncursionRoomModifierIds.Contains(x.Id)) - .ToList(); - ComputeSpecialPseudoPattern(incursionPatterns); + return patterns; + } - var logbookPatterns = Patterns[ModifierCategory.Pseudo] - .Where(x => invariantModifierProvider.LogbookFactionModifierIds.Contains(x.Id)) - .ToList(); - ComputeSpecialPseudoPattern(logbookPatterns); - } + /// + public ModifierCategory GetModifierCategory(string? apiId) => apiId?.Split('.').First() switch + { + "crafted" => ModifierCategory.Crafted, + "delve" => ModifierCategory.Delve, + "enchant" => ModifierCategory.Enchant, + "explicit" => ModifierCategory.Explicit, + "fractured" => ModifierCategory.Fractured, + "implicit" => ModifierCategory.Implicit, + "monster" => ModifierCategory.Monster, + "pseudo" => ModifierCategory.Pseudo, + "scourge" => ModifierCategory.Scourge, + "veiled" => ModifierCategory.Veiled, + "crucible" => ModifierCategory.Crucible, + "rune" => ModifierCategory.Rune, + "sanctum" => ModifierCategory.Sanctum, + _ => ModifierCategory.Undefined, + }; - /// - public ModifierCategory GetModifierCategory(string? apiId) + private void ComputeSpecialPseudoPattern(List pseudoPatterns, List patterns) + { + var specialPatterns = new List(); + foreach (var group in patterns.GroupBy(x => x.Id)) { - return apiId?.Split('.').First() switch + var pattern = group.OrderBy(x => x.Value).First(); + specialPatterns.Add(new ModifierPattern(category: pattern.Category, id: pattern.Id, isOption: pattern.IsOption, text: pattern.Text, fuzzyText: ComputeFuzzyText(ModifierCategory.Pseudo, pattern.Text), pattern: ComputePattern(pattern.Text.Split(':', 2).Last().Trim(), ModifierCategory.Pseudo)) { - "crafted" => ModifierCategory.Crafted, - "delve" => ModifierCategory.Delve, - "enchant" => ModifierCategory.Enchant, - "explicit" => ModifierCategory.Explicit, - "fractured" => ModifierCategory.Fractured, - "implicit" => ModifierCategory.Implicit, - "monster" => ModifierCategory.Monster, - "pseudo" => ModifierCategory.Pseudo, - "scourge" => ModifierCategory.Scourge, - "veiled" => ModifierCategory.Veiled, - "crucible" => ModifierCategory.Crucible, - _ => ModifierCategory.Undefined, - }; + OptionText = pattern.OptionText, + Value = pattern.Value, + }); } - private void ComputeSpecialPseudoPattern(List patterns) + var ids = specialPatterns.Select(x => x.Id).Distinct().ToList(); + pseudoPatterns.RemoveAll(x => ids.Contains(x.Id)); + pseudoPatterns.AddRange(specialPatterns); + } + + private Regex ComputePattern(string text, ModifierCategory? category = null, string? optionText = null) + { + text = RemoveSquareBrackets(text); + if (optionText != null) optionText = RemoveSquareBrackets(optionText); + + // The notes in parentheses are never translated by the game. + // We should be fine hardcoding them this way. + var suffix = category switch { - var specialPatterns = new List(); - foreach (var group in patterns.GroupBy(x => x.Id)) - { - var pattern = group.OrderBy(x => x.Value).First(); - specialPatterns.Add(new ModifierPattern( - category: pattern.Category, - id: pattern.Id, - isOption: pattern.IsOption, - text: pattern.Text, - fuzzyText: ComputeFuzzyText(ModifierCategory.Pseudo, pattern.Text), - pattern: ComputePattern(pattern.Text.Split(':', 2).Last().Trim(), ModifierCategory.Pseudo)) - { - OptionText = pattern.OptionText, - Value = pattern.Value, - }); - } + ModifierCategory.Implicit => "(?:\\ \\(implicit\\))", + ModifierCategory.Enchant => "(?:\\ \\(enchant\\))", + ModifierCategory.Crafted => "(?:\\ \\(crafted\\))?", + ModifierCategory.Veiled => "(?:\\ \\(veiled\\))", + ModifierCategory.Fractured => "(?:\\ \\(fractured\\))?", + ModifierCategory.Scourge => "(?:\\ \\(scourge\\))", + ModifierCategory.Crucible => "(?:\\ \\(crucible\\))", + ModifierCategory.Rune => "(?:\\ \\(rune\\))", + ModifierCategory.Explicit => "(?:\\ \\((?:crafted|fractured)\\))?", + _ => "", + }; - var ids = specialPatterns.Select(x => x.Id).Distinct().ToList(); - Patterns[ModifierCategory.Pseudo].RemoveAll(x => ids.Contains(x.Id)); - Patterns[ModifierCategory.Pseudo].AddRange(specialPatterns); - } + var patternValue = Regex.Escape(text); + patternValue = parenthesesPattern.Replace(patternValue, "(?:$1)?"); + patternValue = newLinePattern.Replace(patternValue, "\\n"); - private Regex ComputePattern(string text, ModifierCategory? category = null, string? optionText = null) + if (string.IsNullOrEmpty(optionText)) { - // The notes in parentheses are never translated by the game. - // We should be fine hardcoding them this way. - var suffix = category switch - { - ModifierCategory.Implicit => "(?:\\ \\(implicit\\))", - ModifierCategory.Enchant => "(?:\\ \\(enchant\\))", - ModifierCategory.Crafted => "(?:\\ \\(crafted\\))?", - ModifierCategory.Veiled => "(?:\\ \\(veiled\\))", - ModifierCategory.Fractured => "(?:\\ \\(fractured\\))?", - ModifierCategory.Scourge => "(?:\\ \\(scourge\\))", - ModifierCategory.Crucible => "(?:\\ \\(crucible\\))", - ModifierCategory.Explicit => "(?:\\ \\((?:crafted|fractured)\\))?", - _ => "", - }; - - var patternValue = Regex.Escape(text); - patternValue = ParenthesesPattern.Replace(patternValue, "(?:$1)?"); - patternValue = NewLinePattern.Replace(patternValue, "\\n"); - - if (string.IsNullOrEmpty(optionText)) - { - patternValue = HashPattern.Replace(patternValue, "[-+0-9,.]+") + suffix; - } - else + patternValue = hashPattern.Replace(patternValue, "[-+0-9,.]+") + suffix; + } + else + { + var optionLines = new List(); + foreach (var optionLine in newLinePattern.Split(optionText)) { - var optionLines = new List(); - foreach (var optionLine in NewLinePattern.Split(optionText)) - { - optionLines.Add(HashPattern.Replace(patternValue, Regex.Escape(optionLine)) + suffix); - } - patternValue = string.Join('\n', optionLines); + optionLines.Add(hashPattern.Replace(patternValue, Regex.Escape(optionLine)) + suffix); } - return new Regex($"^{patternValue}$", RegexOptions.None); + patternValue = string.Join('\n', optionLines); } - private string ComputeFuzzyText(ModifierCategory category, string text, string? optionText = null) - { - var fuzzyValue = text; + return new Regex($"^{patternValue}$", RegexOptions.None); + } - if (!string.IsNullOrEmpty(optionText)) + private string ComputeFuzzyText(ModifierCategory category, string text, string? optionText = null) + { + text = RemoveSquareBrackets(text); + if (optionText != null) optionText = RemoveSquareBrackets(optionText); + + var fuzzyValue = text; + + if (!string.IsNullOrEmpty(optionText)) + { + foreach (var optionLine in newLinePattern.Split(optionText)) { - foreach (var optionLine in NewLinePattern.Split(optionText)) + if (parseHashPattern.IsMatch(fuzzyValue)) { - if (ParseHashPattern.IsMatch(fuzzyValue)) - { - fuzzyValue = ParseHashPattern.Replace(fuzzyValue, optionLine); - } - else - { - fuzzyValue += optionLine; - } + fuzzyValue = parseHashPattern.Replace(fuzzyValue, optionLine); + } + else + { + fuzzyValue += optionLine; } } - - // Add the suffix - // The notes in parentheses are never translated by the game. - // We should be fine hardcoding them this way. - fuzzyValue += category switch - { - ModifierCategory.Implicit => " (implicit)", - ModifierCategory.Enchant => " (enchant)", - ModifierCategory.Crafted => " (crafted)", - ModifierCategory.Veiled => " (veiled)", - ModifierCategory.Fractured => " (fractured)", - ModifierCategory.Scourge => " (scourge)", - ModifierCategory.Pseudo => " (pseudo)", - ModifierCategory.Crucible => " (crucible)", - _ => "", - }; - - return CleanFuzzyText(fuzzyValue); } - private string CleanFuzzyText(string text) + // Add the suffix + // The notes in parentheses are never translated by the game. + // We should be fine hardcoding them this way. + fuzzyValue += category switch { - text = CleanFuzzyPattern.Replace(text, string.Empty); - return TrimPattern.Replace(text, " ").Trim(); - } + ModifierCategory.Implicit => " (implicit)", + ModifierCategory.Enchant => " (enchant)", + ModifierCategory.Crafted => " (crafted)", + ModifierCategory.Veiled => " (veiled)", + ModifierCategory.Fractured => " (fractured)", + ModifierCategory.Scourge => " (scourge)", + ModifierCategory.Pseudo => " (pseudo)", + ModifierCategory.Crucible => " (crucible)", + ModifierCategory.Rune => " (rune)", + _ => "", + }; + + return CleanFuzzyText(fuzzyValue); + } - private void BuildFuzzyDictionary(List patterns) + private string CleanFuzzyText(string text) + { + text = cleanFuzzyPattern.Replace(text, string.Empty); + return trimPattern.Replace(text, " ").Trim(); + } + + private Dictionary> ComputeFuzzyDictionary(IEnumerable patterns) + { + var result = new Dictionary>(); + + foreach (var pattern in patterns) { - foreach (var pattern in patterns) + if (!result.ContainsKey(pattern.FuzzyText)) { - if (!FuzzyDictionary.ContainsKey(pattern.FuzzyText)) - { - FuzzyDictionary.Add(pattern.FuzzyText, new()); - } - - FuzzyDictionary[pattern.FuzzyText].Add(pattern); + result.Add(pattern.FuzzyText, new()); } + + result[pattern.FuzzyText].Add(pattern); } - private string ComputeOptionText(string text, string optionText) + return result; + } + + private string ComputeOptionText(string text, string optionText) + { + var optionLines = new List(); + foreach (var optionLine in newLinePattern.Split(optionText)) { - var optionLines = new List(); - foreach (var optionLine in NewLinePattern.Split(optionText)) - { - optionLines.Add(ParseHashPattern.Replace(text, optionLine)); - } - return string.Join('\n', optionLines).Trim('\r', '\n'); + optionLines.Add(parseHashPattern.Replace(text, optionLine)); } - public bool IsMatch(string id, string text) + return string.Join('\n', optionLines).Trim('\r', '\n'); + } + + public bool IsMatch(string id, string text) + { + foreach (var patternGroup in Patterns) { - foreach (var patternGroup in Patterns) + var pattern = patternGroup.Value.FirstOrDefault(x => x.Id == id); + if (pattern != null && pattern.Pattern.IsMatch(text)) { - var pattern = patternGroup.Value.FirstOrDefault(x => x.Id == id); - if (pattern != null && pattern.Pattern != null && pattern.Pattern.IsMatch(text)) - { - return true; - } + return true; } - - return false; } + + return false; } } diff --git a/src/Sidekick.Apis.Poe/Parser/AdditionalInformation/ClusterJewelParser.cs b/src/Sidekick.Apis.Poe/Parser/AdditionalInformation/ClusterJewelParser.cs index 9f5b7cf1f..ee217f9cc 100644 --- a/src/Sidekick.Apis.Poe/Parser/AdditionalInformation/ClusterJewelParser.cs +++ b/src/Sidekick.Apis.Poe/Parser/AdditionalInformation/ClusterJewelParser.cs @@ -4,20 +4,13 @@ namespace Sidekick.Apis.Poe.Parser.AdditionalInformation { - public class ClusterJewelParser + public class ClusterJewelParser(IInvariantModifierProvider invariantModifierProvider) { - private readonly IInvariantModifierProvider invariantModifierProvider; - - public ClusterJewelParser(IInvariantModifierProvider invariantModifierProvider) - { - this.invariantModifierProvider = invariantModifierProvider; - } - public bool TryParse(Item item, out ClusterJewelInformation? information) { information = null; - if (item.Header.Class != Class.Jewel || item.Metadata.Rarity == Rarity.Unique) + if (item.Metadata.Category != Category.Jewel || item.Metadata.Rarity == Rarity.Unique) { return false; } diff --git a/src/Sidekick.Apis.Poe/Parser/ItemParser.cs b/src/Sidekick.Apis.Poe/Parser/ItemParser.cs index 8fda00ef2..bf0052480 100644 --- a/src/Sidekick.Apis.Poe/Parser/ItemParser.cs +++ b/src/Sidekick.Apis.Poe/Parser/ItemParser.cs @@ -1,43 +1,35 @@ using System.Globalization; using System.Text.RegularExpressions; +using FuzzySharp; +using FuzzySharp.SimilarityRatio; +using FuzzySharp.SimilarityRatio.Scorer.StrategySensitive; using Microsoft.Extensions.Logging; -using Sidekick.Apis.Poe.Metadatas; +using Sidekick.Apis.Poe.Metadata; +using Sidekick.Apis.Poe.Metadata.Models; using Sidekick.Apis.Poe.Parser.AdditionalInformation; using Sidekick.Apis.Poe.Parser.Patterns; using Sidekick.Apis.Poe.Pseudo; using Sidekick.Common.Exceptions; +using Sidekick.Common.Game; using Sidekick.Common.Game.Items; +using Sidekick.Common.Game.Languages; namespace Sidekick.Apis.Poe.Parser { - public class ItemParser : IItemParser + public class ItemParser + ( + ILogger logger, + IItemMetadataParser itemMetadataProvider, + IModifierParser modifierParser, + IPseudoModifierProvider pseudoModifierProvider, + IParserPatterns patterns, + ClusterJewelParser clusterJewelParser, + IInvariantMetadataProvider invariantMetadataProvider, + SocketParser socketParser, + IMetadataProvider metadataProvider, + IGameLanguageProvider gameLanguageProvider + ) : IItemParser { - private readonly ILogger logger; - private readonly IItemMetadataParser itemMetadataProvider; - private readonly IModifierParser modifierParser; - private readonly IPseudoModifierProvider pseudoModifierProvider; - private readonly IParserPatterns patterns; - private readonly ClusterJewelParser clusterJewelParser; - private readonly IInvariantMetadataProvider invariantMetadataProvider; - - public ItemParser( - ILogger logger, - IItemMetadataParser itemMetadataProvider, - IModifierParser modifierParser, - IPseudoModifierProvider pseudoModifierProvider, - IParserPatterns patterns, - ClusterJewelParser clusterJewelParser, - IInvariantMetadataProvider invariantMetadataProvider) - { - this.logger = logger; - this.itemMetadataProvider = itemMetadataProvider; - this.modifierParser = modifierParser; - this.pseudoModifierProvider = pseudoModifierProvider; - this.patterns = patterns; - this.clusterJewelParser = clusterJewelParser; - this.invariantMetadataProvider = invariantMetadataProvider; - } - public Task ParseItemAsync(string itemText) { return Task.Run(() => ParseItem(itemText)); @@ -60,10 +52,11 @@ public Item ParseItem(string itemText) } // Strip the Superior affix from the name - parsingItem.Blocks.First().Lines.ForEach(x => - { - x.Text = itemMetadataProvider.GetLineWithoutSuperiorAffix(x.Text); - }); + parsingItem.Blocks.First() + .Lines.ForEach(x => + { + x.Text = itemMetadataProvider.GetLineWithoutSuperiorAffix(x.Text); + }); parsingItem.Metadata = metadata; ItemMetadata? invariant = null; @@ -72,24 +65,24 @@ public Item ParseItem(string itemText) invariant = invariantMetadata; } - // Order of parsing is important + // Order of parsing is important ParseRequirements(parsingItem); + var header = ParseHeader(parsingItem); var properties = ParseProperties(parsingItem); var influences = ParseInfluences(parsingItem); - var sockets = ParseSockets(parsingItem); + var sockets = socketParser.Parse(parsingItem); var modifierLines = ParseModifiers(parsingItem); - var pseudoModifiers = ParsePseudoModifiers(modifierLines); - var item = new Item( - metadata: metadata, - invariant: invariant, - header: header, - properties: properties, - influences: influences, - sockets: sockets, - modifierLines: modifierLines, - pseudoModifiers: pseudoModifiers, - text: parsingItem.Text); + var pseudoModifiers = parsingItem.Metadata.Game == GameType.PathOfExile ? ParsePseudoModifiers(modifierLines) : []; + var item = new Item(metadata: metadata, + invariant: invariant, + header: header, + properties: properties, + influences: influences, + sockets: sockets, + modifierLines: modifierLines, + pseudoModifiers: pseudoModifiers, + text: parsingItem.Text); if (clusterJewelParser.TryParse(item, out var clusterInformation)) { @@ -125,20 +118,25 @@ public Header ParseHeader(string itemText) private Header ParseHeader(ParsingItem parsingItem) { - var itemClass = Class.Undefined; - foreach (var pattern in patterns.Classes) + var firstLine = parsingItem.Blocks[0].Lines[0].Text; + string? apiItemCategoryId; + + if (firstLine.StartsWith(gameLanguageProvider.Language.Classes.Prefix)) { - if (pattern.Value.IsMatch(parsingItem.Blocks[0].Lines[0].Text)) - { - itemClass = pattern.Key; - } + var classLine = firstLine.Replace(gameLanguageProvider.Language.Classes.Prefix, "").Trim(); + var categoryToMatch = new ApiFilterOption { Text = classLine }; + apiItemCategoryId = Process.ExtractOne(categoryToMatch, metadataProvider.ApiItemCategories, x => x.Text, ScorerCache.Get())?.Value?.Id ?? null; + } + else + { + apiItemCategoryId = null; } return new Header() { Name = parsingItem.Blocks[0].Lines.ElementAtOrDefault(2)?.Text, Type = parsingItem.Blocks[0].Lines.ElementAtOrDefault(3)?.Text, - Class = itemClass, + ItemCategory = apiItemCategoryId }; } @@ -146,11 +144,13 @@ private void ParseRequirements(ParsingItem parsingItem) { foreach (var block in parsingItem.Blocks.Where(x => !x.Parsed)) { - if (TryParseValue(patterns.Requirements, block, out var match)) + if (!block.TryParseRegex(patterns.Requirements, out _)) { - block.Parsed = true; - return; + continue; } + + block.Parsed = true; + return; } } @@ -180,7 +180,6 @@ private Properties ParseWeaponProperties(ParsingItem parsingItem) ItemLevel = GetInt(patterns.ItemLevel, parsingItem), Identified = !GetBool(patterns.Unidentified, parsingItem), Corrupted = GetBool(patterns.Corrupted, parsingItem), - Quality = GetInt(patterns.Quality, propertyBlock), AttacksPerSecond = GetDouble(patterns.AttacksPerSecond, propertyBlock), CriticalStrikeChance = GetDouble(patterns.CriticalStrikeChance, propertyBlock) @@ -202,7 +201,6 @@ private Properties ParseArmourProperties(ParsingItem parsingItem) ItemLevel = GetInt(patterns.ItemLevel, parsingItem), Identified = !GetBool(patterns.Unidentified, parsingItem), Corrupted = GetBool(patterns.Corrupted, parsingItem), - Quality = GetInt(patterns.Quality, propertyBlock), Armor = GetInt(patterns.Armor, propertyBlock), EnergyShield = GetInt(patterns.EnergyShield, propertyBlock), @@ -232,7 +230,6 @@ private Properties ParseMapProperties(ParsingItem parsingItem) Corrupted = GetBool(patterns.Corrupted, parsingItem), Blighted = patterns.Blighted.IsMatch(parsingItem.Blocks[0].Lines[^1].Text), BlightRavaged = patterns.BlightRavaged.IsMatch(parsingItem.Blocks[0].Lines[^1].Text), - ItemQuantity = GetInt(patterns.ItemQuantity, propertyBlock), ItemRarity = GetInt(patterns.ItemRarity, propertyBlock), MonsterPackSize = GetInt(patterns.MonsterPackSize, propertyBlock), @@ -248,10 +245,8 @@ private Properties ParseGemProperties(ParsingItem parsingItem) return new Properties() { Corrupted = GetBool(patterns.Corrupted, parsingItem), - GemLevel = GetInt(patterns.Level, propertyBlock), Quality = GetInt(patterns.Quality, propertyBlock), - AlternateQuality = GetBool(patterns.AlternateQuality, parsingItem), Anomalous = GetBool(patterns.Anomalous, parsingItem), Divergent = GetBool(patterns.Divergent, parsingItem), @@ -296,45 +291,6 @@ private Properties ParseLogbookProperties(ParsingItem parsingItem) }; } - private List ParseSockets(ParsingItem parsingItem) - { - if (TryParseValue(patterns.Socket, parsingItem, out var match)) - { - var groups = match.Groups.Values - .Where(x => !string.IsNullOrEmpty(x.Value)) - .Skip(1) - .Select((x, Index) => new - { - x.Value, - Index, - }) - .ToList(); - - var result = new List(); - - foreach (var group in groups) - { - var groupValue = group.Value.Replace("-", "").Trim(); - while (groupValue.Length > 0) - { - switch (groupValue[0]) - { - case 'B': result.Add(new Socket() { Group = group.Index, Colour = SocketColour.Blue }); break; - case 'G': result.Add(new Socket() { Group = group.Index, Colour = SocketColour.Green }); break; - case 'R': result.Add(new Socket() { Group = group.Index, Colour = SocketColour.Red }); break; - case 'W': result.Add(new Socket() { Group = group.Index, Colour = SocketColour.White }); break; - case 'A': result.Add(new Socket() { Group = group.Index, Colour = SocketColour.Abyss }); break; - } - groupValue = groupValue[1..]; - } - } - - return result; - } - - return new List(); - } - private Influences ParseInfluences(ParsingItem parsingItem) { return parsingItem.Metadata?.Category switch @@ -375,12 +331,12 @@ private List ParsePseudoModifiers(List modifierLin private static bool GetBool(Regex pattern, ParsingItem parsingItem) { - return TryParseValue(pattern, parsingItem, out var _); + return parsingItem.TryParseRegex(pattern, out _); } private static int GetInt(Regex pattern, ParsingItem parsingItem) { - if (TryParseValue(pattern, parsingItem, out var match) && int.TryParse(match.Groups[1].Value, out var result)) + if (parsingItem.TryParseRegex(pattern, out var match) && int.TryParse(match.Groups[1].Value, out var result)) { return result; } @@ -390,7 +346,7 @@ private static int GetInt(Regex pattern, ParsingItem parsingItem) private static int GetInt(Regex pattern, ParsingBlock parsingBlock) { - if (TryParseValue(pattern, parsingBlock, out var match) && int.TryParse(match.Groups[1].Value, out var result)) + if (parsingBlock.TryParseRegex(pattern, out var match) && int.TryParse(match.Groups[1].Value, out var result)) { return result; } @@ -400,7 +356,7 @@ private static int GetInt(Regex pattern, ParsingBlock parsingBlock) private static double GetDouble(Regex pattern, ParsingBlock parsingBlock) { - if (TryParseValue(pattern, parsingBlock, out var match) && double.TryParse(match.Groups[1].Value.Replace(",", "."), NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (parsingBlock.TryParseRegex(pattern, out var match) && double.TryParse(match.Groups[1].Value.Replace(",", "."), NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -410,56 +366,24 @@ private static double GetDouble(Regex pattern, ParsingBlock parsingBlock) private static double GetDps(Regex pattern, ParsingBlock parsingBlock, double attacksPerSecond) { - if (TryParseValue(pattern, parsingBlock, out var match)) + if (!parsingBlock.TryParseRegex(pattern, out var match)) { - var matches = new Regex("(\\d+-\\d+)").Matches(match.Value); - var dps = matches - .Select(x => x.Value.Split("-")) - .Sum(split => - { - if (double.TryParse(split[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var minValue) - && double.TryParse(split[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var maxValue)) - { - return (minValue + maxValue) / 2d; - } - - return 0d; - }); - - return Math.Round(dps * attacksPerSecond, 2); + return default; } - return default; - } - - private static bool TryParseValue(Regex pattern, ParsingItem parsingItem, out Match match) - { - foreach (var block in parsingItem.Blocks) - { - if (TryParseValue(pattern, block, out match)) + var matches = new Regex("(\\d+-\\d+)").Matches(match.Value); + var dps = matches.Select(x => x.Value.Split("-")) + .Sum(split => { - return true; - } - } - - match = null!; - return false; - } + if (double.TryParse(split[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var minValue) && double.TryParse(split[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var maxValue)) + { + return (minValue + maxValue) / 2d; + } - private static bool TryParseValue(Regex pattern, ParsingBlock block, out Match match) - { - foreach (var line in block.Lines) - { - match = pattern.Match(line.Text); - if (match.Success) - { - line.Parsed = true; - return true; - } - } + return 0d; + }); - match = null!; - return false; + return Math.Round(dps * attacksPerSecond, 2); } private static bool TrySetAdditionalInformation(Item item, object? additionalInformation) diff --git a/src/Sidekick.Apis.Poe/Parser/MetadataParser.cs b/src/Sidekick.Apis.Poe/Parser/MetadataParser.cs index f6e32e9e0..3e2d0286a 100644 --- a/src/Sidekick.Apis.Poe/Parser/MetadataParser.cs +++ b/src/Sidekick.Apis.Poe/Parser/MetadataParser.cs @@ -1,9 +1,8 @@ using System.Text.RegularExpressions; -using Sidekick.Apis.Poe.Metadatas; +using Sidekick.Apis.Poe.Metadata; using Sidekick.Apis.Poe.Parser.Patterns; using Sidekick.Common.Game.Items; using Sidekick.Common.Game.Languages; -using Sidekick.Common.Initialization; namespace Sidekick.Apis.Poe.Parser { @@ -17,7 +16,7 @@ public class MetadataParser( private Regex SuperiorAffix { get; set; } = null!; /// - public InitializationPriority Priority => InitializationPriority.Medium; + public int Priority => 200; private string GetLineWithoutAffixes(string line) => Affixes .Replace(line, string.Empty) @@ -30,11 +29,6 @@ public string GetLineWithoutSuperiorAffix(string line) => SuperiorAffix /// public Task Initialize() { - if (gameLanguageProvider.Language == null) - { - throw new Exception("[Item Metadata] Could not find a valid language."); - } - var getRegexLine = (string input) => { if (input.StartsWith('/')) @@ -48,7 +42,6 @@ public Task Initialize() }; Affixes = new Regex("(?:" + getRegexLine(gameLanguageProvider.Language.AffixSuperior) + "|" + getRegexLine(gameLanguageProvider.Language.AffixBlighted) + "|" + getRegexLine(gameLanguageProvider.Language.AffixBlightRavaged) + "|" + getRegexLine(gameLanguageProvider.Language.AffixAnomalous) + "|" + getRegexLine(gameLanguageProvider.Language.AffixDivergent) + "|" + getRegexLine(gameLanguageProvider.Language.AffixPhantasmal) + ")"); - SuperiorAffix = new Regex("(?:" + getRegexLine(gameLanguageProvider.Language.AffixSuperior) + ")"); return Task.CompletedTask; diff --git a/src/Sidekick.Apis.Poe/Parser/ModifierParser.cs b/src/Sidekick.Apis.Poe/Parser/ModifierParser.cs index 14b71a4e0..f1118efd0 100644 --- a/src/Sidekick.Apis.Poe/Parser/ModifierParser.cs +++ b/src/Sidekick.Apis.Poe/Parser/ModifierParser.cs @@ -8,19 +8,12 @@ namespace Sidekick.Apis.Poe.Parser { - public class ModifierParser : IModifierParser + public class ModifierParser(IModifierProvider modifierProvider) : IModifierParser { - private readonly IModifierProvider modifierProvider; private readonly Regex CleanFuzzyPattern = new("[-+0-9%#]"); private readonly Regex TrimPattern = new(@"\s+"); private readonly Regex CleanOriginalTextPattern = new(" \\((?:implicit|enchant|crafted|veiled|fractured|scourge|crucible)\\)$"); - public ModifierParser( - IModifierProvider modifierProvider) - { - this.modifierProvider = modifierProvider; - } - private string CleanFuzzyText(string text) { text = CleanFuzzyPattern.Replace(text, string.Empty); diff --git a/src/Sidekick.Apis.Poe/Parser/ParsingBlock.cs b/src/Sidekick.Apis.Poe/Parser/ParsingBlock.cs index 8bd827153..4eb1afa84 100644 --- a/src/Sidekick.Apis.Poe/Parser/ParsingBlock.cs +++ b/src/Sidekick.Apis.Poe/Parser/ParsingBlock.cs @@ -7,7 +7,7 @@ namespace Sidekick.Apis.Poe.Parser /// public class ParsingBlock { - private static readonly Regex NEWLINEPATTERN = new("[\\r\\n]+"); + private static readonly Regex newLinePattern = new("[\\r\\n]+"); /// /// Represents a single item section seperated by dashes when copying an item in-game. @@ -17,7 +17,7 @@ public ParsingBlock(string text) { Text = text; - Lines = NEWLINEPATTERN + Lines = newLinePattern .Split(Text) .Where(x => !string.IsNullOrEmpty(x)) .Select((x) => new ParsingLine(x)) @@ -34,7 +34,7 @@ public ParsingBlock(string text) /// public bool Parsed { - get => !Lines.Any(x => !x.Parsed); + get => Lines.All(x => x.Parsed); set { foreach (var line in Lines) @@ -59,6 +59,24 @@ public bool Parsed /// public string Text { get; } + public bool TryParseRegex(Regex pattern, out Match match) + { + foreach (var line in Lines) + { + match = pattern.Match(line.Text); + if (!match.Success) + { + continue; + } + + line.Parsed = true; + return true; + } + + match = null!; + return false; + } + public override string ToString() { return Text; diff --git a/src/Sidekick.Apis.Poe/Parser/ParsingItem.cs b/src/Sidekick.Apis.Poe/Parser/ParsingItem.cs index e5abe7f67..6afc2d0db 100644 --- a/src/Sidekick.Apis.Poe/Parser/ParsingItem.cs +++ b/src/Sidekick.Apis.Poe/Parser/ParsingItem.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; using Sidekick.Apis.Poe.Parser.Tokenizers; using Sidekick.Common.Game.Items; @@ -8,7 +9,7 @@ namespace Sidekick.Apis.Poe.Parser /// public class ParsingItem { - private const string SEPARATOR_PATTERN = "--------"; + private const string SeparatorPattern = "--------"; /// /// Stores data about the state of the parsing process for the item @@ -19,7 +20,7 @@ public ParsingItem(string text) Text = new ItemNameTokenizer().CleanString(text); Blocks = text - .Split(SEPARATOR_PATTERN, StringSplitOptions.RemoveEmptyEntries) + .Split(SeparatorPattern, StringSplitOptions.RemoveEmptyEntries) .Select(x => new ParsingBlock(x.Trim('\r', '\n'))) .ToList(); } @@ -36,6 +37,20 @@ public ParsingItem(string text) /// public string Text { get; } + public bool TryParseRegex(Regex pattern, out Match match) + { + foreach (var block in Blocks) + { + if (block.TryParseRegex(pattern, out match)) + { + return true; + } + } + + match = null!; + return false; + } + public override string ToString() { return Text; diff --git a/src/Sidekick.Apis.Poe/Parser/ParsingLine.cs b/src/Sidekick.Apis.Poe/Parser/ParsingLine.cs index 1a57fb36b..156ae8d62 100644 --- a/src/Sidekick.Apis.Poe/Parser/ParsingLine.cs +++ b/src/Sidekick.Apis.Poe/Parser/ParsingLine.cs @@ -17,7 +17,7 @@ public ParsingLine(string text) /// /// Indicates if this line has been successfully parsed /// - public bool Parsed { get; set; } = false; + public bool Parsed { get; set; } /// /// The line of the item description @@ -29,4 +29,4 @@ public override string ToString() return Text; } } -} \ No newline at end of file +} diff --git a/src/Sidekick.Apis.Poe/Parser/Patterns/IParserPatterns.cs b/src/Sidekick.Apis.Poe/Parser/Patterns/IParserPatterns.cs index f739d9d0e..3f52089e4 100644 --- a/src/Sidekick.Apis.Poe/Parser/Patterns/IParserPatterns.cs +++ b/src/Sidekick.Apis.Poe/Parser/Patterns/IParserPatterns.cs @@ -33,13 +33,10 @@ public interface IParserPatterns : IInitializableService Dictionary Rarity { get; } Regex Redeemer { get; } Regex Shaper { get; } - Regex Socket { get; } Regex Unidentified { get; } Regex Warlord { get; } Regex Anomalous { get; } Regex Divergent { get; } Regex Phantasmal { get; } - - Dictionary Classes { get; } } } diff --git a/src/Sidekick.Apis.Poe/Parser/Patterns/ParserPatterns.cs b/src/Sidekick.Apis.Poe/Parser/Patterns/ParserPatterns.cs index 85bc8a55a..74ca3a497 100644 --- a/src/Sidekick.Apis.Poe/Parser/Patterns/ParserPatterns.cs +++ b/src/Sidekick.Apis.Poe/Parser/Patterns/ParserPatterns.cs @@ -1,30 +1,20 @@ using System.Text.RegularExpressions; using Sidekick.Common.Game.Items; using Sidekick.Common.Game.Languages; -using Sidekick.Common.Initialization; namespace Sidekick.Apis.Poe.Parser.Patterns { - public class ParserPatterns : IParserPatterns + public class ParserPatterns(IGameLanguageProvider gameLanguageProvider) : IParserPatterns { - private readonly IGameLanguageProvider gameLanguageProvider; - - public ParserPatterns(IGameLanguageProvider gameLanguageProvider) - { - this.gameLanguageProvider = gameLanguageProvider; - } - /// - public InitializationPriority Priority => InitializationPriority.High; + public int Priority => 100; /// public Task Initialize() { InitHeader(); InitProperties(); - InitSockets(); InitInfluences(); - InitClasses(); return Task.CompletedTask; } @@ -33,11 +23,6 @@ public Task Initialize() private void InitHeader() { - if (gameLanguageProvider.Language == null) - { - throw new Exception("[Parser Patterns] Could not find a valid language."); - } - Rarity = new Dictionary { { Common.Game.Items.Rarity.Normal, gameLanguageProvider.Language.RarityNormal.ToRegexEndOfLine() }, @@ -67,11 +52,6 @@ private void InitHeader() private void InitProperties() { - if (gameLanguageProvider.Language == null) - { - throw new Exception("[Parser Patterns] Could not find a valid language."); - } - Armor = gameLanguageProvider.Language.DescriptionArmour.ToRegexIntCapture(); EnergyShield = gameLanguageProvider.Language.DescriptionEnergyShield.ToRegexIntCapture(); Evasion = gameLanguageProvider.Language.DescriptionEvasion.ToRegexIntCapture(); @@ -124,32 +104,10 @@ private void InitProperties() #endregion Properties (Armour, Evasion, Energy Shield, Quality, Level) - #region Sockets - - private void InitSockets() - { - if (gameLanguageProvider.Language == null) - { - throw new Exception("[Parser Patterns] Could not find a valid language."); - } - - // We need 6 capturing groups as it is possible for a 6 socket unlinked item to exist - Socket = new Regex($"{Regex.Escape(gameLanguageProvider.Language.DescriptionSockets)}.*?([-RGBWA]+)\\ ?([-RGBWA]*)\\ ?([-RGBWA]*)\\ ?([-RGBWA]*)\\ ?([-RGBWA]*)\\ ?([-RGBWA]*)"); - } - - public Regex Socket { get; private set; } = null!; - - #endregion Sockets - #region Influences private void InitInfluences() { - if (gameLanguageProvider.Language == null) - { - throw new Exception("[Parser Patterns] Could not find a valid language."); - } - Crusader = gameLanguageProvider.Language.InfluenceCrusader.ToRegexLine(); Elder = gameLanguageProvider.Language.InfluenceElder.ToRegexLine(); Hunter = gameLanguageProvider.Language.InfluenceHunter.ToRegexLine(); @@ -166,35 +124,5 @@ private void InitInfluences() public Regex Warlord { get; private set; } = null!; #endregion Influences - - #region Classes - - public Dictionary Classes { get; } = new Dictionary(); - - private void InitClasses() - { - if (gameLanguageProvider.Language == null) - { - throw new Exception("[Parser Patterns] Could not find a valid language."); - } - - Classes.Clear(); - - if (gameLanguageProvider.Language.Classes == null) return; - - var type = gameLanguageProvider.Language.Classes.GetType(); - var properties = type.GetProperties().Where(x => x.Name != nameof(ClassLanguage.Prefix)); - var prefix = gameLanguageProvider.Language.Classes.Prefix; - - foreach (var property in properties) - { - var label = property.GetValue(gameLanguageProvider.Language.Classes)?.ToString(); - if (string.IsNullOrEmpty(label)) continue; - - Classes.Add(Enum.Parse(property.Name), new Regex($"^{Regex.Escape(prefix)}:* *{Regex.Escape(label)}$")); - } - } - - #endregion Classes } } diff --git a/src/Sidekick.Apis.Poe/Parser/SocketParser.cs b/src/Sidekick.Apis.Poe/Parser/SocketParser.cs new file mode 100644 index 000000000..ec3e49362 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/SocketParser.cs @@ -0,0 +1,107 @@ +using System.Text.RegularExpressions; +using Sidekick.Common.Game.Items; +using Sidekick.Common.Game.Languages; +using Sidekick.Common.Initialization; + +namespace Sidekick.Apis.Poe.Parser; + +public class SocketParser(IGameLanguageProvider gameLanguageProvider) : IInitializableService +{ + public int Priority => 100; + + public Task Initialize() + { + if (gameLanguageProvider.Language == null) + { + throw new Exception("[Parser Patterns] Could not find a valid language."); + } + + // We need 6 capturing groups as it is possible for a 6 socket unlinked item to exist + Pattern = new Regex($"{Regex.Escape(gameLanguageProvider.Language.DescriptionSockets)}.*?([-RGBWAS]+)\\ ?([-RGBWAS]*)\\ ?([-RGBWAS]*)\\ ?([-RGBWAS]*)\\ ?([-RGBWAS]*)\\ ?([-RGBWAS]*)"); + + return Task.CompletedTask; + } + + private Regex? Pattern { get; set; } + + public List Parse(ParsingItem parsingItem) + { + if (Pattern == null || !parsingItem.TryParseRegex(Pattern, out var match)) + { + return []; + } + + var groups = match.Groups.Values.Where(x => !string.IsNullOrEmpty(x.Value)) + .Skip(1) + .Select((x, index) => new + { + x.Value, + Index = index, + }) + .ToList(); + + var result = new List(); + + foreach (var group in groups) + { + var groupValue = group.Value.Replace("-", "").Trim(); + while (groupValue.Length > 0) + { + switch (groupValue[0]) + { + case 'B': + result.Add(new Socket() + { + Group = group.Index, + Colour = SocketColour.Blue + }); + break; + + case 'G': + result.Add(new Socket() + { + Group = group.Index, + Colour = SocketColour.Green + }); + break; + + case 'R': + result.Add(new Socket() + { + Group = group.Index, + Colour = SocketColour.Red + }); + break; + + case 'W': + result.Add(new Socket() + { + Group = group.Index, + Colour = SocketColour.White + }); + break; + + case 'A': + result.Add(new Socket() + { + Group = group.Index, + Colour = SocketColour.Abyss + }); + break; + + case 'S': + result.Add(new Socket() + { + Group = group.Index, + Colour = SocketColour.Soulcore + }); + break; + } + + groupValue = groupValue[1..]; + } + } + + return result; + } +} diff --git a/src/Sidekick.Apis.Poe/Pseudo/PseudoModifierProvider.cs b/src/Sidekick.Apis.Poe/Pseudo/PseudoModifierProvider.cs index 9b01b589d..79bd7bc5e 100644 --- a/src/Sidekick.Apis.Poe/Pseudo/PseudoModifierProvider.cs +++ b/src/Sidekick.Apis.Poe/Pseudo/PseudoModifierProvider.cs @@ -3,26 +3,17 @@ using Sidekick.Apis.Poe.Modifiers.Models; using Sidekick.Apis.Poe.Pseudo.Models; using Sidekick.Common.Game.Items; -using Sidekick.Common.Initialization; namespace Sidekick.Apis.Poe.Pseudo { - public class PseudoModifierProvider : IPseudoModifierProvider + public class PseudoModifierProvider(IInvariantModifierProvider invariantModifierProvider) : IPseudoModifierProvider { private readonly Regex ParseHashPattern = new("\\#"); - private readonly IInvariantModifierProvider invariantModifierProvider; - - public PseudoModifierProvider( - IInvariantModifierProvider invariantModifierProvider) - { - this.invariantModifierProvider = invariantModifierProvider; - } - private List Definitions { get; } = new(); /// - public InitializationPriority Priority => InitializationPriority.Low; + public int Priority => 200; /// public async Task Initialize() diff --git a/src/Sidekick.Apis.Poe/StartupExtensions.cs b/src/Sidekick.Apis.Poe/StartupExtensions.cs index a5547509e..7f8823f02 100644 --- a/src/Sidekick.Apis.Poe/StartupExtensions.cs +++ b/src/Sidekick.Apis.Poe/StartupExtensions.cs @@ -3,10 +3,11 @@ using Sidekick.Apis.Poe.Authentication; using Sidekick.Apis.Poe.Bulk; using Sidekick.Apis.Poe.Clients; +using Sidekick.Apis.Poe.Clients.Models; using Sidekick.Apis.Poe.Clients.States; using Sidekick.Apis.Poe.Leagues; using Sidekick.Apis.Poe.Localization; -using Sidekick.Apis.Poe.Metadatas; +using Sidekick.Apis.Poe.Metadata; using Sidekick.Apis.Poe.Modifiers; using Sidekick.Apis.Poe.Parser; using Sidekick.Apis.Poe.Parser.AdditionalInformation; @@ -28,9 +29,9 @@ public static IServiceCollection AddSidekickPoeApi(this IServiceCollection servi services.AddSingleton(); services.AddSingleton(); - services.AddHttpClient(ClientNames.TRADECLIENT); + services.AddHttpClient(ClientNames.TradeClient); - services.AddHttpClient(ClientNames.POECLIENT) + services.AddHttpClient(ClientNames.PoeClient) .ConfigurePrimaryHttpMessageHandler((sp) => { var logger = sp.GetRequiredService>(); @@ -45,6 +46,7 @@ public static IServiceCollection AddSidekickPoeApi(this IServiceCollection servi services.AddTransient(); services.AddTransient(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -53,8 +55,8 @@ public static IServiceCollection AddSidekickPoeApi(this IServiceCollection servi services.AddSingleton(); services.AddSingleton(); - services.AddSidekickInitializableService(); services.AddSidekickInitializableService(); + services.AddSidekickInitializableService(); services.AddSidekickInitializableService(); services.AddSidekickInitializableService(); services.AddSidekickInitializableService(); diff --git a/src/Sidekick.Apis.Poe/Stash/StashService.cs b/src/Sidekick.Apis.Poe/Stash/StashService.cs index a5a968ec8..5d6046d16 100644 --- a/src/Sidekick.Apis.Poe/Stash/StashService.cs +++ b/src/Sidekick.Apis.Poe/Stash/StashService.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using Sidekick.Apis.Poe.Clients; using Sidekick.Apis.Poe.Stash.Models; +using Sidekick.Common.Extensions; using Sidekick.Common.Game.Items; using Sidekick.Common.Settings; @@ -17,7 +18,7 @@ public class StashService( try { var leagueId = await settingsService.GetString(SettingKeys.LeagueId); - var response = await client.Fetch($"stash/{leagueId}"); + var response = await client.Fetch($"stash/{leagueId.GetUrlSlugForLeague()}"); if (response == null || leagueId == null) { return null; @@ -68,7 +69,7 @@ private void FillStashTabs( try { var leagueId = await settingsService.GetString(SettingKeys.LeagueId); - var wrapper = await client.Fetch($"stash/{leagueId}/{id}"); + var wrapper = await client.Fetch($"stash/{leagueId.GetUrlSlugForLeague()}/{id}"); if (wrapper == null || leagueId == null) { return null; @@ -111,7 +112,7 @@ private async Task> FetchStashItems(ApiStashTab tab) if (tab.Items == null && tab.Children == null) { var leagueId = await settingsService.GetString(SettingKeys.LeagueId); - var uri = string.IsNullOrEmpty(tab.Parent) ? $"stash/{leagueId}/{tab.Id}" : $"stash/{leagueId}/{tab.Parent}/{tab.Id}"; + var uri = string.IsNullOrEmpty(tab.Parent) ? $"stash/{leagueId.GetUrlSlugForLeague()}/{tab.Id}" : $"stash/{leagueId}/{tab.Parent}/{tab.Id}"; var wrapper = await client.Fetch(uri); if (wrapper?.Stash.Items != null) diff --git a/src/Sidekick.Apis.Poe/Static/ItemStaticDataProvider.cs b/src/Sidekick.Apis.Poe/Static/ItemStaticDataProvider.cs index 05dbb3446..5ba4786ac 100644 --- a/src/Sidekick.Apis.Poe/Static/ItemStaticDataProvider.cs +++ b/src/Sidekick.Apis.Poe/Static/ItemStaticDataProvider.cs @@ -1,6 +1,7 @@ using Sidekick.Apis.Poe.Clients; using Sidekick.Apis.Poe.Static.Models; using Sidekick.Common.Cache; +using Sidekick.Common.Game; using Sidekick.Common.Game.Items; using Sidekick.Common.Game.Languages; using Sidekick.Common.Initialization; @@ -16,14 +17,14 @@ public class ItemStaticDataProvider( private Dictionary Ids { get; set; } = new(); /// - public InitializationPriority Priority => InitializationPriority.Medium; + public int Priority => 100; /// public async Task Initialize() { var result = await cacheProvider.GetOrSet( "ItemStaticDataProvider", - () => poeTradeClient.Fetch("data/static")); + () => poeTradeClient.Fetch(GameType.PathOfExile, gameLanguageProvider.Language, "data/static")); ImageUrls.Clear(); Ids.Clear(); @@ -53,7 +54,7 @@ public async Task Initialize() _ => id }; - if (gameLanguageProvider.Language == null || string.IsNullOrEmpty(id) || !ImageUrls.TryGetValue(id, out var result)) + if (string.IsNullOrEmpty(id) || !ImageUrls.TryGetValue(id, out var result)) { return null; } diff --git a/src/Sidekick.Apis.Poe/Trade/Models/PropertyFilters.cs b/src/Sidekick.Apis.Poe/Trade/Models/PropertyFilters.cs index 3d55ef4cd..ac767ba1a 100644 --- a/src/Sidekick.Apis.Poe/Trade/Models/PropertyFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Models/PropertyFilters.cs @@ -1,10 +1,9 @@ -using Sidekick.Common.Game.Items; namespace Sidekick.Apis.Poe.Trade.Models { public class PropertyFilters { - public Class? Class { get; set; } = null; + public string? ItemCategory { get; set; } = null; public List Weapon { get; set; } = new(); public List Armour { get; set; } = new(); public List Map { get; set; } = new(); diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/SearchFilters.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/SearchFilters.cs index 2d926d4b8..c14d91c57 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/SearchFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/SearchFilters.cs @@ -4,28 +4,28 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters { internal class SearchFilters { + [JsonPropertyName("type_filters")] + public TypeFilterGroup TypeFilters { get; set; } = new(); + + [JsonPropertyName("trade_filters")] + public TradeFilterGroup TradeFilters { get; set; } = new(); + [JsonPropertyName("misc_filters")] - public MiscFilterGroup MiscFilters { get; set; } = new(); + public MiscFilterGroup? MiscFilters { get; set; } [JsonPropertyName("weapon_filters")] - public WeaponFilterGroup WeaponFilters { get; set; } = new(); + public WeaponFilterGroup? WeaponFilters { get; set; } [JsonPropertyName("armour_filters")] - public ArmorFilterGroup ArmourFilters { get; set; } = new(); + public ArmorFilterGroup? ArmourFilters { get; set; } [JsonPropertyName("socket_filters")] - public SocketFilterGroup SocketFilters { get; set; } = new(); + public SocketFilterGroup? SocketFilters { get; set; } [JsonPropertyName("req_filters")] - public RequirementFilterGroup RequirementFilters { get; set; } = new(); - - [JsonPropertyName("type_filters")] - public TypeFilterGroup TypeFilters { get; set; } = new(); + public RequirementFilterGroup? RequirementFilters { get; set; } [JsonPropertyName("map_filters")] - public MapFilterGroup MapFilters { get; set; } = new(); - - [JsonPropertyName("trade_filters")] - public TradeFilterGroup TradeFilters { get; set; } = new(); + public MapFilterGroup? MapFilters { get; set; } } } diff --git a/src/Sidekick.Apis.Poe/Trade/Results/Hashes.cs b/src/Sidekick.Apis.Poe/Trade/Results/Hashes.cs index fc9acecb0..92b23ab64 100644 --- a/src/Sidekick.Apis.Poe/Trade/Results/Hashes.cs +++ b/src/Sidekick.Apis.Poe/Trade/Results/Hashes.cs @@ -17,6 +17,9 @@ public class Hashes [JsonPropertyName("enchant")] public List> Enchant { get; set; } = new(); + [JsonPropertyName("rune")] + public List> Rune { get; set; } = new(); + [JsonPropertyName("pseudo")] public List> Pseudo { get; set; } = new(); diff --git a/src/Sidekick.Apis.Poe/Trade/Results/Mods.cs b/src/Sidekick.Apis.Poe/Trade/Results/Mods.cs index 1d33f3495..1b3a36164 100644 --- a/src/Sidekick.Apis.Poe/Trade/Results/Mods.cs +++ b/src/Sidekick.Apis.Poe/Trade/Results/Mods.cs @@ -8,6 +8,7 @@ public class Mods public List Explicit { get; set; } = new(); public List Crafted { get; set; } = new(); public List Enchant { get; set; } = new(); + public List Rune { get; set; } = new(); public List Fractured { get; set; } = new(); public List Scourge { get; set; } = new(); diff --git a/src/Sidekick.Apis.Poe/Trade/Results/ResultItem.cs b/src/Sidekick.Apis.Poe/Trade/Results/ResultItem.cs index af8aa7954..08fa38aac 100644 --- a/src/Sidekick.Apis.Poe/Trade/Results/ResultItem.cs +++ b/src/Sidekick.Apis.Poe/Trade/Results/ResultItem.cs @@ -64,6 +64,9 @@ public class ResultItem [JsonPropertyName("enchantMods")] public List EnchantMods { get; set; } = new(); + [JsonPropertyName("runeMods")] + public List RuneMods { get; set; } = new(); + [JsonPropertyName("fracturedMods")] public List FracturedMods { get; set; } = new(); diff --git a/src/Sidekick.Apis.Poe/Trade/Results/ResultSocket.cs b/src/Sidekick.Apis.Poe/Trade/Results/ResultSocket.cs index 87b0ba243..2afd16ac9 100644 --- a/src/Sidekick.Apis.Poe/Trade/Results/ResultSocket.cs +++ b/src/Sidekick.Apis.Poe/Trade/Results/ResultSocket.cs @@ -8,5 +8,11 @@ public class ResultSocket [JsonPropertyName("sColour")] public string? ColourString { get; set; } + + [JsonPropertyName("type")] + public string? Type { get; set; } + + [JsonPropertyName("item")] + public string? Item { get; set; } } } diff --git a/src/Sidekick.Apis.Poe/Trade/TradeFilterService.cs b/src/Sidekick.Apis.Poe/Trade/TradeFilterService.cs index f47d22586..62a5b0d06 100644 --- a/src/Sidekick.Apis.Poe/Trade/TradeFilterService.cs +++ b/src/Sidekick.Apis.Poe/Trade/TradeFilterService.cs @@ -5,19 +5,12 @@ namespace Sidekick.Apis.Poe.Trade { - public class TradeFilterService : ITradeFilterService + public class TradeFilterService + ( + IGameLanguageProvider gameLanguageProvider, + FilterResources resources + ) : ITradeFilterService { - private readonly IGameLanguageProvider gameLanguageProvider; - private readonly FilterResources resources; - - public TradeFilterService( - IGameLanguageProvider gameLanguageProvider, - FilterResources resources) - { - this.gameLanguageProvider = gameLanguageProvider; - this.resources = resources; - } - public IEnumerable GetModifierFilters(Item item) { // No filters for divination cards, etc. diff --git a/src/Sidekick.Apis.Poe/Trade/TradeSearchService.cs b/src/Sidekick.Apis.Poe/Trade/TradeSearchService.cs index 68abfc396..7d9b5280e 100644 --- a/src/Sidekick.Apis.Poe/Trade/TradeSearchService.cs +++ b/src/Sidekick.Apis.Poe/Trade/TradeSearchService.cs @@ -3,6 +3,7 @@ using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using Sidekick.Apis.Poe.Clients; +using Sidekick.Apis.Poe.Clients.Models; using Sidekick.Apis.Poe.Modifiers; using Sidekick.Apis.Poe.Trade.Models; using Sidekick.Apis.Poe.Trade.Requests; @@ -11,870 +12,817 @@ using Sidekick.Apis.Poe.Trade.Results; using Sidekick.Common.Enums; using Sidekick.Common.Exceptions; +using Sidekick.Common.Extensions; +using Sidekick.Common.Game; using Sidekick.Common.Game.Items; using Sidekick.Common.Game.Languages; using Sidekick.Common.Settings; -namespace Sidekick.Apis.Poe.Trade +namespace Sidekick.Apis.Poe.Trade; + +public class TradeSearchService +( + ILogger logger, + IGameLanguageProvider gameLanguageProvider, + ISettingsService settingsService, + IPoeTradeClient poeTradeClient, + IModifierProvider modifierProvider +) : ITradeSearchService { - public class TradeSearchService( - ILogger logger, - IGameLanguageProvider gameLanguageProvider, - ISettingsService settingsService, - IPoeTradeClient poeTradeClient, - IModifierProvider modifierProvider) : ITradeSearchService - { - private readonly ILogger logger = logger; + private readonly ILogger logger = logger; - public async Task> Search( - Item item, - TradeCurrency currency, - PropertyFilters? propertyFilters = null, - List? modifierFilters = null, - List? pseudoFilters = null) + public async Task> Search(Item item, TradeCurrency currency, PropertyFilters? propertyFilters = null, List? modifierFilters = null, List? pseudoFilters = null) + { + try { - try - { - logger.LogInformation("[Trade API] Querying Trade API."); - - if (gameLanguageProvider.Language == null) - { - throw new ApiErrorException("[Trade API] Could not find a valid language."); - } - - var hasTypeDiscriminator = !string.IsNullOrEmpty(item.Metadata.ApiTypeDiscriminator); - var query = new Query(); - if (hasTypeDiscriminator) - { - query.Type = new TypeDiscriminator() - { - Option = item.Metadata.ApiType, - Discriminator = item.Metadata.ApiTypeDiscriminator, - }; - } - else - { - query.Type = item.Metadata.ApiType; - } - - query.Filters.TradeFilters.Filters.Price.Option = currency.GetValueAttribute(); - - if (item.Metadata.Category == Category.ItemisedMonster) - { - if (!string.IsNullOrEmpty(item.Metadata.Name)) - { - query.Term = item.Metadata.Name; - query.Type = null; - } - } - else if (item.Metadata.Rarity == Rarity.Unique) - { - query.Name = item.Metadata.Name; - query.Filters.TypeFilters.Filters.Rarity = new SearchFilterOption("Unique"); - } - else - { - query.Filters.TypeFilters.Filters.Rarity = new SearchFilterOption("nonunique"); - } - - SetPropertyFilters(query, propertyFilters); - SetModifierFilters(query.Stats, modifierFilters); - SetPseudoModifierFilters(query.Stats, pseudoFilters); - SetSocketFilters(item, query.Filters); - - if (item.Properties.Anomalous) - { - query.Filters.MiscFilters.Filters.GemQualityType = new SearchFilterOption(SearchFilterOption.AlternateGemQualityOptions.Anomalous); - } - - if (item.Properties.Divergent) - { - query.Filters.MiscFilters.Filters.GemQualityType = new SearchFilterOption(SearchFilterOption.AlternateGemQualityOptions.Divergent); - } - - if (item.Properties.Phantasmal) - { - query.Filters.MiscFilters.Filters.GemQualityType = new SearchFilterOption(SearchFilterOption.AlternateGemQualityOptions.Phantasmal); - } - - var leagueId = await settingsService.GetString(SettingKeys.LeagueId); - var uri = new Uri($"{gameLanguageProvider.Language.PoeTradeApiBaseUrl}search/{leagueId}"); - - var json = JsonSerializer.Serialize( - new QueryRequest() - { - Query = query, - }, - poeTradeClient.Options); - - var body = new StringContent(json, Encoding.UTF8, "application/json"); - var response = await poeTradeClient.HttpClient.PostAsync(uri, body); - - var content = await response.Content.ReadAsStreamAsync(); - if (!response.IsSuccessStatusCode) - { - var responseMessage = await response.Content.ReadAsStringAsync(); - logger.LogWarning("[Trade API] Querying failed: {responseCode} {responseMessage}", response.StatusCode, responseMessage); - logger.LogWarning("[Trade API] Uri: {uri}", uri); - logger.LogWarning("[Trade API] Query: {query}", json); - - var errorResult = await JsonSerializer.DeserializeAsync(content, poeTradeClient.Options); - throw new ApiErrorException("[Trade API] Querying failed. " + errorResult?.Error?.Message); - } + logger.LogInformation("[Trade API] Querying Trade API."); - var result = await JsonSerializer.DeserializeAsync?>(content, poeTradeClient.Options); - if (result != null) + var hasTypeDiscriminator = !string.IsNullOrEmpty(item.Metadata.ApiTypeDiscriminator); + var query = new Query(); + if (hasTypeDiscriminator) + { + query.Type = new TypeDiscriminator() { - return result; - } + Option = item.Metadata.ApiType, + Discriminator = item.Metadata.ApiTypeDiscriminator, + }; } - catch (Exception ex) + else if (string.IsNullOrEmpty(propertyFilters?.ItemCategory)) { - logger.LogWarning(ex, "[Trade API] Exception thrown while querying trade api."); + query.Type = item.Metadata.ApiType; } - throw new ApiErrorException("[Trade API] Could not understand the API response."); - } + query.Filters.TradeFilters.Filters.Price.Option = currency.GetValueAttribute(); - private static void SetPropertyFilters( - Query query, - PropertyFilters? propertyFilters) - { - if (propertyFilters == null) + if (item.Metadata.Category == Category.ItemisedMonster) { - return; - } - - if (propertyFilters.Class.HasValue && propertyFilters.Class.Value != Class.Undefined) - { - var category = propertyFilters.Class.Value switch - { - Class.AbyssJewel => "jewel.abyss", - Class.ActiveSkillGems => "gem.activegem", - Class.Amulet => "accessory.amulet", - Class.Belt => "accessory.belt", - Class.Blueprint => "heistmission.blueprint", - Class.BodyArmours => "armour.chest", - Class.Boots => "armour.boots", - Class.Bows => "weapon.bow", - Class.Claws => "weapon.claw", - Class.Contract => "heistmission.contract", - Class.CriticalUtilityFlasks => "", - Class.Daggers => "weapon.dagger", - Class.DelveStackableSocketableCurrency => "currency.resonator", - Class.DivinationCard => "card", - Class.Gloves => "armour.gloves", - Class.HeistBrooch => "heistequipment.heistreward", - Class.HeistCloak => "heistequipment.heistutility", - Class.HeistGear => "heistequipment.heistweapon", - Class.HeistTarget => "currency.heistobjective", - Class.HeistTool => "heistequipment.heisttool", - Class.Helmets => "armour.helmet", - Class.HybridFlasks => "flask", - Class.Jewel => "jewel.base", - Class.LifeFlasks => "flask", - Class.Logbooks => "logbook", - Class.ManaFlasks => "flask", - Class.MapFragments => "map.fragment", - - // Maven invitations are in misc map items class at the moment. Ignoring for now. - // Class.MapInvitations => "map.invitation", - // This class does not exist, though the filter does. Ignoring for now. - // Class.MapScarabs => "map.scarab", - Class.Maps => "map", - Class.MetamorphSample => "monster.sample", - - // Ignoring for now - // Class.MiscMapItems => "", - Class.OneHandAxes => "weapon.oneaxe", - Class.OneHandMaces => "weapon.onemace", - Class.OneHandSwords => "weapon.onesword", - Class.Quivers => "armour.quiver", - Class.Ring => "accessory.ring", - Class.RuneDaggers => "weapon.runedagger", - Class.Sceptres => "weapon.sceptre", - Class.Shields => "armour.shield", - - // There are a lot of other uses for stackable currency currently such as beasts and scarabs. Ignoring for now. - // Class.StackableCurrency => "currency", - Class.Staves => "weapon.staff", - Class.SupportSkillGems => "gem.supportgem", - Class.ThrustingOneHandSwords => "", - Class.Trinkets => "accessory.trinket", - Class.TwoHandAxes => "weapon.twoaxe", - Class.TwoHandMaces => "weapon.twomace", - Class.TwoHandSwords => "weapon.twosword", - Class.UtilityFlasks => "flask", - Class.Wands => "weapon.wand", - Class.Warstaves => "weapon.warstaff", - Class.Sentinel => "sentinel", - Class.MemoryLine => "memoryline", - Class.AfflictionTinctures => "azmeri.tincture", - Class.AfflictionCharms => "azmeri.charm", - Class.AfflictionCorpses => "azmeri.corpse", - _ => null, - }; - - if (!string.IsNullOrEmpty(category)) + if (!string.IsNullOrEmpty(item.Metadata.Name)) { - query.Filters.TypeFilters.Filters.Category = new SearchFilterOption(category); + query.Term = item.Metadata.Name; query.Type = null; } } + else if (item.Metadata.Rarity == Rarity.Unique) + { + query.Name = item.Metadata.Name; + query.Filters.TypeFilters.Filters.Rarity = new SearchFilterOption("Unique"); + } + else + { + query.Filters.TypeFilters.Filters.Rarity = new SearchFilterOption("nonunique"); + } - SetPropertyFilters(query.Filters, propertyFilters.Armour); - SetPropertyFilters(query.Filters, propertyFilters.Weapon); - SetPropertyFilters(query.Filters, propertyFilters.Map); - SetPropertyFilters(query.Filters, propertyFilters.Misc); - } + SetModifierFilters(query.Stats, modifierFilters); + SetPseudoModifierFilters(query.Stats, pseudoFilters); + SetSocketFilters(item, query.Filters); - private static void SetPropertyFilters( - SearchFilters filters, - List propertyFilters) - { - foreach (var propertyFilter in propertyFilters) + if (propertyFilters != null) { - if (!propertyFilter.Enabled == true && propertyFilter.Type != PropertyFilterType.Misc_Corrupted) - { - continue; - } - - switch (propertyFilter.Type) - { - // Armour - case PropertyFilterType.Armour_Armour: - filters.ArmourFilters.Filters.Armor = new SearchFilterValue(propertyFilter); - break; + query.Filters.TypeFilters.Filters.Category = GetCategoryFilter(propertyFilters.ItemCategory); + query.Filters.WeaponFilters = GetWeaponFilters(propertyFilters.Weapon); + query.Filters.ArmourFilters = GetArmourFilters(propertyFilters.Armour); + query.Filters.MapFilters = GetMapFilters(propertyFilters.Map); + query.Filters.MiscFilters = GetMiscFilters(item, propertyFilters.Misc); + } - case PropertyFilterType.Armour_Block: - filters.ArmourFilters.Filters.Block = new SearchFilterValue(propertyFilter); - break; + var leagueId = await settingsService.GetString(SettingKeys.LeagueId); + var uri = new Uri($"{gameLanguageProvider.Language.GetTradeApiBaseUrl(item.Metadata.Game)}search/{leagueId.GetUrlSlugForLeague()}"); - case PropertyFilterType.Armour_EnergyShield: - filters.ArmourFilters.Filters.EnergyShield = new SearchFilterValue(propertyFilter); - break; + var json = JsonSerializer.Serialize(new QueryRequest() + { + Query = query, + }, + poeTradeClient.Options); - case PropertyFilterType.Armour_Evasion: - filters.ArmourFilters.Filters.Evasion = new SearchFilterValue(propertyFilter); - break; + var body = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await poeTradeClient.HttpClient.PostAsync(uri, body); - // Category - case PropertyFilterType.Category: - filters.TypeFilters.Filters.Category = new SearchFilterOption(propertyFilter); - break; + var content = await response.Content.ReadAsStreamAsync(); + if (!response.IsSuccessStatusCode) + { + var responseMessage = await response.Content.ReadAsStringAsync(); + logger.LogWarning("[Trade API] Querying failed: {responseCode} {responseMessage}", response.StatusCode, responseMessage); + logger.LogWarning("[Trade API] Uri: {uri}", uri); + logger.LogWarning("[Trade API] Query: {query}", json); - // Influence - case PropertyFilterType.Misc_Influence_Crusader: - filters.MiscFilters.Filters.CrusaderItem = new SearchFilterOption(propertyFilter); - break; + var errorResult = await JsonSerializer.DeserializeAsync(content, poeTradeClient.Options); + throw new ApiErrorException("[Trade API] Querying failed. " + errorResult?.Error?.Message); + } - case PropertyFilterType.Misc_Influence_Elder: - filters.MiscFilters.Filters.ElderItem = new SearchFilterOption(propertyFilter); - break; + var result = await JsonSerializer.DeserializeAsync?>(content, poeTradeClient.Options); + if (result != null) + { + return result; + } + } + catch (Exception ex) + { + logger.LogWarning(ex, "[Trade API] Exception thrown while querying trade api."); + } - case PropertyFilterType.Misc_Influence_Hunter: - filters.MiscFilters.Filters.HunterItem = new SearchFilterOption(propertyFilter); - break; + throw new ApiErrorException("[Trade API] Could not understand the API response."); + } - case PropertyFilterType.Misc_Influence_Redeemer: - filters.MiscFilters.Filters.RedeemerItem = new SearchFilterOption(propertyFilter); - break; + private SearchFilterOption? GetCategoryFilter(string? itemCategory) + { + if (string.IsNullOrEmpty(itemCategory)) + { + return null; + } - case PropertyFilterType.Misc_Influence_Shaper: - filters.MiscFilters.Filters.ShaperItem = new SearchFilterOption(propertyFilter); - break; + return new SearchFilterOption(itemCategory); + } - case PropertyFilterType.Misc_Influence_Warlord: - filters.MiscFilters.Filters.WarlordItem = new SearchFilterOption(propertyFilter); - break; + private WeaponFilterGroup? GetWeaponFilters(List propertyFilters) + { + var filters = new WeaponFilterGroup(); + var hasValue = false; - // Map - case PropertyFilterType.Map_ItemQuantity: - filters.MapFilters.Filters.ItemQuantity = new SearchFilterValue(propertyFilter); - break; + foreach (var propertyFilter in propertyFilters) + { + if (!propertyFilter.Enabled == true) + { + continue; + } - case PropertyFilterType.Map_ItemRarity: - filters.MapFilters.Filters.ItemRarity = new SearchFilterValue(propertyFilter); - break; + switch (propertyFilter.Type) + { + case PropertyFilterType.Weapon_PhysicalDps: + filters.Filters.PhysicalDps = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Weapon_ElementalDps: + filters.Filters.ElementalDps = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Weapon_Dps: + filters.Filters.DamagePerSecond = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Weapon_AttacksPerSecond: + filters.Filters.AttacksPerSecond = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Weapon_CriticalStrikeChance: + filters.Filters.CriticalStrikeChance = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + } + } - case PropertyFilterType.Map_AreaLevel: - filters.MapFilters.Filters.AreaLevel = new SearchFilterValue(propertyFilter); - break; + return hasValue ? filters : null; + } - case PropertyFilterType.Map_MonsterPackSize: - filters.MapFilters.Filters.MonsterPackSize = new SearchFilterValue(propertyFilter); - break; + private ArmorFilterGroup? GetArmourFilters(List propertyFilters) + { + var filters = new ArmorFilterGroup(); + var hasValue = false; - case PropertyFilterType.Map_Blighted: - filters.MapFilters.Filters.Blighted = new SearchFilterOption(propertyFilter); - break; + foreach (var propertyFilter in propertyFilters) + { + if (!propertyFilter.Enabled == true) + { + continue; + } - case PropertyFilterType.Map_BlightRavaged: - filters.MapFilters.Filters.BlightRavavaged = new SearchFilterOption(propertyFilter); - break; + switch (propertyFilter.Type) + { + case PropertyFilterType.Armour_Armour: + filters.Filters.Armor = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Armour_Block: + filters.Filters.Block = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Armour_EnergyShield: + filters.Filters.EnergyShield = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Armour_Evasion: + filters.Filters.Evasion = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + } + } - case PropertyFilterType.Map_Tier: - filters.MapFilters.Filters.MapTier = new SearchFilterValue(propertyFilter); - break; + return hasValue ? filters : null; + } - // Misc - case PropertyFilterType.Misc_Quality: - filters.MiscFilters.Filters.Quality = new SearchFilterValue(propertyFilter); - break; + private MapFilterGroup? GetMapFilters(List propertyFilters) + { + var filters = new MapFilterGroup(); + var hasValue = false; - case PropertyFilterType.Misc_GemLevel: - filters.MiscFilters.Filters.GemLevel = new SearchFilterValue(propertyFilter); - break; + foreach (var propertyFilter in propertyFilters) + { + if (!propertyFilter.Enabled == true) + { + continue; + } - case PropertyFilterType.Misc_ItemLevel: - filters.MiscFilters.Filters.ItemLevel = new SearchFilterValue(propertyFilter); - break; + switch (propertyFilter.Type) + { + case PropertyFilterType.Map_ItemQuantity: + filters.Filters.ItemQuantity = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Map_ItemRarity: + filters.Filters.ItemRarity = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Map_AreaLevel: + filters.Filters.AreaLevel = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Map_MonsterPackSize: + filters.Filters.MonsterPackSize = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Map_Blighted: + filters.Filters.Blighted = new SearchFilterOption(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Map_BlightRavaged: + filters.Filters.BlightRavavaged = new SearchFilterOption(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Map_Tier: + filters.Filters.MapTier = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + } + } - case PropertyFilterType.Misc_Corrupted: - filters.MiscFilters.Filters.Corrupted = propertyFilter.Enabled.HasValue ? new SearchFilterOption(propertyFilter) : null; - break; + return hasValue ? filters : null; + } - case PropertyFilterType.Misc_Scourged: - filters.MiscFilters.Filters.Scourged = new SearchFilterValue(propertyFilter); - break; + private MiscFilterGroup? GetMiscFilters(Item item, List propertyFilters) + { + var filters = new MiscFilterGroup(); + var hasValue = false; - // Weapon - case PropertyFilterType.Weapon_PhysicalDps: - filters.WeaponFilters.Filters.PhysicalDps = new SearchFilterValue(propertyFilter); - break; + if (item.Properties.Anomalous) + { + filters.Filters.GemQualityType = new SearchFilterOption(SearchFilterOption.AlternateGemQualityOptions.Anomalous); + hasValue = true; + } - case PropertyFilterType.Weapon_ElementalDps: - filters.WeaponFilters.Filters.ElementalDps = new SearchFilterValue(propertyFilter); - break; + if (item.Properties.Divergent) + { + filters.Filters.GemQualityType = new SearchFilterOption(SearchFilterOption.AlternateGemQualityOptions.Divergent); + hasValue = true; + } - case PropertyFilterType.Weapon_Dps: - filters.WeaponFilters.Filters.DamagePerSecond = new SearchFilterValue(propertyFilter); - break; + if (item.Properties.Phantasmal) + { + filters.Filters.GemQualityType = new SearchFilterOption(SearchFilterOption.AlternateGemQualityOptions.Phantasmal); + hasValue = true; + } - case PropertyFilterType.Weapon_AttacksPerSecond: - filters.WeaponFilters.Filters.AttacksPerSecond = new SearchFilterValue(propertyFilter); - break; + foreach (var propertyFilter in propertyFilters) + { + if (!propertyFilter.Enabled == true) + { + continue; + } - case PropertyFilterType.Weapon_CriticalStrikeChance: - filters.WeaponFilters.Filters.CriticalStrikeChance = new SearchFilterValue(propertyFilter); - break; - } + switch (propertyFilter.Type) + { + // Influence + case PropertyFilterType.Misc_Influence_Crusader: + filters.Filters.CrusaderItem = new SearchFilterOption(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Misc_Influence_Elder: + filters.Filters.ElderItem = new SearchFilterOption(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Misc_Influence_Hunter: + filters.Filters.HunterItem = new SearchFilterOption(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Misc_Influence_Redeemer: + filters.Filters.RedeemerItem = new SearchFilterOption(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Misc_Influence_Shaper: + filters.Filters.ShaperItem = new SearchFilterOption(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Misc_Influence_Warlord: + filters.Filters.WarlordItem = new SearchFilterOption(propertyFilter); + hasValue = true; + break; + + // Misc + case PropertyFilterType.Misc_Quality: + filters.Filters.Quality = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Misc_GemLevel: + filters.Filters.GemLevel = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Misc_ItemLevel: + filters.Filters.ItemLevel = new SearchFilterValue(propertyFilter); + hasValue = true; + break; + + case PropertyFilterType.Misc_Corrupted: + filters.Filters.Corrupted = propertyFilter.Enabled.HasValue ? new SearchFilterOption(propertyFilter) : null; + hasValue = true; + break; + + case PropertyFilterType.Misc_Scourged: + filters.Filters.Scourged = new SearchFilterValue(propertyFilter); + hasValue = true; + break; } } - private static void SetModifierFilters( - List stats, - List? modifierFilters) + return hasValue ? filters : null; + } + + private static void SetModifierFilters(List stats, List? modifierFilters) + { + if (modifierFilters == null) + { + return; + } + + var andGroup = stats.FirstOrDefault(x => x.Type == StatType.And); + if (andGroup == null) { - if (modifierFilters == null) + andGroup = new StatFilterGroup() { - return; - } + Type = StatType.And, + }; + stats.Add(andGroup); + } - var andGroup = stats.FirstOrDefault(x => x.Type == StatType.And); - if (andGroup == null) + var countGroup = stats.FirstOrDefault(x => x.Type == StatType.Count); + if (countGroup == null) + { + countGroup = new StatFilterGroup() { - andGroup = new StatFilterGroup() + Type = StatType.Count, + Value = new SearchFilterValue() { - Type = StatType.And, - }; - stats.Add(andGroup); - } + Min = 0, + }, + }; + stats.Add(countGroup); + } - var countGroup = stats.FirstOrDefault(x => x.Type == StatType.Count); - if (countGroup == null) + foreach (var filter in modifierFilters) + { + if (filter.Enabled == false) { - countGroup = new StatFilterGroup() - { - Type = StatType.Count, - Value = new SearchFilterValue() - { - Min = 0, - }, - }; - stats.Add(countGroup); + continue; } - foreach (var filter in modifierFilters) + if (filter.Line.Modifiers.Count == 1) { - if (filter.Enabled == false) - { - continue; - } - - if (filter.Line.Modifiers.Count == 1) + var modifier = filter.Line.Modifiers.FirstOrDefault(); + if (modifier == null) { - var modifier = filter.Line.Modifiers.FirstOrDefault(); - if (modifier == null) - { - continue; - } - - andGroup.Filters.Add( - new StatFilter() - { - Id = modifier.Id, - Value = new SearchFilterValue(filter), - }); continue; } - var modifiers = filter.Line.Modifiers; - if (filter.ForceFirstCategory) - { - modifiers = modifiers - .Where(x => x.Category == filter.FirstCategory) - .ToList(); - } - else if (modifiers.Any(x => x.Category == ModifierCategory.Explicit)) - { - modifiers = modifiers - .Where(x => x.Category == ModifierCategory.Explicit) - .ToList(); - } - - foreach (var modifier in modifiers) - { - countGroup.Filters.Add( - new StatFilter() - { - Id = modifier.Id, - Value = new SearchFilterValue(filter), - }); - } - - if (countGroup.Value != null && modifiers.Any()) + andGroup.Filters.Add(new StatFilter() { - countGroup.Value.Min += 1; - } + Id = modifier.Id, + Value = new SearchFilterValue(filter), + }); + continue; } - } - private static void SetPseudoModifierFilters( - List stats, - List? pseudoFilters) - { - if (pseudoFilters == null) + var modifiers = filter.Line.Modifiers; + if (filter.ForceFirstCategory) { - return; + modifiers = modifiers.Where(x => x.Category == filter.FirstCategory).ToList(); } - - var andGroup = stats.FirstOrDefault(x => x.Type == StatType.And); - if (andGroup == null) + else if (modifiers.Any(x => x.Category == ModifierCategory.Explicit)) { - andGroup = new StatFilterGroup() - { - Type = StatType.And, - }; - stats.Add(andGroup); + modifiers = modifiers.Where(x => x.Category == ModifierCategory.Explicit).ToList(); } - foreach (var filter in pseudoFilters) + foreach (var modifier in modifiers) { - if (filter.Enabled != true) + countGroup.Filters.Add(new StatFilter() { - continue; - } + Id = modifier.Id, + Value = new SearchFilterValue(filter), + }); + } - andGroup.Filters.Add( - new StatFilter() - { - Id = filter.Modifier.Id, - Value = new SearchFilterValue(filter), - }); + if (countGroup.Value != null && modifiers.Any()) + { + countGroup.Value.Min += 1; } } + } - private static void SetSocketFilters( - Item item, - SearchFilters filters) + private static void SetPseudoModifierFilters(List stats, List? pseudoFilters) + { + if (pseudoFilters == null) { - // Auto Search 5+ Links - var highestCount = item - .Sockets.GroupBy(x => x.Group) - .Select(x => x.Count()) - .OrderByDescending(x => x) - .FirstOrDefault(); - if (highestCount >= 5) + return; + } + + var andGroup = stats.FirstOrDefault(x => x.Type == StatType.And); + if (andGroup == null) + { + andGroup = new StatFilterGroup() { - filters.SocketFilters.Filters.Links = new SocketFilterOption() - { - Min = highestCount, - }; - } + Type = StatType.And, + }; + stats.Add(andGroup); } - public async Task> GetResults( - string queryId, - List ids, - List? pseudoFilters = null) + foreach (var filter in pseudoFilters) { - try + if (filter.Enabled != true) { - logger.LogInformation($"[Trade API] Fetching Trade API Listings from Query {queryId}."); + continue; + } - if (gameLanguageProvider.Language == null) - { - return new(); - } + andGroup.Filters.Add(new StatFilter() + { + Id = filter.Modifier.Id, + Value = new SearchFilterValue(filter), + }); + } + } - var pseudo = string.Empty; - if (pseudoFilters != null) - { - pseudo = string.Join("", pseudoFilters.Select(x => $"&pseudos[]={x.Modifier.Id}")); - } + private static void SetSocketFilters(Item item, SearchFilters filters) + { + // Auto Search 5+ Links + var highestCount = item.Sockets.GroupBy(x => x.Group).Select(x => x.Count()).OrderByDescending(x => x).FirstOrDefault(); + if (highestCount < 5) + { + return; + } - var response = await poeTradeClient.HttpClient.GetAsync(gameLanguageProvider.Language.PoeTradeApiBaseUrl + "fetch/" + string.Join(",", ids) + "?query=" + queryId + pseudo); - if (!response.IsSuccessStatusCode) + filters.SocketFilters = new() + { + Filters = + { + Links = new SocketFilterOption() { - return new(); - } + Min = highestCount, + }, + }, + }; + } - var content = await response.Content.ReadAsStreamAsync(); - var result = await JsonSerializer.DeserializeAsync>( - content, - new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }); - if (result == null) - { - return new(); - } + public async Task> GetResults(GameType game, string queryId, List ids, List? pseudoFilters = null) + { + try + { + logger.LogInformation($"[Trade API] Fetching Trade API Listings from Query {queryId}."); - return result - .Result.Where(x => x != null) - .ToList() - .ConvertAll(x => GetItem(x!)); + var pseudo = string.Empty; + if (pseudoFilters?.Count > 0) + { + pseudo = string.Join("", pseudoFilters.Select(x => $"&pseudos[]={x.Modifier.Id}")); } - catch (Exception ex) + + var response = await poeTradeClient.HttpClient.GetAsync(gameLanguageProvider.Language.GetTradeApiBaseUrl(game) + "fetch/" + string.Join(",", ids) + "?query=" + queryId + pseudo); + if (!response.IsSuccessStatusCode) { - logger.LogWarning(ex, $"[Trade API] Exception thrown when fetching trade API listings from Query {queryId}."); + return new(); } - return new(); + var content = await response.Content.ReadAsStreamAsync(); + var result = await JsonSerializer.DeserializeAsync>(content, + new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + if (result == null) + { + return new(); + } + + return result.Result.Where(x => x != null).ToList().ConvertAll(x => GetItem(game, x!)); + } + catch (Exception ex) + { + logger.LogWarning(ex, $"[Trade API] Exception thrown when fetching trade API listings from Query {queryId}."); } - private TradeItem GetItem(Result result) + return new(); + } + + private TradeItem GetItem(GameType game, Result result) + { + var metadata = new ItemMetadata() { - var metadata = new ItemMetadata() - { - Id = "", - Name = result.Item?.Name, - Rarity = result.Item?.Rarity ?? Rarity.Unknown, - Type = result.Item?.TypeLine, - ApiType = result.Item?.TypeLine, - Category = Category.Unknown, - }; + Id = "", + Name = result.Item?.Name, + Rarity = result.Item?.Rarity ?? Rarity.Unknown, + Type = result.Item?.TypeLine, + ApiType = result.Item?.TypeLine, + Category = Category.Unknown, + Game = game, + }; + + var original = new Header() + { + Name = result.Item?.Name, + Type = result.Item?.TypeLine, + }; - var original = new Header() + var properties = new Properties() + { + ItemLevel = result.Item?.ItemLevel ?? 0, + Corrupted = result.Item?.Corrupted ?? false, + Identified = result.Item?.Identified ?? false, + Armor = result.Item?.Extended?.ArmourAtMax ?? 0, + EnergyShield = result.Item?.Extended?.EnergyShieldAtMax ?? 0, + Evasion = result.Item?.Extended?.EvasionAtMax ?? 0, + DamagePerSecond = result.Item?.Extended?.DamagePerSecond ?? 0, + ElementalDps = result.Item?.Extended?.ElementalDps ?? 0, + PhysicalDps = result.Item?.Extended?.PhysicalDps ?? 0, + BaseDefencePercentile = result.Item?.Extended?.BaseDefencePercentile, + }; + + var influences = result.Item?.Influences ?? new(); + + var item = new TradeItem(metadata: metadata, + original: original, + properties: properties, + influences: influences, + sockets: ParseSockets(result.Item?.Sockets).ToList(), + modifierLines: new(), + pseudoModifiers: new(), + text: Encoding.UTF8.GetString(Convert.FromBase64String(result.Item?.Extended?.Text ?? string.Empty))) + { + Id = result.Id, + Price = new TradePrice() { - Name = result.Item?.Name, - Type = result.Item?.TypeLine, - }; + AccountCharacter = result.Listing?.Account?.LastCharacterName, + AccountName = result.Listing?.Account?.Name, + Amount = result.Listing?.Price?.Amount ?? -1, + Currency = result.Listing?.Price?.Currency ?? "", + Date = result.Listing?.Indexed ?? DateTimeOffset.MinValue, + Whisper = result.Listing?.Whisper, + Note = result.Item?.Note, + }, + Image = result.Item?.Icon, + Width = result.Item?.Width ?? 0, + Height = result.Item?.Height ?? 0, + RequirementContents = ParseLineContents(result.Item?.Requirements), + PropertyContents = ParseLineContents(result.Item?.Properties), + AdditionalPropertyContents = ParseLineContents(result.Item?.AdditionalProperties, false), + }; - var properties = new Properties() - { - ItemLevel = result.Item?.ItemLevel ?? 0, - Corrupted = result.Item?.Corrupted ?? false, - Identified = result.Item?.Identified ?? false, - Armor = result.Item?.Extended?.ArmourAtMax ?? 0, - EnergyShield = result.Item?.Extended?.EnergyShieldAtMax ?? 0, - Evasion = result.Item?.Extended?.EvasionAtMax ?? 0, - DamagePerSecond = result.Item?.Extended?.DamagePerSecond ?? 0, - ElementalDps = result.Item?.Extended?.ElementalDps ?? 0, - PhysicalDps = result.Item?.Extended?.PhysicalDps ?? 0, - BaseDefencePercentile = result.Item?.Extended?.BaseDefencePercentile, - }; + item.ModifierLines.AddRange(ParseModifierLines(result.Item?.EnchantMods, result.Item?.Extended?.Mods?.Enchant, ParseHash(result.Item?.Extended?.Hashes?.Enchant))); - var influences = result.Item?.Influences ?? new(); - - var item = new TradeItem( - metadata: metadata, - original: original, - properties: properties, - influences: influences, - sockets: ParseSockets(result.Item?.Sockets) - .ToList(), - modifierLines: new(), - pseudoModifiers: new(), - text: Encoding.UTF8.GetString(Convert.FromBase64String(result.Item?.Extended?.Text ?? string.Empty))) - { - Id = result.Id, - Price = new TradePrice() - { - AccountCharacter = result.Listing?.Account?.LastCharacterName, - AccountName = result.Listing?.Account?.Name, - Amount = result.Listing?.Price?.Amount ?? -1, - Currency = result.Listing?.Price?.Currency ?? "", - Date = result.Listing?.Indexed ?? DateTimeOffset.MinValue, - Whisper = result.Listing?.Whisper, - Note = result.Item?.Note, - }, - Image = result.Item?.Icon, - Width = result.Item?.Width ?? 0, - Height = result.Item?.Height ?? 0, - RequirementContents = ParseLineContents(result.Item?.Requirements), - PropertyContents = ParseLineContents(result.Item?.Properties), - AdditionalPropertyContents = ParseLineContents(result.Item?.AdditionalProperties, false), - }; + item.ModifierLines.AddRange(ParseModifierLines(result.Item?.RuneMods, result.Item?.Extended?.Mods?.Rune, ParseHash(result.Item?.Extended?.Hashes?.Rune))); - item.ModifierLines.AddRange(ParseModifierLines(result.Item?.EnchantMods, result.Item?.Extended?.Mods?.Enchant, ParseHash(result.Item?.Extended?.Hashes?.Enchant))); + item.ModifierLines.AddRange(ParseModifierLines(result.Item?.ImplicitMods ?? result.Item?.LogbookMods.SelectMany(x => x.Mods).ToList(), result.Item?.Extended?.Mods?.Implicit, ParseHash(result.Item?.Extended?.Hashes?.Implicit))); - item.ModifierLines.AddRange( - ParseModifierLines( - result.Item?.ImplicitMods - ?? result - .Item?.LogbookMods.SelectMany(x => x.Mods) - .ToList(), - result.Item?.Extended?.Mods?.Implicit, - ParseHash(result.Item?.Extended?.Hashes?.Implicit))); + item.ModifierLines.AddRange(ParseModifierLines(result.Item?.CraftedMods, result.Item?.Extended?.Mods?.Crafted, ParseHash(result.Item?.Extended?.Hashes?.Crafted))); - item.ModifierLines.AddRange(ParseModifierLines(result.Item?.CraftedMods, result.Item?.Extended?.Mods?.Crafted, ParseHash(result.Item?.Extended?.Hashes?.Crafted))); + item.ModifierLines.AddRange(ParseModifierLines(result.Item?.ExplicitMods, result.Item?.Extended?.Mods?.Explicit, ParseHash(result.Item?.Extended?.Hashes?.Explicit, result.Item?.Extended?.Hashes?.Monster))); - item.ModifierLines.AddRange(ParseModifierLines(result.Item?.ExplicitMods, result.Item?.Extended?.Mods?.Explicit, ParseHash(result.Item?.Extended?.Hashes?.Explicit, result.Item?.Extended?.Hashes?.Monster))); + item.ModifierLines.AddRange(ParseModifierLines(result.Item?.FracturedMods, result.Item?.Extended?.Mods?.Fractured, ParseHash(result.Item?.Extended?.Hashes?.Fractured))); - item.ModifierLines.AddRange(ParseModifierLines(result.Item?.FracturedMods, result.Item?.Extended?.Mods?.Fractured, ParseHash(result.Item?.Extended?.Hashes?.Fractured))); + item.ModifierLines.AddRange(ParseModifierLines(result.Item?.ScourgeMods, result.Item?.Extended?.Mods?.Scourge, ParseHash(result.Item?.Extended?.Hashes?.Scourge))); - item.ModifierLines.AddRange(ParseModifierLines(result.Item?.ScourgeMods, result.Item?.Extended?.Mods?.Scourge, ParseHash(result.Item?.Extended?.Hashes?.Scourge))); + item.PseudoModifiers.AddRange(ParsePseudoModifiers(result.Item?.PseudoMods, result.Item?.Extended?.Mods?.Pseudo, ParseHash(result.Item?.Extended?.Hashes?.Pseudo))); - item.PseudoModifiers.AddRange(ParsePseudoModifiers(result.Item?.PseudoMods, result.Item?.Extended?.Mods?.Pseudo, ParseHash(result.Item?.Extended?.Hashes?.Pseudo))); + item.ModifierLines = item.ModifierLines.OrderBy(x => item.Text.IndexOf(x.Text, StringComparison.InvariantCultureIgnoreCase)).ToList(); - item.ModifierLines = item - .ModifierLines.OrderBy(x => item.Text.IndexOf(x.Text, StringComparison.InvariantCultureIgnoreCase)) - .ToList(); + return item; + } - return item; - } + private static List ParseHash(params List>?[] hashes) + { + var result = new List(); - private static List ParseHash(params List>?[] hashes) + foreach (var values in hashes) { - var result = new List(); + if (values == null) + { + continue; + } - foreach (var values in hashes) + foreach (var value in values) { - if (values == null) + if (value.Count != 2) { continue; } - foreach (var value in values) + result.Add(new LineContentValue() + { + Value = value[0].GetString(), + Type = value[1].ValueKind == JsonValueKind.Array ? (LineContentType)value[1][0].GetInt32() : LineContentType.Simple + }); + } + } + + return result; + } + + private static List ParseLineContents(List? lines, bool executeOrderBy = true) + { + if (lines == null) + { + return new(); + } + + return lines.OrderBy(x => executeOrderBy ? x.Order : 0) + .Select(line => + { + var values = new List(); + foreach (var value in line.Values) { if (value.Count != 2) { continue; } - result.Add( - new LineContentValue() - { - Value = value[0] - .GetString(), - Type = value[1].ValueKind == JsonValueKind.Array ? - (LineContentType)value[1][0] - .GetInt32() : - LineContentType.Simple - }); + values.Add(new LineContentValue() + { + Value = value[0].GetString(), + Type = (LineContentType)value[1].GetInt32() + }); } - } - return result; - } + var text = line.Name; - private static List ParseLineContents( - List? lines, - bool executeOrderBy = true) - { - if (lines == null) - { - return new(); - } + if (values.Count <= 0) + { + return new LineContent() + { + Text = text, + Values = values, + }; + } - return lines - .OrderBy(x => executeOrderBy ? x.Order : 0) - .Select( - line => - { - var values = new List(); - foreach (var value in line.Values) - { - if (value.Count != 2) - { - continue; - } - - values.Add( - new LineContentValue() - { - Value = value[0] - .GetString(), - Type = (LineContentType)value[1] - .GetInt32() - }); - } - - var text = line.Name; - - if (values.Count <= 0) - { - return new LineContent() - { - Text = text, - Values = values, - }; - } - - switch (line.DisplayMode) - { - case 0: - text = line.Name; - if (values.Count > 0) - { - if (!string.IsNullOrEmpty(line.Name)) - { - text += ": "; - } - - text += string.Join(", ", values.Select(x => x.Value)); - } - - break; - - case 1: - text = $"{values[0].Value} {line.Name}"; - break; - - case 2: - text = $"{values[0].Value}"; - break; - - case 3: - var format = Regex.Replace(line.Name ?? string.Empty, "%(\\d)", "{$1}"); - text = string.Format( - format, - values - .Select(x => x.Value) - .ToArray()); - break; - - default: - text = $"{line.Name} {string.Join(", ", values.Select(x => x.Value))}"; - break; - } - - return new LineContent() - { - Text = text, - Values = values, - }; - }) - .ToList(); - } + switch (line.DisplayMode) + { + case 0: + text = line.Name; + if (values.Count > 0) + { + if (!string.IsNullOrEmpty(line.Name)) + { + text += ": "; + } - private IEnumerable ParseModifierLines( - List? texts, - List? mods, - List? hashes) - { - if (texts == null || mods == null || hashes == null) - { - yield break; - } + text += string.Join(", ", values.Select(x => x.Value)); + } - for (var index = 0; index < hashes.Count; index++) - { - var id = hashes[index].Value; - if (id == null || index >= texts.Count) - { - continue; + break; + + case 1: text = $"{values[0].Value} {line.Name}"; break; + + case 2: text = $"{values[0].Value}"; break; + + case 3: + var format = Regex.Replace(line.Name ?? string.Empty, "%(\\d)", "{$1}"); + text = string.Format(format, values.Select(x => x.Value).ToArray()); + break; + + default: text = $"{line.Name} {string.Join(", ", values.Select(x => x.Value))}"; break; } - var text = texts.FirstOrDefault(x => modifierProvider.IsMatch(id, x)) ?? texts[index]; - var mod = mods.FirstOrDefault(x => x.Magnitudes?.Any(y => y.Hash == id) == true); + if (text != null) text = ModifierProvider.RemoveSquareBrackets(text); - yield return new ModifierLine(text: text) + return new LineContent() { - Modifiers = - [ - new Modifier(text: text) - { - Id = id, - Category = modifierProvider.GetModifierCategory(id), - Tier = mod?.Tier, - TierName = mod?.Name, - },], + Text = text, + Values = values, }; - } + }) + .ToList(); + } + + private IEnumerable ParseModifierLines(List? texts, List? mods, List? hashes) + { + if (texts == null || mods == null || hashes == null) + { + yield break; } - private IEnumerable ParsePseudoModifiers( - List? texts, - List? mods, - List? hashes) + for (var index = 0; index < hashes.Count; index++) { - if (texts == null || mods == null || hashes == null) + var id = hashes[index].Value; + if (id == null || index >= texts.Count) { - yield break; + continue; } - foreach (var hash in hashes) - { - var id = hash.Value; - if (id == null) - { - continue; - } + var text = texts.FirstOrDefault(x => modifierProvider.IsMatch(id, x)) ?? texts[index]; + text = ModifierProvider.RemoveSquareBrackets(text); - var text = texts.FirstOrDefault(x => modifierProvider.IsMatch(id, x)); - if (text == null) - { - continue; - } + var mod = mods.FirstOrDefault(x => x.Magnitudes?.Any(y => y.Hash == id) == true); - yield return new PseudoModifier(text: text) - { - Id = id, - }; - } + yield return new ModifierLine(text: text) + { + Modifiers = + [ + new Modifier(text: text) + { + Id = id, + Category = modifierProvider.GetModifierCategory(id), + Tier = mod?.Tier, + TierName = mod?.Name, + }, + ], + }; + } + } + + private IEnumerable ParsePseudoModifiers(List? texts, List? mods, List? hashes) + { + if (texts == null || mods == null || hashes == null) + { + yield break; } - private static IEnumerable ParseSockets(List? sockets) + foreach (var hash in hashes) { - if (sockets == null) + var id = hash.Value; + if (id == null) { - return - [ - ]; + continue; } - return sockets - .Where(x => x.ColourString != "DV") // Remove delve resonator sockets - .Select( - x => new Socket() - { - Group = x.Group, - Colour = x.ColourString switch - { - "B" => SocketColour.Blue, - "G" => SocketColour.Green, - "R" => SocketColour.Red, - "W" => SocketColour.White, - "A" => SocketColour.Abyss, - _ => throw new Exception("Invalid socket"), - } - }); + var text = texts.FirstOrDefault(x => modifierProvider.IsMatch(id, x)); + if (text == null) + { + continue; + } + + yield return new PseudoModifier(text: text) + { + Id = id, + }; } + } - public async Task GetTradeUri(string queryId) + private static IEnumerable ParseSockets(List? sockets) + { + if (sockets == null) { - var baseUri = gameLanguageProvider.Language?.PoeTradeSearchBaseUrl; - if (baseUri == null) + return + [ + ]; + } + + return sockets.Where(x => x.ColourString != "DV") // Remove delve resonator sockets + .Select(x => new Socket() { - throw new Exception("[Trade API] Could not find the trade uri."); - } + Group = x.Group, + Colour = x.ColourString switch + { + "B" => SocketColour.Blue, + "G" => SocketColour.Green, + "R" => SocketColour.Red, + "W" => SocketColour.White, + "A" => SocketColour.Abyss, + "S" => SocketColour.Soulcore, + _ => x.Type switch + { + "rune" => x.Item == "rune" ? SocketColour.Rune : SocketColour.PathOfExile2, + _ => throw new Exception("Invalid socket"), + } + } + }); + } - var leagueId = await settingsService.GetString(SettingKeys.LeagueId); - return new Uri(baseUri, $"{leagueId}/{queryId}"); - } + public async Task GetTradeUri(GameType game, string queryId) + { + var baseUrl = gameLanguageProvider.Language.GetTradeBaseUrl(game); + var baseUri = new Uri(baseUrl + "search/"); + var leagueId = await settingsService.GetString(SettingKeys.LeagueId); + return new Uri(baseUri, $"{leagueId.GetUrlSlugForLeague()}/{queryId}"); } } diff --git a/src/Sidekick.Apis.PoeNinja/PoeNinjaClient.cs b/src/Sidekick.Apis.PoeNinja/PoeNinjaClient.cs index 619977495..c7679ca46 100644 --- a/src/Sidekick.Apis.PoeNinja/PoeNinjaClient.cs +++ b/src/Sidekick.Apis.PoeNinja/PoeNinjaClient.cs @@ -5,6 +5,7 @@ using Sidekick.Apis.PoeNinja.Models; using Sidekick.Common.Cache; using Sidekick.Common.Enums; +using Sidekick.Common.Extensions; using Sidekick.Common.Game.Items; using Sidekick.Common.Settings; @@ -143,6 +144,7 @@ public async Task GetDetailsUri(NinjaPrice ninjaPrice) /// private string GetLeagueUri(string? leagueId) { + leagueId = leagueId.GetUrlSlugForLeague(); return leagueId switch { "Standard" => "standard", @@ -228,7 +230,7 @@ private async Task> GetItems(ItemType itemType) private async Task> FetchItems(ItemType itemType) { var leagueId = await settingsService.GetString(SettingKeys.LeagueId); - var url = new Uri($"{apiBaseUrl}itemoverview?league={leagueId}&type={itemType}"); + var url = new Uri($"{apiBaseUrl}itemoverview?league={leagueId.GetUrlSlugForLeague()}&type={itemType}"); try { @@ -271,7 +273,7 @@ private async Task> FetchItems(ItemType itemType) private async Task> FetchCurrencies(ItemType itemType) { var leagueId = await settingsService.GetString(SettingKeys.LeagueId); - var url = new Uri($"{apiBaseUrl}currencyoverview?league={leagueId}&type={itemType}"); + var url = new Uri($"{apiBaseUrl}currencyoverview?league={leagueId.GetUrlSlugForLeague()}&type={itemType}"); try { diff --git a/src/Sidekick.Apis.PoePriceInfo/PoePriceInfoClient.cs b/src/Sidekick.Apis.PoePriceInfo/PoePriceInfoClient.cs index 8e214e5ff..9d887260d 100644 --- a/src/Sidekick.Apis.PoePriceInfo/PoePriceInfoClient.cs +++ b/src/Sidekick.Apis.PoePriceInfo/PoePriceInfoClient.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using Sidekick.Apis.PoePriceInfo.Api; using Sidekick.Apis.PoePriceInfo.Models; +using Sidekick.Common.Extensions; using Sidekick.Common.Game.Items; using Sidekick.Common.Settings; @@ -40,7 +41,7 @@ private HttpClient GetHttpClient() var leagueId = await settingsService.GetString(SettingKeys.LeagueId); var encodedItem = Convert.ToBase64String(Encoding.UTF8.GetBytes(item.Text)); using var client = GetHttpClient(); - var response = await client.GetAsync("?l=" + leagueId + "&i=" + encodedItem); + var response = await client.GetAsync("?l=" + leagueId.GetUrlSlugForLeague() + "&i=" + encodedItem); var content = await response.Content.ReadAsStreamAsync(); var result = await JsonSerializer.DeserializeAsync(content, options); diff --git a/src/Sidekick.Apis.PoeWiki/PoeWikiClient.cs b/src/Sidekick.Apis.PoeWiki/PoeWikiClient.cs index 416d43105..502d7e85e 100644 --- a/src/Sidekick.Apis.PoeWiki/PoeWikiClient.cs +++ b/src/Sidekick.Apis.PoeWiki/PoeWikiClient.cs @@ -8,7 +8,6 @@ using Sidekick.Common.Browser; using Sidekick.Common.Cache; using Sidekick.Common.Game.Items; -using Sidekick.Common.Initialization; namespace Sidekick.Apis.PoeWiki { @@ -61,7 +60,7 @@ private HttpClient GetHttpClient() public Dictionary BlightOilNamesByMetadataIds { get; private set; } = new(); /// - public InitializationPriority Priority => InitializationPriority.Low; + public int Priority => 0; /// public async Task Initialize() diff --git a/src/Sidekick.Common.Blazor/Initialization/Initialization.razor.cs b/src/Sidekick.Common.Blazor/Initialization/Initialization.razor.cs index ff2574fac..ab1b76ac8 100644 --- a/src/Sidekick.Common.Blazor/Initialization/Initialization.razor.cs +++ b/src/Sidekick.Common.Blazor/Initialization/Initialization.razor.cs @@ -87,17 +87,19 @@ public async Task Handle() // Report initial progress await ReportProgress(); - foreach (var serviceType in Configuration.Value.InitializableServices) - { - var service = ServiceProvider.GetRequiredService(serviceType); - if (service is not IInitializableService initializableService) + var services = Configuration.Value.InitializableServices.Select(serviceType => { - continue; - } - - Logger.LogInformation($"[Initialization] Initializing {initializableService.GetType().FullName}"); - - await initializableService.Initialize(); + var service = ServiceProvider.GetRequiredService(serviceType); + return service as IInitializableService; + }) + .Where(x => x != null) + .Select(x => x!) + .OrderBy(x => x.Priority); + + foreach (var service in services) + { + Logger.LogInformation($"[Initialization] Initializing {service.GetType().FullName}"); + await service.Initialize(); Completed += 1; await ReportProgress(); } @@ -141,60 +143,53 @@ private async Task Run(Action action) private Task ReportProgress() { - return InvokeAsync( - () => + return InvokeAsync(() => + { + Percentage = Count == 0 ? 0 : Completed * 100 / Count; + if (Percentage >= 100) { - Percentage = Count == 0 ? 0 : Completed * 100 / Count; - if (Percentage >= 100) - { - Step = Resources.Ready; - Percentage = 100; - } - else - { - Step = Resources.Title(Completed, Count); - } + Step = Resources.Ready; + Percentage = 100; + } + else + { + Step = Resources.Title(Completed, Count); + } - StateHasChanged(); - return Task.Delay(100); - }); + StateHasChanged(); + return Task.Delay(100); + }); } private string? GetVersion() { - return FileVersionInfo.GetVersionInfo( - GetType() - .Assembly.Location) - .ProductVersion; + return FileVersionInfo.GetVersionInfo(GetType().Assembly.Location).ProductVersion; } private void InitializeTray() { var menuItems = new List(); - menuItems.AddRange( - new List() - { - new(label: "Sidekick - " + GetVersion()), - new( - label: "Open Website", - onClick: () => - { - BrowserProvider.OpenSidekickWebsite(); - return Task.CompletedTask; - }), - - // new(label: "Wealth", onClick: () => ViewLocator.Open("/wealth")), - - new(label: "Settings", onClick: () => ViewLocator.Open("/settings")), - new( - label: "Exit", - onClick: () => - { - ApplicationService.Shutdown(); - return Task.CompletedTask; - }), - }); + menuItems.AddRange(new List() + { + new(label: "Sidekick - " + GetVersion()), + new(label: "Open Website", + onClick: () => + { + BrowserProvider.OpenSidekickWebsite(); + return Task.CompletedTask; + }), + + // new(label: "Wealth", onClick: () => ViewLocator.Open("/wealth")), + + new(label: "Settings", onClick: () => ViewLocator.Open("/settings")), + new(label: "Exit", + onClick: () => + { + ApplicationService.Shutdown(); + return Task.CompletedTask; + }), + }); TrayProvider.Initialize(menuItems); } diff --git a/src/Sidekick.Common.Platform/Keyboards/KeyboardProvider.cs b/src/Sidekick.Common.Platform/Keyboards/KeyboardProvider.cs index a5dbda367..f742a91ce 100644 --- a/src/Sidekick.Common.Platform/Keyboards/KeyboardProvider.cs +++ b/src/Sidekick.Common.Platform/Keyboards/KeyboardProvider.cs @@ -145,7 +145,7 @@ public class KeyboardProvider( ]; /// - public InitializationPriority Priority => InitializationPriority.Low; + public int Priority => 100; /// public Task Initialize() diff --git a/src/Sidekick.Common.Platform/Windows/Processes/ProcessProvider.cs b/src/Sidekick.Common.Platform/Windows/Processes/ProcessProvider.cs index baaa2c754..74f21c5a0 100644 --- a/src/Sidekick.Common.Platform/Windows/Processes/ProcessProvider.cs +++ b/src/Sidekick.Common.Platform/Windows/Processes/ProcessProvider.cs @@ -5,17 +5,30 @@ using System.Security.Principal; using System.Text; using Microsoft.Extensions.Logging; -using Sidekick.Common.Initialization; using Sidekick.Common.Platform.Windows.DllImport; using Sidekick.Common.Platforms.Localization; namespace Sidekick.Common.Platform.Windows.Processes { - public class ProcessProvider : IProcessProvider, IDisposable + public class ProcessProvider + ( + ILogger logger, + IApplicationService applicationService, + ISidekickDialogs dialogService, + PlatformResources platformResources + ) : IProcessProvider, IDisposable { private const string PATH_OF_EXILE_TITLE = "Path of Exile"; + private const string PATH_OF_EXILE_2_TITLE = "Path of Exile 2"; private const string SIDEKICK_TITLE = "Sidekick"; - private static readonly List PossibleProcessNames = new() { "PathOfExile", "PathOfExile_x64", "PathOfExileSteam", "PathOfExile_x64Steam" }; + + private static readonly List PossibleProcessNames = new() + { + "PathOfExile", + "PathOfExile_x64", + "PathOfExileSteam", + "PathOfExile_x64Steam" + }; public string? ClientLogPath { @@ -43,16 +56,16 @@ public string? ClientLogPath private const int TOKEN_ADJUST_DEFAULT = 0x80; private const int TOKEN_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_SESSIONID | TOKEN_ADJUST_DEFAULT; - private readonly ILogger logger; - private readonly IApplicationService applicationService; - private readonly ISidekickDialogs dialogService; - private readonly PlatformResources platformResources; + private readonly ILogger logger = logger; private bool PermissionChecked { get; set; } = false; + private bool HasInitialized { get; set; } = false; + private CancellationTokenSource? WindowsHook { get; set; } private DateTimeOffset PreviousFocusedWindowAttempt { get; set; } + private string? PreviousFocusedWindow { get; set; } private string? GetFocusedWindow() @@ -81,25 +94,20 @@ public string? ClientLogPath } /// - public bool IsPathOfExileInFocus => GetFocusedWindow() == PATH_OF_EXILE_TITLE; + public bool IsPathOfExileInFocus + { + get + { + var focusedWindow = GetFocusedWindow(); + return focusedWindow is PATH_OF_EXILE_TITLE or PATH_OF_EXILE_2_TITLE; + } + } /// public bool IsSidekickInFocus => GetFocusedWindow()?.StartsWith(SIDEKICK_TITLE) ?? false; - public ProcessProvider( - ILogger logger, - IApplicationService applicationService, - ISidekickDialogs dialogService, - PlatformResources platformResources) - { - this.logger = logger; - this.applicationService = applicationService; - this.dialogService = dialogService; - this.platformResources = platformResources; - } - /// - public InitializationPriority Priority => InitializationPriority.Low; + public int Priority => 0; /// public Task Initialize() @@ -110,13 +118,26 @@ public Task Initialize() return Task.CompletedTask; } - WindowsHook = EventLoop.Run(WinEvent.EVENT_SYSTEM_FOREGROUND, WinEvent.EVENT_SYSTEM_CAPTURESTART, IntPtr.Zero, OnWindowsEvent, 0, 0, WinEvent.WINEVENT_OUTOFCONTEXT); + WindowsHook = EventLoop.Run(WinEvent.EVENT_SYSTEM_FOREGROUND, + WinEvent.EVENT_SYSTEM_CAPTURESTART, + IntPtr.Zero, + OnWindowsEvent, + 0, + 0, + WinEvent.WINEVENT_OUTOFCONTEXT); HasInitialized = true; return Task.CompletedTask; } - private void OnWindowsEvent(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + private void OnWindowsEvent( + IntPtr hWinEventHook, + uint eventType, + IntPtr hwnd, + int idObject, + int idChild, + uint dwEventThread, + uint dwmsEventTime) { if (eventType == WinEvent.EVENT_SYSTEM_MINIMIZEEND || eventType == WinEvent.EVENT_SYSTEM_FOREGROUND) { diff --git a/src/Sidekick.Common.Ui/Errors/ErrorFullScreenBoundary.razor b/src/Sidekick.Common.Ui/Errors/ErrorFullScreenBoundary.razor index c9caabb8b..c6042e53e 100644 --- a/src/Sidekick.Common.Ui/Errors/ErrorFullScreenBoundary.razor +++ b/src/Sidekick.Common.Ui/Errors/ErrorFullScreenBoundary.razor @@ -23,6 +23,15 @@ } + + diff --git a/src/Sidekick.Common.Ui/Errors/SidekickErrorBoundary.razor b/src/Sidekick.Common.Ui/Errors/SidekickErrorBoundary.razor index a61a52259..478cd976e 100644 --- a/src/Sidekick.Common.Ui/Errors/SidekickErrorBoundary.razor +++ b/src/Sidekick.Common.Ui/Errors/SidekickErrorBoundary.razor @@ -54,7 +54,7 @@ private LoggingErrorBoundary? Boundary { get; set; } - private Exception? Exception => Boundary?.CurrentException; + public Exception? Exception => Boundary?.CurrentException; protected override async Task OnAfterRenderAsync(bool firstRender) { diff --git a/src/Sidekick.Common.Ui/Poe/Items/ItemModifierText.razor b/src/Sidekick.Common.Ui/Poe/Items/ItemModifierText.razor index 9e5de0da8..49d8bb23d 100644 --- a/src/Sidekick.Common.Ui/Poe/Items/ItemModifierText.razor +++ b/src/Sidekick.Common.Ui/Poe/Items/ItemModifierText.razor @@ -21,6 +21,7 @@ ModifierCategory.Scourge => "text-[#FF6E25]", ModifierCategory.Fractured => "text-[#A29162]", ModifierCategory.Enchant => "text-[#B4B4FF]", + ModifierCategory.Rune => "text-[#B4B4FF]", ModifierCategory.Crafted => "text-[#B4B4FF]", ModifierCategory.Crucible => "text-[#FF7339]", ModifierCategory.Corrupted => "text-[#D20000]", diff --git a/src/Sidekick.Common.Ui/wwwroot/css/app.css b/src/Sidekick.Common.Ui/wwwroot/css/app.css index fd5851a33..de04fb6be 100644 --- a/src/Sidekick.Common.Ui/wwwroot/css/app.css +++ b/src/Sidekick.Common.Ui/wwwroot/css/app.css @@ -1571,6 +1571,10 @@ video { margin-top: 1rem; } +.mt-9 { + margin-top: 2.25rem; +} + .box-border { box-sizing: border-box; } diff --git a/src/Sidekick.Common/Exceptions/UnparsableException.cs b/src/Sidekick.Common/Exceptions/UnparsableException.cs index 0d6b4109b..f7ac5e4f6 100644 --- a/src/Sidekick.Common/Exceptions/UnparsableException.cs +++ b/src/Sidekick.Common/Exceptions/UnparsableException.cs @@ -3,12 +3,12 @@ namespace Sidekick.Common.Exceptions; public class UnparsableException : SidekickException { public UnparsableException() - : base("Unable to parse this item. Make sure you have set your game language correctly in the settings.") + : base("Unable to parse this item. Make sure you have set your game language and league correctly in the settings.") { } public UnparsableException(string? additionalInformation) - : base("Unable to parse this item. Make sure you have set your game language correctly in the settings.", additionalInformation) + : base("Unable to parse this item. Make sure you have set your game language and league correctly in the settings.", additionalInformation) { } } diff --git a/src/Sidekick.Common/Extensions/StringExtensions.cs b/src/Sidekick.Common/Extensions/StringExtensions.cs index 0c5e188f7..701326f1d 100644 --- a/src/Sidekick.Common/Extensions/StringExtensions.cs +++ b/src/Sidekick.Common/Extensions/StringExtensions.cs @@ -1,5 +1,6 @@ using System.Text; using System.Web; +using Sidekick.Common.Game; namespace Sidekick.Common.Extensions; @@ -104,4 +105,39 @@ public static bool HasInvalidUrlCharacters(this string input) { return input.EncodeUrl() != input; } + + public static string? GetUrlSlugForLeague(this string? leagueId) + { + return leagueId?.Split('.').ElementAtOrDefault(1); + } + + public static GameType GetGameFromLeagueId(this string? leagueId) + { + return leagueId + ?.Split('.') + .ElementAtOrDefault(0) switch + { + "poe2" => GameType.PathOfExile2, + _ => GameType.PathOfExile, + }; + } + + public static int GetDeterministicHashCode(this string str) + { + unchecked + { + var hash1 = (5381 << 16) + 5381; + var hash2 = hash1; + + for (var i = 0; i < str.Length; i += 2) + { + hash1 = ((hash1 << 5) + hash1) ^ str[i]; + if (i == str.Length - 1) + break; + hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; + } + + return hash1 + (hash2 * 1566083941); + } + } } diff --git a/src/Sidekick.Common/Game/GameType.cs b/src/Sidekick.Common/Game/GameType.cs new file mode 100644 index 000000000..edf693507 --- /dev/null +++ b/src/Sidekick.Common/Game/GameType.cs @@ -0,0 +1,12 @@ +using Sidekick.Common.Enums; + +namespace Sidekick.Common.Game; + +public enum GameType +{ + [EnumValue("poe1")] + PathOfExile, + + [EnumValue("poe2")] + PathOfExile2, +} diff --git a/src/Sidekick.Common/Game/Items/Class.cs b/src/Sidekick.Common/Game/Items/Class.cs deleted file mode 100644 index faef20982..000000000 --- a/src/Sidekick.Common/Game/Items/Class.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace Sidekick.Common.Game.Items; - -public enum Class -{ - Undefined = 0, - DivinationCard = 1, - StackableCurrency = 2, - Jewel = 3, - DelveStackableSocketableCurrency = 4, - MetamorphSample = 5, - AbyssJewel = 6, - Logbooks = 7, - Sentinel = 8, - MemoryLine = 9, - - // Accessory - Amulet = 101, - - Ring = 102, - Belt = 103, - Trinkets = 104, - - // Armour - Gloves = 201, - - Boots = 202, - BodyArmours = 203, - Helmets = 204, - Shields = 205, - Quivers = 206, - - // Flasks - LifeFlasks = 301, - - ManaFlasks = 302, - HybridFlasks = 303, - UtilityFlasks = 304, - CriticalUtilityFlasks = 305, - - // Gems - ActiveSkillGems = 401, - - SupportSkillGems = 402, - - // Maps - Maps = 501, - - MapFragments = 502, - Contract = 503, - Blueprint = 504, - MiscMapItems = 505, - - // Weapons - Claws = 601, - - Daggers = 602, - Wands = 603, - OneHandSwords = 604, - ThrustingOneHandSwords = 605, - OneHandAxes = 606, - OneHandMaces = 607, - Bows = 608, - Staves = 609, - TwoHandSwords = 610, - TwoHandAxes = 611, - TwoHandMaces = 612, - Sceptres = 613, - RuneDaggers = 614, - Warstaves = 615, - FishingRods = 616, - - // Heist - HeistTool = 701, - - HeistCloak = 702, - HeistGear = 703, - HeistBrooch = 704, - HeistTarget = 705, - - // Sanctum - SanctumResearch = 801, - - // Affliction - AfflictionCharms = 901, - AfflictionTinctures = 902, - AfflictionCorpses = 903, - - // Necropolis - - EmbersOfTheAllflame = 1001, -} diff --git a/src/Sidekick.Common/Game/Items/Header.cs b/src/Sidekick.Common/Game/Items/Header.cs index 53262406b..265a90ee7 100644 --- a/src/Sidekick.Common/Game/Items/Header.cs +++ b/src/Sidekick.Common/Game/Items/Header.cs @@ -6,7 +6,7 @@ public class Header public string? Type { get; init; } - public Class Class { get; init; } + public string? ItemCategory { get; init; } /// public override string? ToString() diff --git a/src/Sidekick.Common/Game/Items/Item.cs b/src/Sidekick.Common/Game/Items/Item.cs index 3422e4049..2a6736f2c 100644 --- a/src/Sidekick.Common/Game/Items/Item.cs +++ b/src/Sidekick.Common/Game/Items/Item.cs @@ -38,7 +38,7 @@ public class Item( Category.Flask => true, Category.Gem => true, Category.Jewel => true, - Category.Map => Header.Class != Class.MapFragments, + Category.Map => Metadata.Rarity != Rarity.Currency, Category.Weapon => true, Category.HeistEquipment => true, Category.Contract => true, diff --git a/src/Sidekick.Common/Game/Items/ItemMetadata.cs b/src/Sidekick.Common/Game/Items/ItemMetadata.cs index ed11db50a..1b6b7790c 100644 --- a/src/Sidekick.Common/Game/Items/ItemMetadata.cs +++ b/src/Sidekick.Common/Game/Items/ItemMetadata.cs @@ -21,4 +21,6 @@ public class ItemMetadata public required Rarity Rarity { get; set; } public required Category Category { get; init; } + + public required GameType Game { get; init; } } diff --git a/src/Sidekick.Common/Game/Items/ModifierCategory.cs b/src/Sidekick.Common/Game/Items/ModifierCategory.cs index 970972933..36ecea510 100644 --- a/src/Sidekick.Common/Game/Items/ModifierCategory.cs +++ b/src/Sidekick.Common/Game/Items/ModifierCategory.cs @@ -14,6 +14,8 @@ public enum ModifierCategory Scourge = 9, Veiled = 10, Crucible = 11, + Rune = 12, + Sanctum = 13, // Meta modifiers Corrupted = 101, diff --git a/src/Sidekick.Common/Game/Items/SocketColour.cs b/src/Sidekick.Common/Game/Items/SocketColour.cs index ab9dc7545..45f9be95e 100644 --- a/src/Sidekick.Common/Game/Items/SocketColour.cs +++ b/src/Sidekick.Common/Game/Items/SocketColour.cs @@ -7,4 +7,14 @@ public enum SocketColour Red, White, Abyss, + + // The following three socket colours are to support Path of Exile 2 + // This socket represents an empty socket. + PathOfExile2, + + // This socket represents a socket with a soulcore socketed in it. + Soulcore, + + // This socket represents a socket with a rune socketed in it. + Rune, } diff --git a/src/Sidekick.Common/Game/Languages/ClassLanguage.cs b/src/Sidekick.Common/Game/Languages/ClassLanguage.cs index fcbfae531..4a8b449f7 100644 --- a/src/Sidekick.Common/Game/Languages/ClassLanguage.cs +++ b/src/Sidekick.Common/Game/Languages/ClassLanguage.cs @@ -106,11 +106,27 @@ public class ClassLanguage public required string SanctumResearch { get; init; } - public string AfflictionCharms { get; init; } = ""; + public required string SanctumRelics { get; init; } - public string AfflictionTinctures { get; init; } = ""; + public required string Tinctures { get; init; } - public string AfflictionCorpses { get; init; } = ""; + public required string Corpses { get; init; } - public string EmbersOfTheAllflame { get; init; } = ""; + public required string Crossbows { get; init; } + + public required string Quarterstaves { get; init; } + + public required string Focus { get; init; } + + public required string Charms { get; init; } + + public required string Waystones { get; init; } + + public required string Tablets { get; init; } + + public required string Socketable { get; init; } + + public required string InscribedUltimatum { get; init; } + + public required string TrialCoins { get; init; } } diff --git a/src/Sidekick.Common/Game/Languages/GameLanguageProvider.cs b/src/Sidekick.Common/Game/Languages/GameLanguageProvider.cs index 722cd782f..23f37530e 100644 --- a/src/Sidekick.Common/Game/Languages/GameLanguageProvider.cs +++ b/src/Sidekick.Common/Game/Languages/GameLanguageProvider.cs @@ -1,47 +1,28 @@ -using Microsoft.Extensions.Logging; +using Sidekick.Common.Exceptions; using Sidekick.Common.Extensions; -using Sidekick.Common.Initialization; using Sidekick.Common.Settings; namespace Sidekick.Common.Game.Languages; -public class GameLanguageProvider( - ILogger logger, - ISettingsService settingsService) : IGameLanguageProvider +public class GameLanguageProvider(ISettingsService settingsService) : IGameLanguageProvider { private const string EnglishLanguageCode = "en"; + private IGameLanguage? language; + private IGameLanguage? invariantLanguage; - public IGameLanguage? Language { get; private set; } + public IGameLanguage Language => language ?? throw new SidekickException("The game language could not be found."); + + public IGameLanguage InvariantLanguage => invariantLanguage ?? throw new SidekickException("The English language could not be found."); /// - public InitializationPriority Priority => InitializationPriority.Critical; + public int Priority => 0; /// public async Task Initialize() { - var language = await settingsService.GetString(SettingKeys.LanguageParser); - SetLanguage(language ?? EnglishLanguageCode); - } - - public void SetLanguage(string? languageCode) - { - languageCode ??= EnglishLanguageCode; - if (Language?.LanguageCode == languageCode) - { - return; - } - - var availableLanguages = GetList(); - var language = availableLanguages.Find(x => x.LanguageCode == languageCode); - - if (language == null || language.ImplementationType == null) - { - logger.LogWarning("[GameLanguage] Couldn't find language matching {language}. Setting language to English.", languageCode); - SetLanguage(EnglishLanguageCode); - return; - } - - Language = (IGameLanguage?)Activator.CreateInstance(language.ImplementationType); + var languageCode = await settingsService.GetString(SettingKeys.LanguageParser); + language = GetLanguage(languageCode ?? EnglishLanguageCode); + invariantLanguage = GetInvariantLanguage(); } public List GetList() @@ -63,22 +44,31 @@ public List GetList() return result; } - public IGameLanguage? Get(string code) + private IGameLanguage GetLanguage(string? languageCode) { + languageCode ??= EnglishLanguageCode; + var languages = GetList(); + var implementationType = languages.FirstOrDefault(x => x.LanguageCode == languageCode) + ?.ImplementationType + ?? throw new SidekickException("The game language could not be found."); - var implementationType = languages.FirstOrDefault(x => x.LanguageCode == code) - ?.ImplementationType; - if (implementationType != default) - { - return (IGameLanguage?)Activator.CreateInstance(implementationType); - } + return (IGameLanguage?)Activator.CreateInstance(implementationType) ?? throw new SidekickException("The game language could not be found."); + } + + private IGameLanguage GetInvariantLanguage() + { + var languages = GetList(); + + var implementationType = languages.FirstOrDefault(x => x.LanguageCode == EnglishLanguageCode) + ?.ImplementationType + ?? throw new SidekickException("The English language could not be found."); - return null; + return (IGameLanguage?)Activator.CreateInstance(implementationType) ?? throw new SidekickException("The English language could not be found."); } public bool IsEnglish() { - return Language?.LanguageCode == EnglishLanguageCode; + return Language.GetType() == InvariantLanguage.GetType(); } } diff --git a/src/Sidekick.Common/Game/Languages/IGameLanguage.cs b/src/Sidekick.Common/Game/Languages/IGameLanguage.cs index 24ed02a29..044a1e1af 100644 --- a/src/Sidekick.Common/Game/Languages/IGameLanguage.cs +++ b/src/Sidekick.Common/Game/Languages/IGameLanguage.cs @@ -2,13 +2,13 @@ namespace Sidekick.Common.Game.Languages; public interface IGameLanguage { - string LanguageCode { get; } + string PoeTradeBaseUrl { get; } - Uri PoeTradeSearchBaseUrl { get; } + string PoeTradeApiBaseUrl { get; } - Uri PoeTradeExchangeBaseUrl { get; } + string Poe2TradeBaseUrl { get; } - Uri PoeTradeApiBaseUrl { get; } + string Poe2TradeApiBaseUrl { get; } Uri PoeCdnBaseUrl { get; } @@ -94,5 +94,17 @@ public interface IGameLanguage string InfluenceWarlord { get; } - ClassLanguage? Classes { get; } + ClassLanguage Classes { get; } + + public string GetTradeBaseUrl(GameType game) => game switch + { + GameType.PathOfExile2 => Poe2TradeBaseUrl, + _ => PoeTradeBaseUrl, + }; + + public string GetTradeApiBaseUrl(GameType game) => game switch + { + GameType.PathOfExile2 => Poe2TradeApiBaseUrl, + _ => PoeTradeApiBaseUrl, + }; } diff --git a/src/Sidekick.Common/Game/Languages/IGameLanguageProvider.cs b/src/Sidekick.Common/Game/Languages/IGameLanguageProvider.cs index ba13095a8..fda0afd01 100644 --- a/src/Sidekick.Common/Game/Languages/IGameLanguageProvider.cs +++ b/src/Sidekick.Common/Game/Languages/IGameLanguageProvider.cs @@ -4,13 +4,11 @@ namespace Sidekick.Common.Game.Languages; public interface IGameLanguageProvider : IInitializableService { - IGameLanguage? Language { get; } + IGameLanguage Language { get; } - void SetLanguage(string? languageCode); + IGameLanguage InvariantLanguage { get; } List GetList(); - IGameLanguage? Get(string code); - bool IsEnglish(); } diff --git a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageDE.cs b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageDe.cs similarity index 87% rename from src/Sidekick.Common/Game/Languages/Implementations/GameLanguageDE.cs rename to src/Sidekick.Common/Game/Languages/Implementations/GameLanguageDe.cs index 905dceb29..00ff3fe2c 100644 --- a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageDE.cs +++ b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageDe.cs @@ -1,15 +1,17 @@ namespace Sidekick.Common.Game.Languages.Implementations; [GameLanguage("German", "de")] -public class GameLanguageDE : IGameLanguage +public class GameLanguageDe : IGameLanguage { public string LanguageCode => "de"; - public Uri PoeTradeSearchBaseUrl => new("https://de.pathofexile.com/trade/search/"); + public string PoeTradeBaseUrl => new("https://de.pathofexile.com/trade/"); - public Uri PoeTradeExchangeBaseUrl => new("https://de.pathofexile.com/trade/exchange/"); + public string PoeTradeApiBaseUrl => new("https://de.pathofexile.com/api/trade/"); - public Uri PoeTradeApiBaseUrl => new("https://de.pathofexile.com/api/trade/"); + public string Poe2TradeBaseUrl => new("https://de.pathofexile.com/trade2/"); + + public string Poe2TradeApiBaseUrl => new("https://de.pathofexile.com/api/trade2/"); public Uri PoeCdnBaseUrl => new("https://web.poecdn.com/"); @@ -149,5 +151,17 @@ public class GameLanguageDE : IGameLanguage Logbooks = "Expeditions-Logbücher", MemoryLine = "Erinnerungen", SanctumResearch = "Sanktum-Forschungsauftrag", + SanctumRelics = "__", + Tinctures = "__", + Corpses = "__", + Charms = "__", + Crossbows = "__", + Focus = "__", + Quarterstaves = "__", + Socketable = "__", + Tablets = "__", + Waystones = "__", + InscribedUltimatum = "__", + TrialCoins = "__", }; } diff --git a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageEN.cs b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageEn.cs similarity index 86% rename from src/Sidekick.Common/Game/Languages/Implementations/GameLanguageEN.cs rename to src/Sidekick.Common/Game/Languages/Implementations/GameLanguageEn.cs index 85267a977..7b929aa01 100644 --- a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageEN.cs +++ b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageEn.cs @@ -1,15 +1,17 @@ namespace Sidekick.Common.Game.Languages.Implementations; [GameLanguage("English", "en")] -public class GameLanguageEN : IGameLanguage +public class GameLanguageEn : IGameLanguage { public string LanguageCode => "en"; - public Uri PoeTradeSearchBaseUrl => new("https://www.pathofexile.com/trade/search/"); + public string PoeTradeBaseUrl => "https://www.pathofexile.com/trade/"; - public Uri PoeTradeExchangeBaseUrl => new("https://www.pathofexile.com/trade/exchange/"); + public string PoeTradeApiBaseUrl => "https://www.pathofexile.com/api/trade/"; - public Uri PoeTradeApiBaseUrl => new("https://www.pathofexile.com/api/trade/"); + public string Poe2TradeBaseUrl => "https://www.pathofexile.com/trade2/"; + + public string Poe2TradeApiBaseUrl => "https://www.pathofexile.com/api/trade2/"; public Uri PoeCdnBaseUrl => new("https://web.poecdn.com/"); @@ -148,10 +150,18 @@ public class GameLanguageEN : IGameLanguage Trinkets = "Trinkets", Logbooks = "Expedition Logbooks", MemoryLine = "Memories", - AfflictionCharms = "Charms", - AfflictionTinctures = "Tinctures", - AfflictionCorpses = "Corpses", + SanctumRelics = "Relics", SanctumResearch = "Sanctum Research", - EmbersOfTheAllflame = "Embers of the Allflame", + Tinctures = "Tinctures", + Corpses = "Corpses", + Charms = "__", + Crossbows = "__", + Focus = "__", + Quarterstaves = "__", + Socketable = "__", + Tablets = "__", + Waystones = "__", + InscribedUltimatum = "__", + TrialCoins = "__", }; } diff --git a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageES.cs b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageEs.cs similarity index 87% rename from src/Sidekick.Common/Game/Languages/Implementations/GameLanguageES.cs rename to src/Sidekick.Common/Game/Languages/Implementations/GameLanguageEs.cs index a08861260..367c72d22 100644 --- a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageES.cs +++ b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageEs.cs @@ -1,15 +1,17 @@ namespace Sidekick.Common.Game.Languages.Implementations; [GameLanguage("Spanish", "es")] -public class GameLanguageES : IGameLanguage +public class GameLanguageEs : IGameLanguage { public string LanguageCode => "es"; - public Uri PoeTradeSearchBaseUrl => new("https://es.pathofexile.com/trade/search/"); + public string PoeTradeBaseUrl => new("https://es.pathofexile.com/trade/"); - public Uri PoeTradeExchangeBaseUrl => new("https://es.pathofexile.com/trade/exchange/"); + public string PoeTradeApiBaseUrl => new("https://es.pathofexile.com/api/trade/"); - public Uri PoeTradeApiBaseUrl => new("https://es.pathofexile.com/api/trade/"); + public string Poe2TradeBaseUrl => new("https://es.pathofexile.com/trade2/"); + + public string Poe2TradeApiBaseUrl => new("https://es.pathofexile.com/api/trade2/"); public Uri PoeCdnBaseUrl => new("https://web.poecdn.com/"); @@ -149,5 +151,17 @@ public class GameLanguageES : IGameLanguage Logbooks = "Registros de expedición", MemoryLine = "Recuerdos", SanctumResearch = "Investigación del santuario", + SanctumRelics = "__", + Tinctures = "__", + Corpses = "__", + Charms = "__", + Crossbows = "__", + Focus = "__", + Quarterstaves = "__", + Socketable = "__", + Tablets = "__", + Waystones = "__", + InscribedUltimatum = "__", + TrialCoins = "__", }; } diff --git a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageFR.cs b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageFr.cs similarity index 87% rename from src/Sidekick.Common/Game/Languages/Implementations/GameLanguageFR.cs rename to src/Sidekick.Common/Game/Languages/Implementations/GameLanguageFr.cs index 7e92a799a..da75497fb 100644 --- a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageFR.cs +++ b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageFr.cs @@ -1,15 +1,17 @@ namespace Sidekick.Common.Game.Languages.Implementations; [GameLanguage("French", "fr")] -public class GameLanguageFR : IGameLanguage +public class GameLanguageFr : IGameLanguage { public string LanguageCode => "fr"; - public Uri PoeTradeSearchBaseUrl => new("https://fr.pathofexile.com/trade/search/"); + public string PoeTradeBaseUrl => "https://fr.pathofexile.com/trade/"; - public Uri PoeTradeExchangeBaseUrl => new("https://fr.pathofexile.com/trade/exchange/"); + public string PoeTradeApiBaseUrl => "https://fr.pathofexile.com/api/trade/"; - public Uri PoeTradeApiBaseUrl => new("https://fr.pathofexile.com/api/trade/"); + public string Poe2TradeBaseUrl => "https://fr.pathofexile.com/trade2/"; + + public string Poe2TradeApiBaseUrl => "https://fr.pathofexile.com/api/trade2/"; public Uri PoeCdnBaseUrl => new("https://web.poecdn.com/"); @@ -149,5 +151,17 @@ public class GameLanguageFR : IGameLanguage Logbooks = "Journaux de bord d'Expédition", MemoryLine = "Souvenirs", SanctumResearch = "Recherche du Sanctuaire", + SanctumRelics = "__", + Tinctures = "__", + Corpses = "__", + Charms = "__", + Crossbows = "__", + Focus = "__", + Quarterstaves = "__", + Socketable = "__", + Tablets = "__", + Waystones = "__", + InscribedUltimatum = "__", + TrialCoins = "__", }; } diff --git a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageJP.cs b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageJp.cs similarity index 87% rename from src/Sidekick.Common/Game/Languages/Implementations/GameLanguageJP.cs rename to src/Sidekick.Common/Game/Languages/Implementations/GameLanguageJp.cs index a1e28a32f..98d7e1a37 100644 --- a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageJP.cs +++ b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageJp.cs @@ -1,15 +1,17 @@ namespace Sidekick.Common.Game.Languages.Implementations; [GameLanguage("Japanese", "jp")] -public class GameLanguageJP : IGameLanguage +public class GameLanguageJp : IGameLanguage { public string LanguageCode => "jp"; - public Uri PoeTradeSearchBaseUrl => new("https://jp.pathofexile.com/trade/search/"); + public string PoeTradeBaseUrl => "https://jp.pathofexile.com/trade/"; - public Uri PoeTradeExchangeBaseUrl => new("https://jp.pathofexile.com/trade/exchange/"); + public string PoeTradeApiBaseUrl => "https://jp.pathofexile.com/api/trade/"; - public Uri PoeTradeApiBaseUrl => new("https://jp.pathofexile.com/api/trade/"); + public string Poe2TradeBaseUrl => "https://jp.pathofexile.com/trade2/"; + + public string Poe2TradeApiBaseUrl => "https://jp.pathofexile.com/api/trade2/"; public Uri PoeCdnBaseUrl => new("https://web.poecdn.com/"); @@ -149,5 +151,17 @@ public class GameLanguageJP : IGameLanguage Logbooks = "エクスペディションログブック", MemoryLine = "記憶", SanctumResearch = "サンクタム調査書", + SanctumRelics = "__", + Tinctures = "__", + Corpses = "__", + Charms = "__", + Crossbows = "__", + Focus = "__", + Quarterstaves = "__", + Socketable = "__", + Tablets = "__", + Waystones = "__", + InscribedUltimatum = "__", + TrialCoins = "__", }; } diff --git a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageKR.cs b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageKr.cs similarity index 87% rename from src/Sidekick.Common/Game/Languages/Implementations/GameLanguageKR.cs rename to src/Sidekick.Common/Game/Languages/Implementations/GameLanguageKr.cs index 1e4a68c05..09cbcaba2 100644 --- a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageKR.cs +++ b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageKr.cs @@ -1,15 +1,17 @@ namespace Sidekick.Common.Game.Languages.Implementations; [GameLanguage("Korean", "kr")] -public class GameLanguageKR : IGameLanguage +public class GameLanguageKr : IGameLanguage { public string LanguageCode => "kr"; - public Uri PoeTradeSearchBaseUrl => new("https://poe.game.daum.net/trade/search/"); + public string PoeTradeBaseUrl => "https://poe.game.daum.net/trade/"; - public Uri PoeTradeExchangeBaseUrl => new("https://poe.game.daum.net/trade/exchange/"); + public string PoeTradeApiBaseUrl => "https://poe.game.daum.net/api/trade/"; - public Uri PoeTradeApiBaseUrl => new("https://poe.game.daum.net/api/trade/"); + public string Poe2TradeBaseUrl => "https://poe.game.daum.net/trade2/"; + + public string Poe2TradeApiBaseUrl => "https://poe.game.daum.net/api/trade2/"; public Uri PoeCdnBaseUrl => new("https://web.poecdn.com/"); @@ -149,5 +151,17 @@ public class GameLanguageKR : IGameLanguage Logbooks = "탐험 일지", MemoryLine = "기억", SanctumResearch = "성역 연구", + SanctumRelics = "__", + Tinctures = "__", + Corpses = "__", + Charms = "__", + Crossbows = "__", + Focus = "__", + Quarterstaves = "__", + Socketable = "__", + Tablets = "__", + Waystones = "__", + InscribedUltimatum = "__", + TrialCoins = "__", }; } diff --git a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguagePT.cs b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguagePt.cs similarity index 87% rename from src/Sidekick.Common/Game/Languages/Implementations/GameLanguagePT.cs rename to src/Sidekick.Common/Game/Languages/Implementations/GameLanguagePt.cs index c7be0e5e3..0aa971d64 100644 --- a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguagePT.cs +++ b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguagePt.cs @@ -1,15 +1,17 @@ namespace Sidekick.Common.Game.Languages.Implementations; [GameLanguage("Portuguese", "pt")] -public class GameLanguagePT : IGameLanguage +public class GameLanguagePt : IGameLanguage { public string LanguageCode => "pt"; - public Uri PoeTradeSearchBaseUrl => new("https://br.pathofexile.com/trade/search/"); + public string PoeTradeBaseUrl => "https://br.pathofexile.com/trade/"; - public Uri PoeTradeExchangeBaseUrl => new("https://br.pathofexile.com/trade/exchange/"); + public string PoeTradeApiBaseUrl => "https://br.pathofexile.com/api/trade/"; - public Uri PoeTradeApiBaseUrl => new("https://br.pathofexile.com/api/trade/"); + public string Poe2TradeBaseUrl => "https://br.pathofexile.com/trade2/"; + + public string Poe2TradeApiBaseUrl => "https://br.pathofexile.com/api/trade2/"; public Uri PoeCdnBaseUrl => new("https://web.poecdn.com/"); @@ -149,5 +151,17 @@ public class GameLanguagePT : IGameLanguage Logbooks = "Diários de Bordo Expedition", MemoryLine = "Memórias", SanctumResearch = "Pesquisa Sanctum", + SanctumRelics = "__", + Tinctures = "__", + Corpses = "__", + Charms = "__", + Crossbows = "__", + Focus = "__", + Quarterstaves = "__", + Socketable = "__", + Tablets = "__", + Waystones = "__", + InscribedUltimatum = "__", + TrialCoins = "__", }; } diff --git a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageRU.cs b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageRu.cs similarity index 89% rename from src/Sidekick.Common/Game/Languages/Implementations/GameLanguageRU.cs rename to src/Sidekick.Common/Game/Languages/Implementations/GameLanguageRu.cs index b1838df67..ec24fbe1e 100644 --- a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageRU.cs +++ b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageRu.cs @@ -1,15 +1,17 @@ namespace Sidekick.Common.Game.Languages.Implementations; [GameLanguage("Russian", "ru")] -public class GameLanguageRU : IGameLanguage +public class GameLanguageRu : IGameLanguage { public string LanguageCode => "ru"; - public Uri PoeTradeSearchBaseUrl => new("https://ru.pathofexile.com/trade/search/"); + public string PoeTradeBaseUrl => new("https://ru.pathofexile.com/trade/"); - public Uri PoeTradeExchangeBaseUrl => new("https://ru.pathofexile.com/trade/exchange/"); + public string PoeTradeApiBaseUrl => new("https://ru.pathofexile.com/api/trade/"); - public Uri PoeTradeApiBaseUrl => new("https://ru.pathofexile.com/api/trade/"); + public string Poe2TradeBaseUrl => new("https://ru.pathofexile.com/trade2/"); + + public string Poe2TradeApiBaseUrl => new("https://ru.pathofexile.com/api/trade2/"); public Uri PoeCdnBaseUrl => new("https://web.poecdn.com/"); @@ -149,5 +151,17 @@ public class GameLanguageRU : IGameLanguage Logbooks = "Журналы экспедиции", MemoryLine = "Воспоминания", SanctumResearch = "Исследования Святилища", + SanctumRelics = "__", + Tinctures = "__", + Corpses = "__", + Charms = "__", + Crossbows = "__", + Focus = "__", + Quarterstaves = "__", + Socketable = "__", + Tablets = "__", + Waystones = "__", + InscribedUltimatum = "__", + TrialCoins = "__", }; } diff --git a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageTH.cs b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageTh.cs similarity index 90% rename from src/Sidekick.Common/Game/Languages/Implementations/GameLanguageTH.cs rename to src/Sidekick.Common/Game/Languages/Implementations/GameLanguageTh.cs index bea3528d9..7c1132f31 100644 --- a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageTH.cs +++ b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageTh.cs @@ -1,15 +1,17 @@ namespace Sidekick.Common.Game.Languages.Implementations; [GameLanguage("Thai", "th")] -public class GameLanguageTH : IGameLanguage +public class GameLanguageTh : IGameLanguage { public string LanguageCode => "th"; - public Uri PoeTradeSearchBaseUrl => new("https://th.pathofexile.com/trade/search/"); + public string PoeTradeBaseUrl => new("https://th.pathofexile.com/trade/"); - public Uri PoeTradeExchangeBaseUrl => new("https://th.pathofexile.com/trade/exchange/"); + public string PoeTradeApiBaseUrl => new("https://th.pathofexile.com/api/trade/"); - public Uri PoeTradeApiBaseUrl => new("https://th.pathofexile.com/api/trade/"); + public string Poe2TradeBaseUrl => new("https://th.pathofexile.com/trade2/"); + + public string Poe2TradeApiBaseUrl => new("https://th.pathofexile.com/api/trade2/"); public Uri PoeCdnBaseUrl => new("https://web.poecdn.com/"); @@ -149,5 +151,17 @@ public class GameLanguageTH : IGameLanguage Logbooks = "สมุดปูมเดินทางกองสำรวจ", MemoryLine = "ความทรงจำ", SanctumResearch = "บทวิจัยเทวสถาน", + SanctumRelics = "__", + Tinctures = "__", + Corpses = "__", + Charms = "__", + Crossbows = "__", + Focus = "__", + Quarterstaves = "__", + Socketable = "__", + Tablets = "__", + Waystones = "__", + InscribedUltimatum = "__", + TrialCoins = "__", }; } diff --git a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageZHTW.cs b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageZhTw.cs similarity index 84% rename from src/Sidekick.Common/Game/Languages/Implementations/GameLanguageZHTW.cs rename to src/Sidekick.Common/Game/Languages/Implementations/GameLanguageZhTw.cs index 7a084ca24..48a4f9137 100644 --- a/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageZHTW.cs +++ b/src/Sidekick.Common/Game/Languages/Implementations/GameLanguageZhTw.cs @@ -1,19 +1,17 @@ namespace Sidekick.Common.Game.Languages.Implementations; [GameLanguage("Traditional Chinese", "zh")] -public class GameLanguageZHTW : IGameLanguage +public class GameLanguageZhTw : IGameLanguage { - public string DescriptionIsRelic => "古典傳奇"; - - public string DescriptionScourged => "災魘"; - public string LanguageCode => "zh"; - public Uri PoeTradeSearchBaseUrl => new("http://web.poe.garena.tw/trade/search/"); + public string PoeTradeBaseUrl => new("http://web.poe.garena.tw/trade/"); + + public string PoeTradeApiBaseUrl => new("http://web.poe.garena.tw/api/trade/"); - public Uri PoeTradeExchangeBaseUrl => new("http://web.poe.garena.tw/trade/exchange/"); + public string Poe2TradeBaseUrl => new("http://web.poe.garena.tw/trade2/"); - public Uri PoeTradeApiBaseUrl => new("http://web.poe.garena.tw/api/trade/"); + public string Poe2TradeApiBaseUrl => new("http://web.poe.garena.tw/api/trade2/"); public Uri PoeCdnBaseUrl => new("https://web.poecdn.com/"); @@ -99,7 +97,7 @@ public class GameLanguageZHTW : IGameLanguage public string InfluenceWarlord => "總督軍物品"; - public ClassLanguage? Classes => new() + public ClassLanguage Classes => new() { Prefix = "___", DivinationCard = "___", @@ -153,5 +151,17 @@ public class GameLanguageZHTW : IGameLanguage Logbooks = "___", MemoryLine = "___", SanctumResearch = "___", + SanctumRelics = "__", + Tinctures = "__", + Corpses = "__", + Charms = "__", + Crossbows = "__", + Focus = "__", + Quarterstaves = "__", + Socketable = "__", + Tablets = "__", + Waystones = "__", + InscribedUltimatum = "__", + TrialCoins = "__", }; } diff --git a/src/Sidekick.Common/Initialization/IInitializableService.cs b/src/Sidekick.Common/Initialization/IInitializableService.cs index d83a3d3f8..484674d70 100644 --- a/src/Sidekick.Common/Initialization/IInitializableService.cs +++ b/src/Sidekick.Common/Initialization/IInitializableService.cs @@ -8,7 +8,7 @@ public interface IInitializableService /// /// Gets the priority of execution for this service during the initialization process. /// - InitializationPriority Priority { get; } + int Priority { get; } /// /// Initializes the service during startup. diff --git a/src/Sidekick.Common/Initialization/InitializationPriority.cs b/src/Sidekick.Common/Initialization/InitializationPriority.cs deleted file mode 100644 index 0e8bd75be..000000000 --- a/src/Sidekick.Common/Initialization/InitializationPriority.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Sidekick.Common.Initialization; - -/// -/// Determines the order of execution for the initialization services. -/// -public enum InitializationPriority -{ - /// - /// Represents a critical priority, will run before any other priorities. - /// - Critical = 0, - - /// - /// Represents a medium priority, will run after critical priority services. - /// - High = 1, - - /// - /// Represents a medium priority, will run after high priority services. - /// - Medium = 2, - - /// - /// Represents a low priority, will run after high and medium priority services. - /// - Low = 3, -} diff --git a/src/Sidekick.Common/Keybinds/KeybindHandler.cs b/src/Sidekick.Common/Keybinds/KeybindHandler.cs index 0e0c96c9c..8dc901282 100644 --- a/src/Sidekick.Common/Keybinds/KeybindHandler.cs +++ b/src/Sidekick.Common/Keybinds/KeybindHandler.cs @@ -30,7 +30,7 @@ private void OnSettingsChanged() [ ]; - public InitializationPriority Priority => InitializationPriority.Low; + public int Priority => 0; public async Task Initialize() { diff --git a/src/Sidekick.Common/Localization/UiLanguageProvider.cs b/src/Sidekick.Common/Localization/UiLanguageProvider.cs index 3daf9c32c..0832b5715 100644 --- a/src/Sidekick.Common/Localization/UiLanguageProvider.cs +++ b/src/Sidekick.Common/Localization/UiLanguageProvider.cs @@ -1,5 +1,4 @@ using System.Globalization; -using Sidekick.Common.Initialization; using Sidekick.Common.Settings; namespace Sidekick.Common.Localization; @@ -20,7 +19,7 @@ public class UiLanguageProvider(ISettingsService settingsService) : IUiLanguageP private string? currentLanguage; /// - public InitializationPriority Priority => InitializationPriority.Critical; + public int Priority => 0; /// public async Task Initialize() diff --git a/src/Sidekick.Common/ServiceCollectionExtensions.cs b/src/Sidekick.Common/ServiceCollectionExtensions.cs index 273d0a639..d156610d6 100644 --- a/src/Sidekick.Common/ServiceCollectionExtensions.cs +++ b/src/Sidekick.Common/ServiceCollectionExtensions.cs @@ -6,7 +6,6 @@ using Sidekick.Common.Browser; using Sidekick.Common.Cache; using Sidekick.Common.Game.GameLogs; -using Sidekick.Common.Game.Items; using Sidekick.Common.Game.Languages; using Sidekick.Common.Initialization; using Sidekick.Common.Keybinds; @@ -33,19 +32,6 @@ public static IServiceCollection AddSidekickCommon( services.AddSingleton(); services.AddSidekickInitializableService(); - - // Validate ClassLanguage implements correct properties - var properties = typeof(ClassLanguage) - .GetProperties() - .Where(x => x.Name != "Prefix"); - foreach (var property in properties) - { - if (!Enum.IsDefined(enumType: typeof(Class), property.Name)) - { - throw new Exception($"ClassLanguage has a property {property.Name} that does not match any Class enum values."); - } - } - services.AddSidekickInitializableService(); return services.AddSidekickLogging(); @@ -96,6 +82,18 @@ public static IServiceCollection AddSidekickModule( return services; } + /// + /// Adds an initializable service to the application. + /// + /// The service collection to add an initializable service. + /// The type of service to add to the application. + /// The service collection. + public static IServiceCollection AddSidekickInitializableService(this IServiceCollection services) + where TService : class, IInitializableService + { + return services.AddSidekickInitializableService(); + } + /// /// Adds an initializable service to the application. /// diff --git a/src/Sidekick.Common/Sidekick.Common.csproj b/src/Sidekick.Common/Sidekick.Common.csproj index 1999a8855..aba896bdb 100644 --- a/src/Sidekick.Common/Sidekick.Common.csproj +++ b/src/Sidekick.Common/Sidekick.Common.csproj @@ -19,8 +19,4 @@ - - - - diff --git a/src/Sidekick.Mock/MockKeyboardProvider.cs b/src/Sidekick.Mock/MockKeyboardProvider.cs index e27b418ed..362f852da 100644 --- a/src/Sidekick.Mock/MockKeyboardProvider.cs +++ b/src/Sidekick.Mock/MockKeyboardProvider.cs @@ -1,13 +1,10 @@ -using System; -using System.Threading.Tasks; -using Sidekick.Common.Initialization; using Sidekick.Common.Platform; namespace Sidekick.Mock { public class MockKeyboardProvider : IKeyboardProvider { - public InitializationPriority Priority => InitializationPriority.Low; + public int Priority => 0; #pragma warning disable CS0067 diff --git a/src/Sidekick.Mock/MockProcessProvider.cs b/src/Sidekick.Mock/MockProcessProvider.cs index a89853ec7..1bd4126ab 100644 --- a/src/Sidekick.Mock/MockProcessProvider.cs +++ b/src/Sidekick.Mock/MockProcessProvider.cs @@ -1,8 +1,5 @@ #pragma warning disable CS0067 -using System; -using System.Threading.Tasks; -using Sidekick.Common.Initialization; using Sidekick.Common.Platform; namespace Sidekick.Mock @@ -11,14 +8,9 @@ public class MockProcessProvider : IProcessProvider { public string ClientLogPath => string.Empty; - public event Action? OnFocus; - - public event Action? OnBlur; - public bool IsPathOfExileInFocus => true; public bool IsSidekickInFocus => false; - - public InitializationPriority Priority => InitializationPriority.Low; + public int Priority => 0; public Task Initialize() { diff --git a/src/Sidekick.Modules.Settings/General/LeagueIdEditor.razor b/src/Sidekick.Modules.Settings/General/LeagueIdEditor.razor index 93d37af0b..4c4d15b88 100644 --- a/src/Sidekick.Modules.Settings/General/LeagueIdEditor.razor +++ b/src/Sidekick.Modules.Settings/General/LeagueIdEditor.razor @@ -1,17 +1,35 @@ @using Sidekick.Apis.Poe +@using Sidekick.Apis.Poe.Trade.Models +@using Sidekick.Common.Cache +@using Sidekick.Common.Extensions +@using Sidekick.Common.Game @using Sidekick.Common.Settings @using Sidekick.Modules.Settings.Localization - + +
+
+ +
+
+ +
+
@inject SettingsResources Resources @inject ISettingsService SettingsService @inject ILeagueProvider LeagueProvider +@inject ICacheProvider CacheProvider +@inject NavigationManager NavigationManager @code { + [Parameter] + public bool AutoRefresh { get; set; } = true; + private string? LeagueId { get; set; } private List Options { get; set; } = @@ -43,8 +61,30 @@ private async Task LeagueChanged(string? value) { + var currentGame = LeagueId.GetGameFromLeagueId(); + var newGame = value.GetGameFromLeagueId(); + LeagueId = value; await SettingsService.Set(SettingKeys.LeagueId, value); + + if (currentGame != newGame) + { + if (newGame == GameType.PathOfExile2) + { + await SettingsService.Set(SettingKeys.PriceCheckBulkCurrency, TradeCurrency.Exalted); + await SettingsService.Set(SettingKeys.PriceCheckItemCurrency, TradeCurrency.ChaosEquivalent); + } + else if (newGame == GameType.PathOfExile) + { + await SettingsService.Set(SettingKeys.PriceCheckBulkCurrency, TradeCurrency.Divine); + } + } + + if (AutoRefresh && currentGame != newGame) + { + await CacheProvider.Clear(); + NavigationManager.NavigateTo("/initialize"); + } } } diff --git a/src/Sidekick.Modules.Settings/Setup/Setup.razor b/src/Sidekick.Modules.Settings/Setup/Setup.razor index 173adc5b7..75de007b3 100644 --- a/src/Sidekick.Modules.Settings/Setup/Setup.razor +++ b/src/Sidekick.Modules.Settings/Setup/Setup.razor @@ -1,13 +1,10 @@ -@page "/setup" -@page "/" -@using System.Text @using System.Text.Json @using Sidekick.Apis.Poe @using Sidekick.Common.Cache +@using Sidekick.Common.Extensions @using Sidekick.Common.Platform @using Sidekick.Common.Settings @using Sidekick.Modules.Settings.General -@inherits Sidekick.Common.Ui.Views.SidekickView @@ -30,7 +27,7 @@ - + } @@ -62,6 +59,7 @@ @inject ILeagueProvider LeagueProvider @inject ICacheProvider CacheProvider @inject IStringLocalizer Resources +@inject NavigationManager NavigationManager @code { @@ -71,29 +69,22 @@ private bool HasError { get; set; } - public override SidekickViewType ViewType => SidekickViewType.Modal; - - public override int ViewHeight => 360; - protected override async Task OnInitializedAsync() { var leagues = await LeagueProvider.GetList(false); - var apiLeaguesHash = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(leagues))); - var settingsLeaguesHash = await SettingsService.GetString(SettingKeys.LeaguesHash); - if (!string.IsNullOrEmpty(settingsLeaguesHash) && apiLeaguesHash != settingsLeaguesHash) + var apiLeaguesHash = JsonSerializer.Serialize(leagues).GetDeterministicHashCode(); + var settingsLeaguesHash = await SettingsService.GetInt(SettingKeys.LeaguesHash); + if (apiLeaguesHash != settingsLeaguesHash) { NewLeagues = true; await CacheProvider.Clear(); } - await SettingsService.Set(SettingKeys.LeaguesHash, apiLeaguesHash); - // Check to see if we should run Setup first before running the rest of the initialization process var leagueId = await SettingsService.GetString(SettingKeys.LeagueId); - if (string.IsNullOrEmpty(leagueId) || leagues.All(x => x.Id != leagueId)) + if (string.IsNullOrEmpty(leagueId) || leagues.All(x => x.Id != leagueId) || NewLeagues) { RequiresSetup = true; - await CurrentView.Initialize(this); } else { @@ -118,6 +109,9 @@ return; } + var apiLeaguesHash = JsonSerializer.Serialize(leagues).GetDeterministicHashCode(); + await SettingsService.Set(SettingKeys.LeaguesHash, apiLeaguesHash); + NavigationManager.NavigateTo("/initialize"); } diff --git a/src/Sidekick.Modules.Settings/Setup/SetupPage.razor b/src/Sidekick.Modules.Settings/Setup/SetupPage.razor new file mode 100644 index 000000000..a7ac445b0 --- /dev/null +++ b/src/Sidekick.Modules.Settings/Setup/SetupPage.razor @@ -0,0 +1,19 @@ +@page "/setup" +@page "/" +@inherits Sidekick.Common.Ui.Views.SidekickView + + + +@code{ + + public override SidekickViewType ViewType => SidekickViewType.Modal; + + public override int ViewHeight => 360; + + protected override async Task OnInitializedAsync() + { + await CurrentView.Initialize(this); + await base.OnInitializedAsync(); + } + +} diff --git a/src/Sidekick.Modules.Trade/Components/Bulk/BulkResult.razor b/src/Sidekick.Modules.Trade/Components/Bulk/BulkResult.razor index 7ae9b3509..741478fbd 100644 --- a/src/Sidekick.Modules.Trade/Components/Bulk/BulkResult.razor +++ b/src/Sidekick.Modules.Trade/Components/Bulk/BulkResult.razor @@ -17,7 +17,7 @@ else if (PriceCheckService.BulkTradeResult != null) {
- @LeagueId +
@@ -59,11 +59,8 @@ else if (PriceCheckService.BulkTradeResult != null) @code { - private string? LeagueId { get; set; } - protected override async Task OnInitializedAsync() { - LeagueId = await SettingsService.GetString(SettingKeys.LeagueId); PriceCheckService.LoadingChanged += StateHasChanged; await base.OnInitializedAsync(); } diff --git a/src/Sidekick.Modules.Trade/Components/Filters/ClassFilter.razor b/src/Sidekick.Modules.Trade/Components/Filters/ClassFilter.razor index 7b624fdad..f360adb92 100644 --- a/src/Sidekick.Modules.Trade/Components/Filters/ClassFilter.razor +++ b/src/Sidekick.Modules.Trade/Components/Filters/ClassFilter.razor @@ -1,3 +1,4 @@ +@using Sidekick.Apis.Poe.Metadata @using Sidekick.Common.Game.Items @using Sidekick.Common.Game.Languages @using Sidekick.Modules.Trade.Localization @@ -5,10 +6,10 @@ {
- - Resources @inject PriceCheckService PriceCheckService +@inject IMetadataProvider MetadataProvider @code { - private bool Visible => PriceCheckService.Item?.Metadata.Rarity is Rarity.Rare or Rarity.Magic or Rarity.Normal && PriceCheckService.Item.Header.Class != Class.Undefined && !string.IsNullOrEmpty(ClassLabel); + private bool Visible => PriceCheckService.Item?.Metadata.Rarity is Rarity.Rare or Rarity.Magic or Rarity.Normal && !string.IsNullOrEmpty(PriceCheckService.Item.Header.ItemCategory) && !string.IsNullOrEmpty(ClassLabel); private string? ClassLabel { get; set; } @@ -38,19 +40,7 @@ return; } - var property = GameLanguageProvider - .Language?.Classes?.GetType() - .GetProperties() - .FirstOrDefault(x => x.Name == PriceCheckService.Item.Header.Class.ToString()); - - if (GameLanguageProvider.Language?.Classes == null) - { - return; - } - - ClassLabel = property - ?.GetValue(GameLanguageProvider.Language.Classes) - ?.ToString(); + ClassLabel = MetadataProvider.ApiItemCategories.FirstOrDefault(x => x.Id == PriceCheckService.Item?.Header.ItemCategory)?.Text; } private void CheckedChanged(bool value) @@ -62,11 +52,11 @@ if (value) { - PriceCheckService.PropertyFilters.Class = PriceCheckService.Item?.Header.Class; + PriceCheckService.PropertyFilters.ItemCategory = PriceCheckService.Item?.Header.ItemCategory; } else { - PriceCheckService.PropertyFilters.Class = null; + PriceCheckService.PropertyFilters.ItemCategory = null; } } diff --git a/src/Sidekick.Modules.Trade/Components/Items/ItemModifierLineComponent.razor b/src/Sidekick.Modules.Trade/Components/Items/ItemModifierLineComponent.razor index b6418bc0d..ead96cb1d 100644 --- a/src/Sidekick.Modules.Trade/Components/Items/ItemModifierLineComponent.razor +++ b/src/Sidekick.Modules.Trade/Components/Items/ItemModifierLineComponent.razor @@ -12,7 +12,7 @@ } - if (IsLastOfCategory && Category is ModifierCategory.Implicit or ModifierCategory.Enchant) + if (IsLastOfCategory && Category is ModifierCategory.Implicit or ModifierCategory.Enchant or ModifierCategory.Rune) { } diff --git a/src/Sidekick.Modules.Trade/Components/Items/ItemsResult.razor b/src/Sidekick.Modules.Trade/Components/Items/ItemsResult.razor index b3547a534..e79f69606 100644 --- a/src/Sidekick.Modules.Trade/Components/Items/ItemsResult.razor +++ b/src/Sidekick.Modules.Trade/Components/Items/ItemsResult.razor @@ -1,5 +1,4 @@ @using Sidekick.Apis.Poe.Trade.Models -@using Sidekick.Common.Settings @using Sidekick.Modules.Trade.Localization @using Sidekick.Modules.Trade.Components.Options @@ -12,7 +11,7 @@ @if (PriceCheckService.ItemTradeResult != null) {
- @LeagueId +
@@ -45,18 +44,14 @@ @implements IDisposable @inject TradeResources Resources -@inject ISettingsService SettingsService @inject PriceCheckService PriceCheckService @code { - private string? LeagueId { get; set; } - private bool FullyLoaded => (PriceCheckService.TradeItems?.Count ?? 0) == (PriceCheckService.ItemTradeResult?.Result?.Count ?? 0); protected override async Task OnInitializedAsync() { - LeagueId = await SettingsService.GetString(SettingKeys.LeagueId); PriceCheckService.LoadingChanged += StateHasChanged; await base.OnInitializedAsync(); } diff --git a/src/Sidekick.Modules.Trade/Components/Options/LeagueText.razor b/src/Sidekick.Modules.Trade/Components/Options/LeagueText.razor new file mode 100644 index 000000000..a1055ff34 --- /dev/null +++ b/src/Sidekick.Modules.Trade/Components/Options/LeagueText.razor @@ -0,0 +1,23 @@ +@using Sidekick.Apis.Poe +@using Sidekick.Common.Settings + +@Text + +@inject ISettingsService SettingsService +@inject ILeagueProvider LeagueProvider + +@code { + + private string? Text { get; set; } + + protected override async Task OnInitializedAsync() + { + var leagues = await LeagueProvider.GetList(true); + var leagueId = await SettingsService.GetString(SettingKeys.LeagueId); + Text = leagues.FirstOrDefault(x => x.Id == leagueId) + ?.Text; + + await base.OnInitializedAsync(); + } + +} diff --git a/src/Sidekick.Modules.Trade/Components/Options/OpenWebsiteLink.razor b/src/Sidekick.Modules.Trade/Components/Options/OpenWebsiteLink.razor index ac09bdcb0..b16c8d097 100644 --- a/src/Sidekick.Modules.Trade/Components/Options/OpenWebsiteLink.razor +++ b/src/Sidekick.Modules.Trade/Components/Options/OpenWebsiteLink.razor @@ -36,7 +36,7 @@ } else { - var uri = await TradeSearchService.GetTradeUri(QueryId); + var uri = await TradeSearchService.GetTradeUri(PriceCheckService.Item.Metadata.Game, QueryId); BrowserProvider.OpenUri(uri); } } diff --git a/src/Sidekick.Modules.Trade/Components/Prices/PriceNinjaComponent.razor b/src/Sidekick.Modules.Trade/Components/Prices/PriceNinjaComponent.razor index 77934e1b8..0247389d8 100644 --- a/src/Sidekick.Modules.Trade/Components/Prices/PriceNinjaComponent.razor +++ b/src/Sidekick.Modules.Trade/Components/Prices/PriceNinjaComponent.razor @@ -1,10 +1,10 @@ @using Sidekick.Apis.PoeNinja -@using Sidekick.Apis.PoeNinja.Api @using Sidekick.Apis.PoeNinja.Models @using Sidekick.Common.Browser @using Sidekick.Common.Game.Items @using Sidekick.Common.Game.Items.AdditionalInformation; @using Sidekick.Modules.Trade.Localization +@using Sidekick.Common.Game @using ApexCharts @if (Loading) @@ -44,7 +44,7 @@ else if (Price != null) @if (Series != null) {
- + Series { get; set; } - private ApexChartOptions options { get; set; } + private List? Series { get; set; } + private ApexChartOptions? Options { get; set; } protected override async Task OnParametersSetAsync() { await base.OnParametersSetAsync(); + if (PriceCheckService.Item?.Metadata.Game == GameType.PathOfExile2) + { + return; + } + Loading = true; StateHasChanged(); @@ -96,13 +101,13 @@ else if (Price != null) if (Price?.SparkLine?.Data.All(x => x.HasValue) == true) { Series = Price.SparkLine.Data.Select((value, index) => new DataPoint { Index = index, Value = (decimal?)value }).ToList(); - options = new(); - options.PlotOptions = new() { Line = new() { IsSlopeChart = true } }; - options.Tooltip = new() { Enabled = false }; - options.Chart.Sparkline = new() { Enabled = true }; - options.Markers = new() { Size = 0 }; - options.Chart.Height = 40; - options.Stroke = new() { Curve = Curve.Smooth, Width = 2, Colors = new() { "#8888ff" } }; + Options = new(); + Options.PlotOptions = new() { Line = new() { IsSlopeChart = true } }; + Options.Tooltip = new() { Enabled = false }; + Options.Chart.Sparkline = new() { Enabled = true }; + Options.Markers = new() { Size = 0 }; + Options.Chart.Height = 40; + Options.Stroke = new() { Curve = Curve.Smooth, Width = 2, Colors = new() { "#8888ff" } }; } Loading = false; diff --git a/src/Sidekick.Modules.Trade/Components/Prices/PricePredictionComponent.razor b/src/Sidekick.Modules.Trade/Components/Prices/PricePredictionComponent.razor index 5bfa6974e..d670d4fe6 100644 --- a/src/Sidekick.Modules.Trade/Components/Prices/PricePredictionComponent.razor +++ b/src/Sidekick.Modules.Trade/Components/Prices/PricePredictionComponent.razor @@ -1,5 +1,6 @@ @using Sidekick.Apis.PoePriceInfo @using Sidekick.Apis.PoePriceInfo.Models +@using Sidekick.Common.Game @using Sidekick.Common.Game.Items @using Sidekick.Common.Settings @using Sidekick.Modules.Trade.Localization @@ -45,6 +46,11 @@ else if (Prediction != null && (Prediction.Min != 0 || Prediction.Max != 0)) { await base.OnParametersSetAsync(); + if (PriceCheckService.Item?.Metadata.Game == GameType.PathOfExile2) + { + return; + } + if (PriceCheckService.Item == null || !await SettingsService.GetBool(SettingKeys.PriceCheckPredictionEnabled)) { return; diff --git a/src/Sidekick.Modules.Trade/PriceCheckService.cs b/src/Sidekick.Modules.Trade/PriceCheckService.cs index 9de857109..d1206e9ae 100644 --- a/src/Sidekick.Modules.Trade/PriceCheckService.cs +++ b/src/Sidekick.Modules.Trade/PriceCheckService.cs @@ -4,6 +4,7 @@ using Sidekick.Apis.Poe.Trade.Models; using Sidekick.Apis.Poe.Trade.Results; using Sidekick.Common.Extensions; +using Sidekick.Common.Game; using Sidekick.Common.Game.Items; using Sidekick.Common.Settings; @@ -115,7 +116,7 @@ public async Task ItemSearch() public async Task LoadMoreItems() { - if (IsLoading || ItemTradeResult?.Result == null || ItemTradeResult?.Id == null) + if (IsLoading || ItemTradeResult?.Result == null || ItemTradeResult?.Id == null || Item == null) { return; } @@ -132,7 +133,7 @@ public async Task LoadMoreItems() IsLoading = true; LoadingChanged?.Invoke(); - var result = await tradeSearchService.GetResults(ItemTradeResult.Id, ids, PseudoFilters); + var result = await tradeSearchService.GetResults(Item.Metadata.Game, ItemTradeResult.Id, ids, PseudoFilters); TradeItems?.AddRange(result); IsLoading = false; diff --git a/src/Sidekick.Modules.Wealth/Components/WealthControl.razor b/src/Sidekick.Modules.Wealth/Components/WealthControl.razor index aecff0e1d..e27386e30 100644 --- a/src/Sidekick.Modules.Wealth/Components/WealthControl.razor +++ b/src/Sidekick.Modules.Wealth/Components/WealthControl.razor @@ -1,4 +1,5 @@ @using Sidekick.Apis.Poe.Clients +@using Sidekick.Apis.Poe.Clients.Models @using Sidekick.Apis.Poe.Clients.States; @if (WealthParser.IsRunning()) { @@ -8,7 +9,7 @@
API Status
- @switch (ApiStateProvider.Get(ClientNames.POECLIENT)) + @switch (ApiStateProvider.Get(ClientNames.PoeClient)) { case ApiState.Working:
Working
diff --git a/src/Sidekick.Modules.Wealth/WealthParser.cs b/src/Sidekick.Modules.Wealth/WealthParser.cs index e87e1215e..8da7d1533 100644 --- a/src/Sidekick.Modules.Wealth/WealthParser.cs +++ b/src/Sidekick.Modules.Wealth/WealthParser.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Sidekick.Apis.Poe.Clients; +using Sidekick.Apis.Poe.Clients.Exceptions; using Sidekick.Apis.Poe.Stash; using Sidekick.Apis.Poe.Stash.Models; using Sidekick.Apis.PoeNinja; diff --git a/tests/Sidekick.Apis.Poe.Tests/Collections.cs b/tests/Sidekick.Apis.Poe.Tests/Collections.cs index 27b75151e..b470dc183 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Collections.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Collections.cs @@ -1,7 +1,11 @@ -namespace Sidekick.Apis.Poe.Tests +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] + +namespace Sidekick.Apis.Poe.Tests; + +public static class Collections { - public static class Collections - { - public const string Mediator = "Parser"; - } + public const string Poe1Parser = "Poe1Parser"; + public const string Poe2Parser = "Poe2Parser"; } diff --git a/tests/Sidekick.Apis.Poe.Tests/ItemExtensions.cs b/tests/Sidekick.Apis.Poe.Tests/ItemExtensions.cs index 67fa575e3..20ceac396 100644 --- a/tests/Sidekick.Apis.Poe.Tests/ItemExtensions.cs +++ b/tests/Sidekick.Apis.Poe.Tests/ItemExtensions.cs @@ -14,10 +14,8 @@ public static void AssertHasModifier(this Item actual, ModifierCategory expected Modifier = modifier, })); - var actualModifier = modifiers.FirstOrDefault(x => expectedText == x.Modifier.Text); - Assert.Equal(expectedText, actualModifier?.Modifier.Text); - Assert.Equal(expectedCategory, actualModifier?.Modifier.Category); - + var actualModifier = modifiers.FirstOrDefault(x => expectedCategory == x.Modifier.Category && expectedText == x.Modifier.Text); + Assert.NotNull(actualModifier); Assert.True(actualModifier?.Line.Values.Count == expectedValues.Length); for (var i = 0; i < expectedValues.Length; i++) @@ -26,27 +24,6 @@ public static void AssertHasModifier(this Item actual, ModifierCategory expected } } - public static void AssertHasAlternateModifier(this Item actual, ModifierCategory expectedCategory, string expectedText, params double[] expectedValues) - { - var modifiers = actual.ModifierLines - .SelectMany(line => line.Modifiers.Select(modifier => new - { - Line = line, - Modifier = modifier, - })); - - var actualModifier = modifiers.FirstOrDefault(x => expectedCategory == x.Modifier.Category && expectedText == x.Modifier.Text); - Assert.Equal(expectedText, actualModifier?.Modifier.Text); - Assert.Equal(expectedCategory, actualModifier?.Modifier.Category); - - Assert.True(actualModifier?.Line.Values.Count >= expectedValues.Length); - - for (var i = 0; i < expectedValues.Length; i++) - { - Assert.Equal(expectedValues[i], actualModifier.Line.Values[i]); - } - } - public static void AssertHasPseudoModifier(this Item actual, string expectedText, double? expectedValue = null) { var actualModifier = actual.PseudoModifiers.FirstOrDefault(x => expectedText == x.Text); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/AfflictionParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Parser/AfflictionParsing.cs deleted file mode 100644 index ddf10d66e..000000000 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/AfflictionParsing.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Sidekick.Common.Game.Items; -using Xunit; - -namespace Sidekick.Apis.Poe.Tests.Parser -{ - [Collection(Collections.Mediator)] - public class AfflictionParsing - { - private readonly IItemParser parser; - - public AfflictionParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } - - [Fact] - public void BulbonicTrail() - { -// var actual = parser.ParseItem(@"Item Class: Charms -//Rarity: Magic -//Corvine Charm of the Trickster -//-------- -//Item Level: 70 -//-------- -//Recover 2% of Life on Kill -//Recover 2% of Energy Shield on Kill -//Recover 2% of Mana on Kill -//-------- -//Place into an allocated Charm Socket on the Wildwood Primalist Passive Skill Tree. Right click to remove from the Socket. -//-------- -//Unmodifiable -//"); - -// Assert.Equal(Class.AfflictionCharms, actual.Header.Class); -// Assert.Equal(Category.Affliction, actual.Metadata.Category); -// Assert.Equal(Rarity.Magic, actual.Metadata.Rarity); -// Assert.Equal("Corvine Charm", actual.Metadata.Type); - } - } -} diff --git a/tests/Sidekick.Apis.Poe.Tests/Modifiers/InvariantModifierProviderTests.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Modifiers/InvariantModifierProviderTests.cs similarity index 72% rename from tests/Sidekick.Apis.Poe.Tests/Modifiers/InvariantModifierProviderTests.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Modifiers/InvariantModifierProviderTests.cs index 1f85aa065..f927165c8 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Modifiers/InvariantModifierProviderTests.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Modifiers/InvariantModifierProviderTests.cs @@ -1,17 +1,12 @@ using Sidekick.Apis.Poe.Modifiers; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Modifiers { - [Collection(Collections.Mediator)] - public class InvariantModifierProviderTests + [Collection(Collections.Poe1Parser)] + public class InvariantModifierProviderTests(ParserFixture fixture) { - private readonly IInvariantModifierProvider provider; - - public InvariantModifierProviderTests(ParserFixture fixture) - { - provider = fixture.InvariantModifierProvider; - } + private readonly IInvariantModifierProvider provider = fixture.InvariantModifierProvider; [Fact] public void ClusterJewelSmallPassiveCountModifierIdIsDefined() diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/AbyssParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/AbyssParsing.cs similarity index 82% rename from tests/Sidekick.Apis.Poe.Tests/Parser/AbyssParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/AbyssParsing.cs index c827e14f4..7282e95ff 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/AbyssParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/AbyssParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class AbyssParsing + [Collection(Collections.Poe1Parser)] + public class AbyssParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public AbyssParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void BulbonicTrail() @@ -42,7 +37,7 @@ Triggers Level 20 Death Walk when Equipped Even the dead serve the Lightless. "); - Assert.Equal(Class.Boots, actual.Header.Class); + Assert.Equal("armour.boots", actual.Header.ItemCategory); Assert.Equal(Category.Armour, actual.Metadata.Category); Assert.Equal(Rarity.Unique, actual.Metadata.Rarity); Assert.Equal("Bubonic Trail", actual.Metadata.Name); @@ -76,7 +71,7 @@ Place into an Abyssal Socket on an Item or into an allocated Jewel Socket on the Note: ~price 1 alch "); - Assert.Equal(Class.AbyssJewel, actual.Header.Class); + Assert.Equal("jewel.abyss", actual.Header.ItemCategory); Assert.Equal(Rarity.Rare, actual.Metadata.Rarity); Assert.Equal(Category.Jewel, actual.Metadata.Category); Assert.Equal("Hypnotic Eye Jewel", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/AscendancyParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/AscendancyParsing.cs similarity index 81% rename from tests/Sidekick.Apis.Poe.Tests/Parser/AscendancyParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/AscendancyParsing.cs index 6f6ee63f5..d617a8537 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/AscendancyParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/AscendancyParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class AscendancyParsing + [Collection(Collections.Poe1Parser)] + public class AscendancyParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public AscendancyParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseEnchantWithAdditionalProjectiles() @@ -58,7 +53,7 @@ but justice favours only the truly worthy. Travel to the Aspirants' Plaza and spend this item to open the Eternal Labyrinth of Fortune. You must have completed the six different Trials of Ascendancy found in Maps in order to access this area. "); - Assert.Equal(Class.MapFragments, actual.Header.Class); + Assert.Equal("map.fragment", actual.Header.ItemCategory); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal(Category.Map, actual.Metadata.Category); Assert.Equal("Tribute to the Goddess", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/BeastParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BeastParsing.cs similarity index 90% rename from tests/Sidekick.Apis.Poe.Tests/Parser/BeastParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BeastParsing.cs index 6ca2e24f0..50e85b992 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/BeastParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BeastParsing.cs @@ -1,9 +1,9 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] + [Collection(Collections.Poe1Parser)] public class BeastParsing(ParserFixture fixture) { private readonly IItemParser parser = fixture.Parser; @@ -30,7 +30,7 @@ Aspect of the Hellion Right-click to add this to your bestiary. "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Rare, actual.Metadata.Rarity); Assert.Equal(Category.ItemisedMonster, actual.Metadata.Category); Assert.Null(actual.Metadata.Name); @@ -84,7 +84,7 @@ Summons Apes from Trees Right-click to add this to your bestiary. "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Rare, actual.Metadata.Rarity); Assert.Equal(Category.ItemisedMonster, actual.Metadata.Category); Assert.Equal("Farric Chieftain", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/BetrayalParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BetrayalParsing.cs similarity index 69% rename from tests/Sidekick.Apis.Poe.Tests/Parser/BetrayalParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BetrayalParsing.cs index 076f69fd6..bf8143842 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/BetrayalParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BetrayalParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class BetrayalParsing + [Collection(Collections.Poe1Parser)] + public class BetrayalParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public BetrayalParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void RustedReliquaryScarab() @@ -30,7 +25,7 @@ Can be used in a personal Map Device to add modifiers to a Map. Note: ~b/o .50 chaos "); - Assert.Equal(Class.MapFragments, actual.Header.Class); + Assert.Equal("map.fragment", actual.Header.ItemCategory); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal(Category.Map, actual.Metadata.Category); Assert.Equal("Rusted Reliquary Scarab", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/BlightParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BlightParsing.cs similarity index 87% rename from tests/Sidekick.Apis.Poe.Tests/Parser/BlightParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BlightParsing.cs index 1c56d2778..e7a842ed2 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/BlightParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BlightParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class BlightParsing + [Collection(Collections.Poe1Parser)] + public class BlightParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public BlightParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseBlightedMap() @@ -33,7 +28,7 @@ Natural inhabitants of this area have been removed (implicit) "); Assert.Equal(Category.Map, actual.Metadata.Category); - Assert.Equal(Class.Maps, actual.Header.Class); + Assert.Equal("map", actual.Header.ItemCategory); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal("Blighted Atoll Map", actual.Metadata.Type); Assert.Equal(14, actual.Properties.MapTier); @@ -62,7 +57,7 @@ Natural inhabitants of this area have been removed (implicit) "); Assert.Equal(Category.Map, actual.Metadata.Category); - Assert.Equal(Class.Maps, actual.Header.Class); + Assert.Equal("map", actual.Header.ItemCategory); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal("Blighted Shore Map", actual.Metadata.Type); Assert.Equal(6, actual.Properties.MapTier); @@ -84,7 +79,7 @@ Shift click to unstack. Note: ~price 1 blessed "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Clear Oil", actual.Metadata.Type); @@ -119,7 +114,7 @@ Players have 20% less Recovery Rate of Life and Energy Shield Travel to this Map by using it in a personal Map Device.Maps can only be used once. "); - Assert.Equal(Class.Maps, actual.Header.Class); + Assert.Equal("map", actual.Header.ItemCategory); Assert.Equal(Rarity.Rare, actual.Metadata.Rarity); Assert.Equal(Category.Map, actual.Metadata.Category); Assert.Equal("Blighted Spider Forest Map", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/BodyArmourParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BodyArmourParsing.cs similarity index 90% rename from tests/Sidekick.Apis.Poe.Tests/Parser/BodyArmourParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BodyArmourParsing.cs index 1ae117025..a25d134f4 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/BodyArmourParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BodyArmourParsing.cs @@ -1,18 +1,12 @@ -using System.Linq; using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class BodyArmourParsing + [Collection(Collections.Poe1Parser)] + public class BodyArmourParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public BodyArmourParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseSixLinkUniqueBodyArmor() @@ -108,7 +102,7 @@ You gain an Endurance Charge on Kill Note: ~price 2 chaos "); - Assert.Equal(Class.BodyArmours, actual.Header.Class); + Assert.Equal("armour.chest", actual.Header.ItemCategory); Assert.Equal(Rarity.Unique, actual.Metadata.Rarity); Assert.Equal(Category.Armour, actual.Metadata.Category); Assert.Equal("Daresso's Defiance", actual.Metadata.Name); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/BootParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BootParsing.cs similarity index 74% rename from tests/Sidekick.Apis.Poe.Tests/Parser/BootParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BootParsing.cs index 5dbeec365..514dd4002 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/BootParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BootParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class BootParsing + [Collection(Collections.Poe1Parser)] + public class BootParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public BootParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseFracturedItem() diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/BreachParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BreachParsing.cs similarity index 62% rename from tests/Sidekick.Apis.Poe.Tests/Parser/BreachParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BreachParsing.cs index 81b3ea983..1ec822297 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/BreachParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BreachParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class BreachParsing + [Collection(Collections.Poe1Parser)] + public class BreachParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public BreachParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void SplinterOfTul() @@ -26,7 +21,7 @@ Combine 100 Splinters to create Tul's Breachstone. Shift click to unstack. "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Splinter of Tul", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/ChargedCompassParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/ChargedCompassParsing.cs similarity index 66% rename from tests/Sidekick.Apis.Poe.Tests/Parser/ChargedCompassParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/ChargedCompassParsing.cs index b51f29f3b..341d3623f 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/ChargedCompassParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/ChargedCompassParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class ChargedCompassParsing + [Collection(Collections.Poe1Parser)] + public class ChargedCompassParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public ChargedCompassParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseNikoModifier() @@ -28,7 +23,7 @@ 3 uses remaining (enchant) Right click on this item then left click on a Voidstone to apply the itemised Sextant Modifier to the Voidstone. "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/DeliriumParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/DeliriumParsing.cs similarity index 78% rename from tests/Sidekick.Apis.Poe.Tests/Parser/DeliriumParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/DeliriumParsing.cs index 401353b42..a67b76b38 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/DeliriumParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/DeliriumParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class DeliriumParsing + [Collection(Collections.Poe1Parser)] + public class DeliriumParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public DeliriumParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void SimulacrumSplinter() @@ -28,7 +23,7 @@ Shift click to unstack. Note: ~price .5 chaos "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Simulacrum Splinter", actual.Metadata.Type); @@ -56,7 +51,7 @@ 1 Added Passive Skill is Readiness Note: ~b/o 1 chance "); - Assert.Equal(Class.Jewel, actual.Header.Class); + Assert.Equal("jewel", actual.Header.ItemCategory); Assert.Equal(Rarity.Rare, actual.Metadata.Rarity); Assert.Equal(Category.Jewel, actual.Metadata.Category); Assert.Equal("Small Cluster Jewel", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/DelveParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/DelveParsing.cs similarity index 79% rename from tests/Sidekick.Apis.Poe.Tests/Parser/DelveParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/DelveParsing.cs index 4422e6f8b..ef545dbb8 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/DelveParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/DelveParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class DelveParsing + [Collection(Collections.Poe1Parser)] + public class DelveParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public DelveParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParsePotentChaoticResonator() @@ -32,7 +27,7 @@ All sockets must be filled with Fossils before this item can be used. Note: ~price 1 chaos "); - Assert.Equal(Class.DelveStackableSocketableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal("Potent Chaotic Resonator", actual.Metadata.Type); @@ -57,7 +52,7 @@ All sockets must be filled with Fossils before this item can be used. Note: ~price 4 chaos "); - Assert.Equal(Class.DelveStackableSocketableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Powerful Chaotic Resonator", actual.Metadata.Type); @@ -78,7 +73,7 @@ No Tagless modifiers Place in a Resonator to influence item crafting. "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Opulent Fossil", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/DivinationCardParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/DivinationCardParsing.cs similarity index 82% rename from tests/Sidekick.Apis.Poe.Tests/Parser/DivinationCardParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/DivinationCardParsing.cs index 3be6022c0..376910d95 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/DivinationCardParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/DivinationCardParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class DivinationCardParsing + [Collection(Collections.Poe1Parser)] + public class DivinationCardParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public DivinationCardParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseSaintTreasure() @@ -27,7 +22,7 @@ 2x Exalted Orb Publicly, he lived a pious and chaste life of poverty. Privately, tithes and tributes made him and his lascivious company very comfortable indeed. "); - Assert.Equal(Class.DivinationCard, actual.Header.Class); + Assert.Equal("card", actual.Header.ItemCategory); Assert.Equal(Category.DivinationCard, actual.Metadata.Category); Assert.Equal(Rarity.DivinationCard, actual.Metadata.Rarity); Assert.Null(actual.Metadata.Name); @@ -48,7 +43,7 @@ Shaper Item -------- Though they were a pack of elite combatants, the Emperor's royal guards were not ready to face one of his notorious parties."); - Assert.Equal(Class.DivinationCard, actual.Header.Class); + Assert.Equal("card", actual.Header.ItemCategory); Assert.Equal(Category.DivinationCard, actual.Metadata.Category); Assert.Equal(Rarity.DivinationCard, actual.Metadata.Rarity); Assert.Null(actual.Metadata.Name); @@ -77,7 +72,7 @@ Offering to the Goddess Note: ~price 1 blessed "); - Assert.Equal(Class.DivinationCard, actual.Header.Class); + Assert.Equal("card", actual.Header.ItemCategory); Assert.Equal(Rarity.DivinationCard, actual.Metadata.Rarity); Assert.Equal(Category.DivinationCard, actual.Metadata.Category); Assert.Equal("Boon of Justice", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/EssenceParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/EssenceParsing.cs similarity index 74% rename from tests/Sidekick.Apis.Poe.Tests/Parser/EssenceParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/EssenceParsing.cs index 6c9ae46ac..7ebc59501 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/EssenceParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/EssenceParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class EssenceParsing + [Collection(Collections.Poe1Parser)] + public class EssenceParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public EssenceParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseWeepingEssenceOfAnger() @@ -38,7 +33,7 @@ Shift click to unstack. Note: ~price 1 fusing "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Weeping Essence of Anger", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/ExpeditionParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/ExpeditionParsing.cs similarity index 82% rename from tests/Sidekick.Apis.Poe.Tests/Parser/ExpeditionParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/ExpeditionParsing.cs index 3270b90d1..1e7aab235 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/ExpeditionParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/ExpeditionParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class ExpeditionParsing + [Collection(Collections.Poe1Parser)] + public class ExpeditionParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public ExpeditionParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseMagicLogbook() @@ -50,7 +45,7 @@ Area contains 8 additional Chest Markers (implicit) Take this item to Dannig in your Hideout to open portals to an expedition. "); - Assert.Equal(Class.Logbooks, actual.Header.Class); + Assert.Equal("logbook", actual.Header.ItemCategory); Assert.Equal(Rarity.Magic, actual.Metadata.Rarity); Assert.Equal(Category.Logbook, actual.Metadata.Category); Assert.Equal("Expedition Logbook", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/FlaskParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/FlaskParsing.cs similarity index 86% rename from tests/Sidekick.Apis.Poe.Tests/Parser/FlaskParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/FlaskParsing.cs index 99e6a8c74..b0563df8d 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/FlaskParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/FlaskParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class FlaskParsing + [Collection(Collections.Poe1Parser)] + public class FlaskParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public FlaskParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseSanctifiedManaFlask() @@ -36,7 +31,7 @@ Currently has 0 Charges Right click to drink. Can only hold charges while in belt. Refills as you kill monsters. "); - Assert.Equal(Class.ManaFlasks, actual.Header.Class); + Assert.Equal("flask", actual.Header.ItemCategory); Assert.Equal(Category.Flask, actual.Metadata.Category); Assert.Equal(Rarity.Magic, actual.Metadata.Rarity); Assert.Equal("Sanctified Mana Flask", actual.Metadata.Type); @@ -63,7 +58,7 @@ Currently has 0 Charges Right click to drink. Can only hold charges while in belt. Refills as you kill monsters. "); - Assert.Equal(Class.LifeFlasks, actual.Header.Class); + Assert.Equal("flask", actual.Header.ItemCategory); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal(Category.Flask, actual.Metadata.Category); Assert.Equal("Hallowed Life Flask", actual.Metadata.Type); @@ -90,7 +85,7 @@ Currently has 0 Charges Right click to drink. Can only hold charges while in belt. Refills as you kill monsters. "); - Assert.Equal(Class.HybridFlasks, actual.Header.Class); + Assert.Equal("flask", actual.Header.ItemCategory); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal(Category.Flask, actual.Metadata.Category); Assert.Equal("Sacred Hybrid Flask", actual.Metadata.Type); @@ -118,7 +113,7 @@ 6 Second Cooldown when Deactivated Right click to activate. Only one Tincture in your belt can be active at a time. Mana Burn causes you to lose 1% of your maximum Mana per stack per second. Can be deactivated manually, or will automatically deactivate when you reach 0 Mana. "); - Assert.Equal(Class.AfflictionTinctures, actual.Header.Class); + Assert.Equal("tincture", actual.Header.ItemCategory); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal(Category.Tincture, actual.Metadata.Category); Assert.Equal("Poisonberry Tincture", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/GemParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/GemParsing.cs similarity index 95% rename from tests/Sidekick.Apis.Poe.Tests/Parser/GemParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/GemParsing.cs index 1ad7228e4..31e59ad7b 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/GemParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/GemParsing.cs @@ -1,9 +1,9 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] + [Collection(Collections.Poe1Parser)] public class GemParsing(ParserFixture fixture) { private readonly IItemParser parser = fixture.Parser; @@ -135,7 +135,7 @@ Arcane Surge lasts 4 seconds This is a Support Gem. It does not grant a bonus to your character, but to skills in sockets connected to it. Place into an item socket connected to a socket containing the Skill Gem you wish to augment. Right click to remove from a socket. "); - Assert.Equal(Class.SupportSkillGems, actual.Header.Class); + Assert.Equal("gem.supportgem", actual.Header.ItemCategory); Assert.Equal(Rarity.Gem, actual.Metadata.Rarity); Assert.Equal(Category.Gem, actual.Metadata.Category); Assert.Equal("Arcane Surge Support", actual.Metadata.Type); @@ -173,7 +173,7 @@ Pulses every 0.40 seconds Place into an item socket of the right colour to gain this skill. Right click to remove from a socket. "); - Assert.Equal(Class.ActiveSkillGems, actual.Header.Class); + Assert.Equal("gem.activegem", actual.Header.ItemCategory); Assert.Equal(Rarity.Gem, actual.Metadata.Rarity); Assert.Equal(Category.Gem, actual.Metadata.Category); Assert.Equal("Void Sphere", actual.Metadata.Type); @@ -213,7 +213,7 @@ Place into an item socket of the right colour to gain this skill. Right click to Transfigured "); - Assert.Equal(Class.ActiveSkillGems, actual.Header.Class); + Assert.Equal("gem.activegem", actual.Header.ItemCategory); Assert.Equal(Rarity.Gem, actual.Metadata.Rarity); Assert.Equal(Category.Gem, actual.Metadata.Category); Assert.Equal("Kinetic Blast of Clustering", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/GloveParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/GloveParsing.cs similarity index 82% rename from tests/Sidekick.Apis.Poe.Tests/Parser/GloveParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/GloveParsing.cs index 80b03a987..6633ff998 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/GloveParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/GloveParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class GloveParsing + [Collection(Collections.Poe1Parser)] + public class GloveParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public GloveParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseRareGloves() diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/HeistParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/HeistParsing.cs similarity index 85% rename from tests/Sidekick.Apis.Poe.Tests/Parser/HeistParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/HeistParsing.cs index 847cef4b7..a38ba7802 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/HeistParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/HeistParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class HeistParsing + [Collection(Collections.Poe1Parser)] + public class HeistParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public HeistParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void HeistTool() @@ -36,7 +31,7 @@ Level 2 in Deception Can only be equipped to Heist members. "); - Assert.Equal(Class.HeistTool, actual.Header.Class); + Assert.Equal("heistequipment.heisttool", actual.Header.ItemCategory); Assert.Equal(Rarity.Magic, actual.Metadata.Rarity); Assert.Equal(Category.HeistEquipment, actual.Metadata.Category); Assert.Equal("Basic Disguise Kit", actual.Metadata.Type); @@ -61,7 +56,7 @@ Level 2 in Any Job Can only be equipped to Heist members. "); - Assert.Equal(Class.HeistCloak, actual.Header.Class); + Assert.Equal("heistequipment.heistutility", actual.Header.ItemCategory); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal(Category.HeistEquipment, actual.Metadata.Category); Assert.Equal("Torn Cloak", actual.Metadata.Type); @@ -86,7 +81,7 @@ Level 2 in Any Job Can only be equipped to Heist members. "); - Assert.Equal(Class.HeistBrooch, actual.Header.Class); + Assert.Equal("heistequipment.heistreward", actual.Header.ItemCategory); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal(Category.HeistEquipment, actual.Metadata.Category); Assert.Equal("Silver Brooch", actual.Metadata.Type); @@ -118,7 +113,7 @@ Grants Level 10 Anger Skill Can only be equipped to Heist members. "); - Assert.Equal(Class.HeistGear, actual.Header.Class); + Assert.Equal("heistequipment.heistweapon", actual.Header.ItemCategory); Assert.Equal(Rarity.Rare, actual.Metadata.Rarity); Assert.Equal(Category.HeistEquipment, actual.Metadata.Category); Assert.Equal("Rough Sharpening Stone", actual.Metadata.Type); @@ -138,7 +133,7 @@ Such a gift will set me apart from all other suitors."" Can be exchanged with Faustus, the Fence in The Rogue Harbour "); - Assert.Equal(Class.HeistTarget, actual.Header.Class); + Assert.Equal("currency.heistobjective", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Golden Napuatzi Idol", actual.Metadata.Type); @@ -166,7 +161,7 @@ You must find the sculpture The Catch in a Smuggler's Den or Underbelly Blueprin Corrupted "); - Assert.Equal(Class.Trinkets, actual.Header.Class); + Assert.Equal("accessory.trinket", actual.Header.ItemCategory); Assert.Equal(Rarity.Rare, actual.Metadata.Rarity); Assert.Equal(Category.Accessory, actual.Metadata.Category); Assert.Equal("Thief's Trinket", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/HelmetParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/HelmetParsing.cs similarity index 91% rename from tests/Sidekick.Apis.Poe.Tests/Parser/HelmetParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/HelmetParsing.cs index ecaa041d8..44b046e8f 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/HelmetParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/HelmetParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class HelmetParsing + [Collection(Collections.Poe1Parser)] + public class HelmetParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public HelmetParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseBlightGuardian() diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/IncursionParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/IncursionParsing.cs similarity index 78% rename from tests/Sidekick.Apis.Poe.Tests/Parser/IncursionParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/IncursionParsing.cs index 722230121..6af20d513 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/IncursionParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/IncursionParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class IncursionParsing + [Collection(Collections.Poe1Parser)] + public class IncursionParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public IncursionParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void LocusOfCorruption() @@ -44,8 +39,8 @@ Can be used in a personal Map Device to open portals to the Temple of Atzoatl in Note: ~price 1.29 exalted "); + Assert.Equal("map.fragment", actual.Header.ItemCategory); Assert.Equal(Category.Map, actual.Metadata.Category); - Assert.Equal(Class.MiscMapItems, actual.Header.Class); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal("Chronicle of Atzoatl", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/JewelParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/JewelParsing.cs similarity index 91% rename from tests/Sidekick.Apis.Poe.Tests/Parser/JewelParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/JewelParsing.cs index bd453fa45..e31186419 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/JewelParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/JewelParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class JewelParsing + [Collection(Collections.Poe1Parser)] + public class JewelParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public JewelParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseJewelBlightCut() @@ -99,7 +94,7 @@ Viridian Jewel Place into an allocated Jewel Socket on the Passive Skill Tree. Right click to remove from the Socket. "); - Assert.Equal(Class.Jewel, actual.Header.Class); + Assert.Equal("jewel", actual.Header.ItemCategory); Assert.Equal(Rarity.Rare, actual.Metadata.Rarity); Assert.Equal(Category.Jewel, actual.Metadata.Category); Assert.Equal("Viridian Jewel", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/MapParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/MapParsing.cs similarity index 92% rename from tests/Sidekick.Apis.Poe.Tests/Parser/MapParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/MapParsing.cs index ed53c9b2d..b0eb50465 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/MapParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/MapParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class MapParsing + [Collection(Collections.Poe1Parser)] + public class MapParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public MapParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseArcadeMap() @@ -28,8 +23,8 @@ Arcade Map Travel to this Map by using it in a personal Map Device. Maps can only be used once. "); + Assert.Equal("map", actual.Header.ItemCategory); Assert.Equal(Category.Map, actual.Metadata.Category); - Assert.Equal(Class.Maps, actual.Header.Class); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal("Arcade Map", actual.Metadata.Type); Assert.Equal(15, actual.Properties.MapTier); @@ -65,8 +60,8 @@ Will they grant me strength or doom? Travel to this Map by using it in a personal Map Device. Maps can only be used once. "); + Assert.Equal("map", actual.Header.ItemCategory); Assert.Equal(Category.Map, actual.Metadata.Category); - Assert.Equal(Class.Maps, actual.Header.Class); Assert.Equal(Rarity.Unique, actual.Metadata.Rarity); Assert.Equal("Maelström of Chaos", actual.Metadata.Name); Assert.Equal("Atoll Map", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/OrbParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/OrbParsing.cs similarity index 66% rename from tests/Sidekick.Apis.Poe.Tests/Parser/OrbParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/OrbParsing.cs index 4ccffe7e4..cd5b34d9f 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/OrbParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/OrbParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class OrbParsing + [Collection(Collections.Poe1Parser)] + public class OrbParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public OrbParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ChaosOrb() @@ -29,7 +24,7 @@ Right click this item then left click a rare item to apply it. Note: ~b/o 2 blessed "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Chaos Orb", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/PantheonParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/PantheonParsing.cs similarity index 74% rename from tests/Sidekick.Apis.Poe.Tests/Parser/PantheonParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/PantheonParsing.cs index e11673861..cd4ca7779 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/PantheonParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/PantheonParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class PantheonParsing + [Collection(Collections.Poe1Parser)] + public class PantheonParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public PantheonParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void DivineVessel() @@ -34,7 +29,7 @@ and yet it always breaks free. Note: ~price 1 chaos "); - Assert.Equal(Class.MapFragments, actual.Header.Class); + Assert.Equal("map.fragment", actual.Header.ItemCategory); Assert.Equal(Rarity.Normal, actual.Metadata.Rarity); Assert.Equal(Category.Map, actual.Metadata.Category); Assert.Equal("Divine Vessel", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/RingParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/RingParsing.cs similarity index 86% rename from tests/Sidekick.Apis.Poe.Tests/Parser/RingParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/RingParsing.cs index 20d9acab9..76ed2140a 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/RingParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/RingParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class RingParsing + [Collection(Collections.Poe1Parser)] + public class RingParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public RingParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseBroodCircle() @@ -36,7 +31,7 @@ Adds 8 to 13 Physical Damage to Attacks Corrupted "); - Assert.Equal(Class.Ring, actual.Header.Class); + Assert.Equal("accessory.ring", actual.Header.ItemCategory); Assert.Equal(Category.Accessory, actual.Metadata.Category); Assert.Equal(Rarity.Rare, actual.Metadata.Rarity); Assert.Equal("Ruby Ring", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/RitualParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/RitualParsing.cs similarity index 80% rename from tests/Sidekick.Apis.Poe.Tests/Parser/RitualParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/RitualParsing.cs index 9b3ff6b94..85a7742a0 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/RitualParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/RitualParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class RitualParsing + [Collection(Collections.Poe1Parser)] + public class RitualParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public RitualParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void RitualSplinter() @@ -28,7 +23,7 @@ Shift click to unstack. Note: ~price 1 alch "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Ritual Splinter", actual.Metadata.Type); @@ -50,7 +45,7 @@ Ritual Vessel Note: ~price 8 chaos "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Ritual Vessel", actual.Metadata.Type); @@ -77,7 +72,7 @@ Right click this item to create this corpse. Note: ~price 3 chaos "); - Assert.Equal(Class.AfflictionCorpses, actual.Header.Class); + Assert.Equal("corpse", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Corpse, actual.Metadata.Category); Assert.Equal("Perfect Needle Horror", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/SanctumParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/SanctumParsing.cs similarity index 66% rename from tests/Sidekick.Apis.Poe.Tests/Parser/SanctumParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/SanctumParsing.cs index 9c8847984..39897cdbd 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/SanctumParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/SanctumParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class SanctumParsing + [Collection(Collections.Poe1Parser)] + public class SanctumParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public SanctumParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseFloor() @@ -31,7 +26,7 @@ Take this item to the Relic Altar in the Forbidden Sanctum to enter. "); Assert.Equal(Category.Sanctum, actual.Metadata.Category); - Assert.Equal(Class.SanctumResearch, actual.Header.Class); + Assert.Equal("sanctum.research", actual.Header.ItemCategory); Assert.Equal("Forbidden Tome", actual.Metadata.Type); Assert.Equal(83, actual.Properties.AreaLevel); } diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/UltimatumParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/UltimatumParsing.cs similarity index 69% rename from tests/Sidekick.Apis.Poe.Tests/Parser/UltimatumParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/UltimatumParsing.cs index fb7c6254e..45f257417 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/UltimatumParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/UltimatumParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class UltimatumParsing + [Collection(Collections.Poe1Parser)] + public class UltimatumParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public UltimatumParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void NoxiousCatalyst() @@ -28,7 +23,7 @@ Replaces other quality types Right click this item then left click a ring, amulet or belt to apply it. Has greater effect on lower-rarity jewellery. The maximum quality is 20%. "); - Assert.Equal(Class.StackableCurrency, actual.Header.Class); + Assert.Equal("currency", actual.Header.ItemCategory); Assert.Equal(Rarity.Currency, actual.Metadata.Rarity); Assert.Equal(Category.Currency, actual.Metadata.Category); Assert.Equal("Noxious Catalyst", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/Parser/WeaponParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/WeaponParsing.cs similarity index 92% rename from tests/Sidekick.Apis.Poe.Tests/Parser/WeaponParsing.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/WeaponParsing.cs index bec810efb..c98c5538a 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Parser/WeaponParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/WeaponParsing.cs @@ -1,17 +1,12 @@ using Sidekick.Common.Game.Items; using Xunit; -namespace Sidekick.Apis.Poe.Tests.Parser +namespace Sidekick.Apis.Poe.Tests.Poe1.Parser { - [Collection(Collections.Mediator)] - public class WeaponParsing + [Collection(Collections.Poe1Parser)] + public class WeaponParsing(ParserFixture fixture) { - private readonly IItemParser parser; - - public WeaponParsing(ParserFixture fixture) - { - parser = fixture.Parser; - } + private readonly IItemParser parser = fixture.Parser; [Fact] public void ParseUnidentifiedUnique() @@ -79,7 +74,7 @@ Crusader Item Assert.True(actual.Influences.Crusader); actual.AssertHasModifier(ModifierCategory.Implicit, "#% increased Spell Damage", 33); - actual.AssertHasAlternateModifier(ModifierCategory.Explicit, "Adds # to # Physical Damage (Local)", 10, 16); + actual.AssertHasModifier(ModifierCategory.Explicit, "Adds # to # Physical Damage (Local)", 10, 16); actual.AssertHasModifier(ModifierCategory.Explicit, "#% increased Fire Damage", 24); actual.AssertHasModifier(ModifierCategory.Explicit, "Attacks with this Weapon Penetrate #% Lightning Resistance", 10); } @@ -237,7 +232,7 @@ And tore out her heart. Note: ~price 40 chaos "); - Assert.Equal(Class.FishingRods, actual.Header.Class); + Assert.Equal("weapon.rod", actual.Header.ItemCategory); Assert.Equal(Rarity.Unique, actual.Metadata.Rarity); Assert.Equal(Category.Weapon, actual.Metadata.Category); Assert.Equal("Reefbane", actual.Metadata.Name); @@ -270,7 +265,7 @@ One Handed Mace -------- Hunter Item"); - Assert.Equal(Class.OneHandMaces, actual.Header.Class); + Assert.Equal("weapon.onemace", actual.Header.ItemCategory); Assert.Equal(Rarity.Rare, actual.Metadata.Rarity); Assert.Equal(Category.Weapon, actual.Metadata.Category); Assert.Equal("Ornate Mace", actual.Metadata.Type); diff --git a/tests/Sidekick.Apis.Poe.Tests/ParserFixture.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/ParserFixture.cs similarity index 89% rename from tests/Sidekick.Apis.Poe.Tests/ParserFixture.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/ParserFixture.cs index a94da6ceb..75b4c5756 100644 --- a/tests/Sidekick.Apis.Poe.Tests/ParserFixture.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/ParserFixture.cs @@ -14,11 +14,11 @@ using Sidekick.Modules.Settings; using Xunit; -namespace Sidekick.Apis.Poe.Tests +namespace Sidekick.Apis.Poe.Tests.Poe1 { public class ParserFixture : IAsyncLifetime { - private static Task? InitializationTask; + private static Task? initializationTask; public IInvariantModifierProvider InvariantModifierProvider { get; private set; } = null!; @@ -53,15 +53,15 @@ public async Task InitializeAsync() var settingsService = ctx.Services.GetRequiredService(); await settingsService.Set(SettingKeys.LanguageParser, "en"); await settingsService.Set(SettingKeys.LanguageUi, "en"); - await settingsService.Set(SettingKeys.LeagueId, "Standard"); + await settingsService.Set(SettingKeys.LeagueId, "poe1.Standard"); - if (InitializationTask == null) + if (initializationTask == null) { var serviceProvider = ctx.Services.GetRequiredService(); - InitializationTask = Initialize(serviceProvider); + initializationTask = Initialize(serviceProvider); } - await InitializationTask; + await initializationTask; Parser = ctx.Services.GetRequiredService(); InvariantModifierProvider = ctx.Services.GetRequiredService(); diff --git a/tests/Sidekick.Apis.Poe.Tests/ParserCollection.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Poe1Collection.cs similarity index 58% rename from tests/Sidekick.Apis.Poe.Tests/ParserCollection.cs rename to tests/Sidekick.Apis.Poe.Tests/Poe1/Poe1Collection.cs index e5b29e550..4e736be0d 100644 --- a/tests/Sidekick.Apis.Poe.Tests/ParserCollection.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Poe1Collection.cs @@ -1,9 +1,9 @@ using Xunit; -namespace Sidekick.Apis.Poe.Tests +namespace Sidekick.Apis.Poe.Tests.Poe1 { - [CollectionDefinition(Collections.Mediator)] - public class ParserCollection : ICollectionFixture + [CollectionDefinition(Collections.Poe1Parser)] + public class Poe1Collection : ICollectionFixture { // This class has no code, and is never created. Its purpose is simply // to be the place to apply [CollectionDefinition] and all the diff --git a/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/ArmourParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/ArmourParsing.cs new file mode 100644 index 000000000..7e3ce6333 --- /dev/null +++ b/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/ArmourParsing.cs @@ -0,0 +1,66 @@ +using Sidekick.Common.Game.Items; +using Xunit; + +namespace Sidekick.Apis.Poe.Tests.Poe2.Parser; + +[Collection(Collections.Poe2Parser)] +public class ArmourParsing(ParserFixture fixture) +{ + private readonly IItemParser parser = fixture.Parser; + + [Fact] + public void ParseThunderstep() + { + var actual = parser.ParseItem( + @"Item Class: Boots +Rarity: Unique +Thunderstep +Steeltoe Boots +-------- +Evasion Rating: 129 (augmented) +-------- +Requirements: +Level: 28 +Dex: 49 +-------- +Sockets: S +-------- +Item Level: 36 +-------- +22% increased Evasion Rating (enchant) +-------- ++12% to Fire Resistance (rune) +-------- +10% increased Movement Speed +41% increased Evasion Rating ++5% to Maximum Lightning Resistance ++33% to Lightning Resistance +-------- +Where legends tread, +the world hearkens. +-------- +Corrupted +"); + + Assert.Equal(Category.Armour, actual.Metadata.Category); + Assert.Equal(Rarity.Unique, actual.Metadata.Rarity); + Assert.Equal("armour.boots", actual.Header.ItemCategory); + Assert.Equal("Steeltoe Boots", actual.Metadata.Type); + Assert.Equal("Thunderstep", actual.Metadata.Name); + + Assert.Equal(129, actual.Properties.Evasion); + + Assert.Single(actual.Sockets); + + Assert.Equal(36, actual.Properties.ItemLevel); + + actual.AssertHasModifier(ModifierCategory.Enchant, "#% increased Evasion Rating", 22); + actual.AssertHasModifier(ModifierCategory.Rune, "#% to Fire Resistance", 12); + actual.AssertHasModifier(ModifierCategory.Explicit, "#% increased Movement Speed", 10); + actual.AssertHasModifier(ModifierCategory.Explicit, "#% increased Evasion Rating", 41); + actual.AssertHasModifier(ModifierCategory.Explicit, "#% to Maximum Lightning Resistance", 5); + actual.AssertHasModifier(ModifierCategory.Explicit, "#% to Lightning Resistance", 33); + + Assert.True(actual.Properties.Corrupted); + } +} diff --git a/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/GemParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/GemParsing.cs new file mode 100644 index 000000000..ce5cf0b91 --- /dev/null +++ b/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/GemParsing.cs @@ -0,0 +1,54 @@ +using Sidekick.Common.Game.Items; +using Xunit; + +namespace Sidekick.Apis.Poe.Tests.Poe2.Parser; + +[Collection(Collections.Poe2Parser)] +public class GemParsing(ParserFixture fixture) +{ + private readonly IItemParser parser = fixture.Parser; + + [Fact] + public void ParseUncutSpirit() + { + var actual = parser.ParseItem( + @"Rarity: Currency +Uncut Spirit Gem +-------- +Level: 14 +-------- +Item Level: 14 +-------- +Creates a Persistent Buff Skill Gem or Level an existing gem to Level 14 +-------- +Right Click to engrave a Persistent Buff Skill Gem. +"); + + Assert.Equal(Category.Gem, actual.Metadata.Category); + Assert.Equal("Uncut Spirit Gem", actual.Metadata.Type); + Assert.Null(actual.Metadata.Name); + Assert.Equal(14, actual.Properties.GemLevel); + } + + [Fact] + public void ParseSupport3() + { + var actual = parser.ParseItem( + @"Rarity: Currency +Uncut Support Gem +-------- +Level: 3 +-------- +Item Level: 3 +-------- +Creates a Support Gem up to level 3 +-------- +Right Click to engrave a Support Gem. +"); + + Assert.Equal(Category.Gem, actual.Metadata.Category); + Assert.Equal("Uncut Support Gem", actual.Metadata.Type); + Assert.Null(actual.Metadata.Name); + Assert.Equal(3, actual.Properties.GemLevel); + } +} diff --git a/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/SanctumParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/SanctumParsing.cs new file mode 100644 index 000000000..f7883d86e --- /dev/null +++ b/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/SanctumParsing.cs @@ -0,0 +1,37 @@ +using Sidekick.Common.Game.Items; +using Xunit; + +namespace Sidekick.Apis.Poe.Tests.Poe2.Parser; + +[Collection(Collections.Poe2Parser)] +public class SanctumParsing(ParserFixture fixture) +{ + private readonly IItemParser parser = fixture.Parser; + + [Fact] + public void ParseRelic() + { + var actual = parser.ParseItem( + @"Item Class: Relics +Rarity: Magic +Revitalising Urn Relic of Flowing +-------- +Item Level: 22 +-------- +Fountains have 6% chance to grant double Sacred Water +9% increased Honour restored +-------- +Place this item on the Relic Altar at the start of the Trial of the Sekhemas +"); + + Assert.Equal(Category.Sanctum, actual.Metadata.Category); + Assert.Equal(Rarity.Magic, actual.Metadata.Rarity); + Assert.Equal("sanctum.relic", actual.Header.ItemCategory); + Assert.Equal("Urn Relic", actual.Metadata.Type); + Assert.Null(actual.Metadata.Name); + + actual.AssertHasModifier(ModifierCategory.Sanctum, "Fountains have #% chance to grant double Sacred Water", 6); + actual.AssertHasModifier(ModifierCategory.Sanctum, "#% increased Honour restored", 9); + // Ass + } +} diff --git a/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/WeaponParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/WeaponParsing.cs new file mode 100644 index 000000000..65b39f4d2 --- /dev/null +++ b/tests/Sidekick.Apis.Poe.Tests/Poe2/Parser/WeaponParsing.cs @@ -0,0 +1,37 @@ +using Sidekick.Common.Game.Items; +using Xunit; + +namespace Sidekick.Apis.Poe.Tests.Poe2.Parser; + +[Collection(Collections.Poe2Parser)] +public class WeaponParsing(ParserFixture fixture) +{ + private readonly IItemParser parser = fixture.Parser; + + [Fact] + public void ParseStaff() + { + var actual = parser.ParseItem( + @"Item Class: Staves +Rarity: Magic +Chalybeous Ashen Staff of the Augur +-------- +Requirements: +Level: 58 +Int: 133 (unmet) +-------- +Item Level: 60 +-------- ++148 to maximum Mana ++20 to Intelligence +"); + + Assert.Equal(Category.Weapon, actual.Metadata.Category); + Assert.Equal("Ashen Staff", actual.Metadata.Type); + Assert.Null(actual.Metadata.Name); + Assert.Equal(60, actual.Properties.ItemLevel); + + actual.AssertHasModifier(ModifierCategory.Explicit, "# to maximum Mana", 148); + actual.AssertHasModifier(ModifierCategory.Explicit, "# to Intelligence", 20); + } +} diff --git a/tests/Sidekick.Apis.Poe.Tests/Poe2/ParserFixture.cs b/tests/Sidekick.Apis.Poe.Tests/Poe2/ParserFixture.cs new file mode 100644 index 000000000..548aa752a --- /dev/null +++ b/tests/Sidekick.Apis.Poe.Tests/Poe2/ParserFixture.cs @@ -0,0 +1,90 @@ +using Bunit; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Sidekick.Apis.Poe.Modifiers; +using Sidekick.Apis.PoeNinja; +using Sidekick.Apis.PoeWiki; +using Sidekick.Common; +using Sidekick.Common.Cache; +using Sidekick.Common.Database; +using Sidekick.Common.Initialization; +using Sidekick.Common.Settings; +using Sidekick.Mock; +using Sidekick.Modules.Settings; +using Xunit; + +namespace Sidekick.Apis.Poe.Tests.Poe2 +{ + public class ParserFixture : IAsyncLifetime + { + private static Task? initializationTask; + + public IInvariantModifierProvider InvariantModifierProvider { get; private set; } = null!; + + public IItemParser Parser { get; private set; } = null!; + + public Task DisposeAsync() + { + return Task.CompletedTask; + } + + public async Task InitializeAsync() + { + using var ctx = new TestContext(); + ctx.Services.AddLocalization(); + + ctx.Services + // Building blocks + .AddSidekickCommon() + .AddSidekickCommonDatabase() + + // Apis + .AddSidekickPoeApi() + .AddSidekickPoeNinjaApi() + .AddSidekickPoeWikiApi() + + // Modules + .AddSidekickSettings() + + // Mocks + .AddSidekickMocks(); + + var settingsService = ctx.Services.GetRequiredService(); + await settingsService.Set(SettingKeys.LanguageParser, "en"); + await settingsService.Set(SettingKeys.LanguageUi, "en"); + await settingsService.Set(SettingKeys.LeagueId, "poe2.Standard"); + + if (initializationTask == null) + { + var serviceProvider = ctx.Services.GetRequiredService(); + initializationTask = Initialize(serviceProvider); + } + + await initializationTask; + + Parser = ctx.Services.GetRequiredService(); + InvariantModifierProvider = ctx.Services.GetRequiredService(); + } + + private async Task Initialize(IServiceProvider serviceProvider) + { + var cache = serviceProvider.GetRequiredService(); + await cache.Clear(); + + var configuration = serviceProvider.GetRequiredService>(); + var logger = serviceProvider.GetRequiredService>(); + foreach (var serviceType in configuration.Value.InitializableServices) + { + var service = serviceProvider.GetRequiredService(serviceType); + if (service is not IInitializableService initializableService) + { + continue; + } + + logger.LogInformation($"[Initialization] Initializing {initializableService.GetType().FullName}"); + await initializableService.Initialize(); + } + } + } +} diff --git a/tests/Sidekick.Apis.Poe.Tests/Poe2/Poe2Collection.cs b/tests/Sidekick.Apis.Poe.Tests/Poe2/Poe2Collection.cs new file mode 100644 index 000000000..f095411a4 --- /dev/null +++ b/tests/Sidekick.Apis.Poe.Tests/Poe2/Poe2Collection.cs @@ -0,0 +1,12 @@ +using Xunit; + +namespace Sidekick.Apis.Poe.Tests.Poe2 +{ + [CollectionDefinition(Collections.Poe2Parser)] + public class Poe2Collection : ICollectionFixture + { + // This class has no code, and is never created. Its purpose is simply + // to be the place to apply [CollectionDefinition] and all the + // ICollectionFixture<> interfaces. + } +} diff --git a/tests/Sidekick.Apis.Poe.Tests/xunit.runner.json b/tests/Sidekick.Apis.Poe.Tests/xunit.runner.json new file mode 100644 index 000000000..50880bf7c --- /dev/null +++ b/tests/Sidekick.Apis.Poe.Tests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "parallelizeTestCollections": false +}