From 84e730838b3af9aa9a0cfbac8315acd18040e447 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 3 Aug 2023 01:51:16 +0500 Subject: [PATCH] Add a new .editorconfig and reformat code (#76) *I'll start working on features and bugfixes after this PR, I promise* very short summary: - no more braceless statements - braces are on new lines now - `sealed` on everything that can be `sealed` - no more awkwardly looking alignment of fields/parameters - no more `Service` suffix on service fields. yeah. - no more `else`s. who needs them? - code style is now enforced by CI --------- Signed-off-by: Octol1ttle --- .editorconfig | 1806 ++++++++++++++++- locale/Messages.resx | 44 +- locale/Messages.ru.resx | 44 +- locale/Messages.tt-ru.resx | 44 +- src/Boyfriend.cs | 34 +- src/ColorsList.cs | 17 +- src/Commands/AboutCommandGroup.cs | 38 +- src/Commands/BanCommandGroup.cs | 112 +- src/Commands/ClearCommandGroup.cs | 51 +- .../Events/ErrorLoggingPostExecutionEvent.cs | 14 +- .../Events/LoggingPreparationErrorEvent.cs | 14 +- src/Commands/KickCommandGroup.cs | 72 +- src/Commands/MuteCommandGroup.cs | 98 +- src/Commands/PingCommandGroup.cs | 42 +- src/Commands/RemindCommandGroup.cs | 37 +- src/Commands/SettingsCommandGroup.cs | 106 +- src/Data/GuildData.cs | 24 +- src/Data/GuildSettings.cs | 9 +- src/Data/MemberData.cs | 12 +- src/Data/Options/BoolOption.cs | 18 +- src/Data/Options/IOption.cs | 5 +- src/Data/Options/LanguageOption.cs | 15 +- src/Data/Options/Option.cs | 19 +- src/Data/Options/SnowflakeOption.cs | 18 +- src/Data/Options/TimeSpanOption.cs | 14 +- src/Data/Reminder.cs | 7 +- src/Data/ScheduledEventData.cs | 12 +- src/Extensions.cs | 93 +- src/InteractionResponders.cs | 20 +- src/Responders/GuildLoadedResponder.cs | 41 +- src/Responders/GuildMemberJoinedResponder.cs | 41 +- .../GuildMemberRolesUpdatedResponder.cs | 15 +- src/Responders/MessageDeletedResponder.cs | 62 +- src/Responders/MessageEditedResponder.cs | 58 +- src/Responders/MessageReceivedResponder.cs | 20 +- .../ScheduledEventCancelledResponder.cs | 22 +- src/Services/GuildDataService.cs | 81 +- src/Services/GuildUpdateService.cs | 273 ++- src/Services/UtilityService.cs | 88 +- 39 files changed, 2917 insertions(+), 623 deletions(-) diff --git a/.editorconfig b/.editorconfig index bb647a7..5d54b45 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,82 +1,1734 @@ [*] -charset = utf-8 -end_of_line = lf -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = space -indent_size = 4 -tab_width = 4 +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 # Microsoft .NET properties -csharp_new_line_before_catch = false -csharp_new_line_before_else = false -csharp_new_line_before_finally = false -csharp_new_line_before_members_in_object_initializers = false -csharp_new_line_before_open_brace = none -csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async -csharp_style_var_elsewhere = true : suggestion -csharp_style_var_for_built_in_types = true : suggestion -csharp_style_var_when_type_is_apparent = true : suggestion -dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary : none -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity : none -dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary : none -dotnet_style_predefined_type_for_locals_parameters_members = true : suggestion -dotnet_style_predefined_type_for_member_access = true : suggestion -dotnet_style_qualification_for_event = false : suggestion -dotnet_style_qualification_for_field = false : suggestion -dotnet_style_qualification_for_method = false : suggestion -dotnet_style_qualification_for_property = false : suggestion -dotnet_style_require_accessibility_modifiers = for_non_interface_members : suggestion +csharp_indent_braces = false +csharp_indent_switch_labels = true +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true +csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:warning +csharp_prefer_braces = true:warning +csharp_preserve_single_line_blocks = true +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false +csharp_style_expression_bodied_accessors = false:warning +csharp_style_expression_bodied_constructors = false:warning +csharp_style_expression_bodied_methods = false:warning +csharp_style_expression_bodied_properties = false:warning +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_prefer_utf8_string_literals = true:warning +csharp_style_var_elsewhere = true:warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_using_directive_placement = outside_namespace:warning +dotnet_diagnostic.bc40000.severity = warning +dotnet_diagnostic.bc400005.severity = warning +dotnet_diagnostic.bc40008.severity = warning +dotnet_diagnostic.bc40056.severity = warning +dotnet_diagnostic.bc42016.severity = warning +dotnet_diagnostic.bc42024.severity = warning +dotnet_diagnostic.bc42025.severity = warning +dotnet_diagnostic.bc42104.severity = warning +dotnet_diagnostic.bc42105.severity = warning +dotnet_diagnostic.bc42106.severity = warning +dotnet_diagnostic.bc42107.severity = warning +dotnet_diagnostic.bc42304.severity = warning +dotnet_diagnostic.bc42309.severity = warning +dotnet_diagnostic.bc42322.severity = warning +dotnet_diagnostic.bc42349.severity = warning +dotnet_diagnostic.bc42353.severity = warning +dotnet_diagnostic.bc42354.severity = warning +dotnet_diagnostic.bc42355.severity = warning +dotnet_diagnostic.bc42356.severity = warning +dotnet_diagnostic.bc42358.severity = warning +dotnet_diagnostic.bc42504.severity = warning +dotnet_diagnostic.bc42505.severity = warning +dotnet_diagnostic.ca2252.severity = error +dotnet_diagnostic.cs0067.severity = warning +dotnet_diagnostic.cs0078.severity = warning +dotnet_diagnostic.cs0108.severity = warning +dotnet_diagnostic.cs0109.severity = warning +dotnet_diagnostic.cs0114.severity = warning +dotnet_diagnostic.cs0162.severity = warning +dotnet_diagnostic.cs0164.severity = warning +dotnet_diagnostic.cs0168.severity = warning +dotnet_diagnostic.cs0169.severity = warning +dotnet_diagnostic.cs0183.severity = warning +dotnet_diagnostic.cs0184.severity = warning +dotnet_diagnostic.cs0197.severity = warning +dotnet_diagnostic.cs0219.severity = warning +dotnet_diagnostic.cs0252.severity = warning +dotnet_diagnostic.cs0253.severity = warning +dotnet_diagnostic.cs0414.severity = warning +dotnet_diagnostic.cs0420.severity = warning +dotnet_diagnostic.cs0458.severity = warning +dotnet_diagnostic.cs0464.severity = warning +dotnet_diagnostic.cs0465.severity = warning +dotnet_diagnostic.cs0469.severity = warning +dotnet_diagnostic.cs0472.severity = warning +dotnet_diagnostic.cs0612.severity = warning +dotnet_diagnostic.cs0618.severity = warning +dotnet_diagnostic.cs0628.severity = warning +dotnet_diagnostic.cs0642.severity = warning +dotnet_diagnostic.cs0649.severity = warning +dotnet_diagnostic.cs0652.severity = warning +dotnet_diagnostic.cs0657.severity = warning +dotnet_diagnostic.cs0658.severity = warning +dotnet_diagnostic.cs0659.severity = warning +dotnet_diagnostic.cs0660.severity = warning +dotnet_diagnostic.cs0661.severity = warning +dotnet_diagnostic.cs0665.severity = warning +dotnet_diagnostic.cs0672.severity = warning +dotnet_diagnostic.cs0675.severity = warning +dotnet_diagnostic.cs0693.severity = warning +dotnet_diagnostic.cs1030.severity = warning +dotnet_diagnostic.cs1058.severity = warning +dotnet_diagnostic.cs1066.severity = warning +dotnet_diagnostic.cs1522.severity = warning +dotnet_diagnostic.cs1570.severity = warning +dotnet_diagnostic.cs1571.severity = warning +dotnet_diagnostic.cs1572.severity = warning +dotnet_diagnostic.cs1573.severity = warning +dotnet_diagnostic.cs1574.severity = warning +dotnet_diagnostic.cs1580.severity = warning +dotnet_diagnostic.cs1581.severity = warning +dotnet_diagnostic.cs1584.severity = warning +dotnet_diagnostic.cs1587.severity = warning +dotnet_diagnostic.cs1589.severity = warning +dotnet_diagnostic.cs1590.severity = warning +dotnet_diagnostic.cs1591.severity = warning +dotnet_diagnostic.cs1592.severity = warning +dotnet_diagnostic.cs1710.severity = warning +dotnet_diagnostic.cs1711.severity = warning +dotnet_diagnostic.cs1712.severity = warning +dotnet_diagnostic.cs1717.severity = warning +dotnet_diagnostic.cs1723.severity = warning +dotnet_diagnostic.cs1911.severity = warning +dotnet_diagnostic.cs1957.severity = warning +dotnet_diagnostic.cs1981.severity = warning +dotnet_diagnostic.cs1998.severity = warning +dotnet_diagnostic.cs4014.severity = warning +dotnet_diagnostic.cs7022.severity = warning +dotnet_diagnostic.cs7023.severity = warning +dotnet_diagnostic.cs7095.severity = warning +dotnet_diagnostic.cs8073.severity = warning +dotnet_diagnostic.cs8094.severity = warning +dotnet_diagnostic.cs8123.severity = warning +dotnet_diagnostic.cs8321.severity = warning +dotnet_diagnostic.cs8383.severity = warning +dotnet_diagnostic.cs8416.severity = warning +dotnet_diagnostic.cs8417.severity = warning +dotnet_diagnostic.cs8424.severity = warning +dotnet_diagnostic.cs8425.severity = warning +dotnet_diagnostic.cs8500.severity = warning +dotnet_diagnostic.cs8509.severity = warning +dotnet_diagnostic.cs8524.severity = warning +dotnet_diagnostic.cs8597.severity = warning +dotnet_diagnostic.cs8600.severity = warning +dotnet_diagnostic.cs8601.severity = warning +dotnet_diagnostic.cs8602.severity = warning +dotnet_diagnostic.cs8603.severity = warning +dotnet_diagnostic.cs8604.severity = warning +dotnet_diagnostic.cs8605.severity = warning +dotnet_diagnostic.cs8607.severity = warning +dotnet_diagnostic.cs8608.severity = warning +dotnet_diagnostic.cs8609.severity = warning +dotnet_diagnostic.cs8610.severity = warning +dotnet_diagnostic.cs8611.severity = warning +dotnet_diagnostic.cs8612.severity = warning +dotnet_diagnostic.cs8613.severity = warning +dotnet_diagnostic.cs8614.severity = warning +dotnet_diagnostic.cs8615.severity = warning +dotnet_diagnostic.cs8616.severity = warning +dotnet_diagnostic.cs8617.severity = warning +dotnet_diagnostic.cs8618.severity = warning +dotnet_diagnostic.cs8619.severity = warning +dotnet_diagnostic.cs8620.severity = warning +dotnet_diagnostic.cs8621.severity = warning +dotnet_diagnostic.cs8622.severity = warning +dotnet_diagnostic.cs8624.severity = warning +dotnet_diagnostic.cs8625.severity = warning +dotnet_diagnostic.cs8629.severity = warning +dotnet_diagnostic.cs8631.severity = warning +dotnet_diagnostic.cs8632.severity = warning +dotnet_diagnostic.cs8633.severity = warning +dotnet_diagnostic.cs8634.severity = warning +dotnet_diagnostic.cs8643.severity = warning +dotnet_diagnostic.cs8644.severity = warning +dotnet_diagnostic.cs8645.severity = warning +dotnet_diagnostic.cs8655.severity = warning +dotnet_diagnostic.cs8656.severity = warning +dotnet_diagnostic.cs8667.severity = warning +dotnet_diagnostic.cs8669.severity = warning +dotnet_diagnostic.cs8670.severity = warning +dotnet_diagnostic.cs8714.severity = warning +dotnet_diagnostic.cs8762.severity = warning +dotnet_diagnostic.cs8763.severity = warning +dotnet_diagnostic.cs8764.severity = warning +dotnet_diagnostic.cs8765.severity = warning +dotnet_diagnostic.cs8766.severity = warning +dotnet_diagnostic.cs8767.severity = warning +dotnet_diagnostic.cs8768.severity = warning +dotnet_diagnostic.cs8769.severity = warning +dotnet_diagnostic.cs8770.severity = warning +dotnet_diagnostic.cs8774.severity = warning +dotnet_diagnostic.cs8775.severity = warning +dotnet_diagnostic.cs8776.severity = warning +dotnet_diagnostic.cs8777.severity = warning +dotnet_diagnostic.cs8794.severity = warning +dotnet_diagnostic.cs8819.severity = warning +dotnet_diagnostic.cs8824.severity = warning +dotnet_diagnostic.cs8825.severity = warning +dotnet_diagnostic.cs8846.severity = warning +dotnet_diagnostic.cs8847.severity = warning +dotnet_diagnostic.cs8851.severity = warning +dotnet_diagnostic.cs8860.severity = warning +dotnet_diagnostic.cs8892.severity = warning +dotnet_diagnostic.cs8907.severity = warning +dotnet_diagnostic.cs8947.severity = warning +dotnet_diagnostic.cs8960.severity = warning +dotnet_diagnostic.cs8961.severity = warning +dotnet_diagnostic.cs8962.severity = warning +dotnet_diagnostic.cs8963.severity = warning +dotnet_diagnostic.cs8965.severity = warning +dotnet_diagnostic.cs8966.severity = warning +dotnet_diagnostic.cs8971.severity = warning +dotnet_diagnostic.cs8981.severity = warning +dotnet_diagnostic.cs9042.severity = warning +dotnet_diagnostic.cs9073.severity = warning +dotnet_diagnostic.cs9074.severity = warning +dotnet_diagnostic.cs9080.severity = warning +dotnet_diagnostic.cs9081.severity = warning +dotnet_diagnostic.cs9082.severity = warning +dotnet_diagnostic.cs9083.severity = warning +dotnet_diagnostic.cs9084.severity = warning +dotnet_diagnostic.cs9085.severity = warning +dotnet_diagnostic.cs9086.severity = warning +dotnet_diagnostic.cs9087.severity = warning +dotnet_diagnostic.cs9088.severity = warning +dotnet_diagnostic.cs9089.severity = warning +dotnet_diagnostic.cs9090.severity = warning +dotnet_diagnostic.cs9091.severity = warning +dotnet_diagnostic.cs9092.severity = warning +dotnet_diagnostic.cs9093.severity = warning +dotnet_diagnostic.cs9094.severity = warning +dotnet_diagnostic.cs9095.severity = warning +dotnet_diagnostic.cs9097.severity = warning +dotnet_diagnostic.wme006.severity = warning +dotnet_naming_rule.constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.constants_rule.severity = warning +dotnet_naming_rule.constants_rule.style = upper_camel_case_style +dotnet_naming_rule.constants_rule.symbols = constants_symbols +dotnet_naming_rule.event_rule.import_to_resharper = as_predefined +dotnet_naming_rule.event_rule.severity = warning +dotnet_naming_rule.event_rule.style = upper_camel_case_style +dotnet_naming_rule.event_rule.symbols = event_symbols +dotnet_naming_rule.interfaces_rule.import_to_resharper = as_predefined +dotnet_naming_rule.interfaces_rule.severity = warning +dotnet_naming_rule.interfaces_rule.style = i_upper_camel_case_style +dotnet_naming_rule.interfaces_rule.symbols = interfaces_symbols +dotnet_naming_rule.locals_rule.import_to_resharper = as_predefined +dotnet_naming_rule.locals_rule.severity = warning +dotnet_naming_rule.locals_rule.style = lower_camel_case_style_1 +dotnet_naming_rule.locals_rule.symbols = locals_symbols +dotnet_naming_rule.local_constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.local_constants_rule.severity = warning +dotnet_naming_rule.local_constants_rule.style = lower_camel_case_style_1 +dotnet_naming_rule.local_constants_rule.symbols = local_constants_symbols +dotnet_naming_rule.local_functions_rule.import_to_resharper = as_predefined +dotnet_naming_rule.local_functions_rule.severity = warning +dotnet_naming_rule.local_functions_rule.style = upper_camel_case_style +dotnet_naming_rule.local_functions_rule.symbols = local_functions_symbols +dotnet_naming_rule.method_rule.import_to_resharper = as_predefined +dotnet_naming_rule.method_rule.severity = warning +dotnet_naming_rule.method_rule.style = upper_camel_case_style +dotnet_naming_rule.method_rule.symbols = method_symbols +dotnet_naming_rule.parameters_rule.import_to_resharper = as_predefined +dotnet_naming_rule.parameters_rule.severity = warning +dotnet_naming_rule.parameters_rule.style = lower_camel_case_style_1 +dotnet_naming_rule.parameters_rule.symbols = parameters_symbols +dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_constants_rule.severity = warning +dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style +dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols +dotnet_naming_rule.private_instance_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_instance_fields_rule.severity = warning +dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style +dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols +dotnet_naming_rule.private_static_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_fields_rule.severity = warning +dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols +dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_readonly_rule.severity = warning +dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols +dotnet_naming_rule.property_rule.import_to_resharper = as_predefined +dotnet_naming_rule.property_rule.severity = warning +dotnet_naming_rule.property_rule.style = upper_camel_case_style +dotnet_naming_rule.property_rule.symbols = property_symbols +dotnet_naming_rule.public_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.public_fields_rule.severity = warning +dotnet_naming_rule.public_fields_rule.style = upper_camel_case_style +dotnet_naming_rule.public_fields_rule.symbols = public_fields_symbols +dotnet_naming_rule.static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.static_readonly_rule.severity = warning +dotnet_naming_rule.static_readonly_rule.style = upper_camel_case_style +dotnet_naming_rule.static_readonly_rule.symbols = static_readonly_symbols +dotnet_naming_rule.types_and_namespaces_rule.import_to_resharper = as_predefined +dotnet_naming_rule.types_and_namespaces_rule.severity = warning +dotnet_naming_rule.types_and_namespaces_rule.style = upper_camel_case_style +dotnet_naming_rule.types_and_namespaces_rule.symbols = types_and_namespaces_symbols +dotnet_naming_rule.type_parameters_rule.import_to_resharper = as_predefined +dotnet_naming_rule.type_parameters_rule.severity = warning +dotnet_naming_rule.type_parameters_rule.style = t_upper_camel_case_style +dotnet_naming_rule.type_parameters_rule.symbols = type_parameters_symbols +dotnet_naming_style.i_upper_camel_case_style.capitalization = pascal_case +dotnet_naming_style.i_upper_camel_case_style.required_prefix = I +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.lower_camel_case_style.required_prefix = _ +dotnet_naming_style.lower_camel_case_style_1.capitalization = camel_case +dotnet_naming_style.t_upper_camel_case_style.capitalization = pascal_case +dotnet_naming_style.t_upper_camel_case_style.required_prefix = T +dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case +dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.constants_symbols.applicable_kinds = field +dotnet_naming_symbols.constants_symbols.required_modifiers = const +dotnet_naming_symbols.event_symbols.applicable_accessibilities = * +dotnet_naming_symbols.event_symbols.applicable_kinds = event +dotnet_naming_symbols.interfaces_symbols.applicable_accessibilities = * +dotnet_naming_symbols.interfaces_symbols.applicable_kinds = interface +dotnet_naming_symbols.locals_symbols.applicable_accessibilities = * +dotnet_naming_symbols.locals_symbols.applicable_kinds = local +dotnet_naming_symbols.local_constants_symbols.applicable_accessibilities = * +dotnet_naming_symbols.local_constants_symbols.applicable_kinds = local +dotnet_naming_symbols.local_constants_symbols.required_modifiers = const +dotnet_naming_symbols.local_functions_symbols.applicable_accessibilities = * +dotnet_naming_symbols.local_functions_symbols.applicable_kinds = local_function +dotnet_naming_symbols.method_symbols.applicable_accessibilities = * +dotnet_naming_symbols.method_symbols.applicable_kinds = method +dotnet_naming_symbols.parameters_symbols.applicable_accessibilities = * +dotnet_naming_symbols.parameters_symbols.applicable_kinds = parameter +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_symbols.required_modifiers = const +dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static +dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly +dotnet_naming_symbols.property_symbols.applicable_accessibilities = * +dotnet_naming_symbols.property_symbols.applicable_kinds = property +dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly +dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = * +dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace, class, struct, enum, delegate +dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = * +dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:warning +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +dotnet_style_qualification_for_event = false:warning +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:warning # ReSharper properties -resharper_align_linq_query = true -resharper_align_multiline_argument = true -resharper_align_multiline_binary_patterns = true -resharper_align_multiline_extends_list = true -resharper_align_multiline_parameter = true -resharper_align_multiple_declaration = true -resharper_align_multline_type_parameter_constrains = true -resharper_align_multline_type_parameter_list = true -resharper_align_tuple_components = true -resharper_allow_comment_after_lbrace = true -resharper_csharp_empty_block_style = together_same_line -resharper_csharp_indent_type_constraints = false -resharper_csharp_int_align_comments = true -resharper_csharp_outdent_commas = true -resharper_csharp_stick_comment = false -resharper_csharp_wrap_after_declaration_lpar = true -resharper_csharp_wrap_after_invocation_lpar = true -resharper_csharp_wrap_before_binary_opsign = true -resharper_csharp_wrap_before_first_type_parameter_constraint = true -resharper_csharp_wrap_multiple_declaration_style = wrap_if_long -resharper_csharp_wrap_multiple_type_parameter_constraints_style = chop_always -resharper_indent_nested_fixed_stmt = true -resharper_indent_nested_foreach_stmt = true -resharper_indent_nested_for_stmt = true -resharper_indent_nested_lock_stmt = true -resharper_indent_nested_usings_stmt = true -resharper_indent_nested_while_stmt = true -resharper_indent_preprocessor_if = usual_indent -resharper_indent_preprocessor_other = usual_indent -resharper_int_align_fields = true -resharper_int_align_methods = true -resharper_int_align_parameters = true -resharper_int_align_properties = true -resharper_int_align_switch_expressions = true -resharper_int_align_switch_sections = true -resharper_keep_existing_switch_expression_arrangement = false -resharper_outdent_statement_labels = true -resharper_place_field_attribute_on_same_line = if_owner_is_single_line -resharper_place_simple_accessorholder_on_single_line = true -resharper_place_simple_accessor_on_single_line = false -resharper_place_simple_case_statement_on_same_line = true -resharper_place_simple_embedded_block_on_same_line = true -resharper_place_simple_switch_expression_on_single_line = true -resharper_space_around_arrow_op = true -resharper_wrap_before_arrow_with_expressions = true -resharper_wrap_before_eq = true -resharper_wrap_before_extends_colon = true -resharper_wrap_before_linq_expression = true -resharper_wrap_chained_binary_expressions = chop_if_long -resharper_wrap_for_stmt_header_style = wrap_if_long -resharper_wrap_switch_expression = chop_if_long +resharper_alignment_tab_fill_style = use_spaces +resharper_align_first_arg_by_paren = false +resharper_align_linq_query = false +resharper_align_multiline_array_and_object_initializer = false +resharper_align_multiline_array_initializer = true +resharper_align_multiline_binary_patterns = false +resharper_align_multiline_ctor_init = true +resharper_align_multiline_expression_braces = false +resharper_align_multiline_implements_list = true +resharper_align_multiline_list_pattern = false +resharper_align_multiline_property_pattern = false +resharper_align_multiline_statement_conditions = true +resharper_align_multiline_switch_expression = false +resharper_align_multiline_type_argument = true +resharper_align_multiline_type_parameter = true +resharper_align_multline_type_parameter_constrains = false +resharper_align_multline_type_parameter_list = false +resharper_align_ternary = align_not_nested +resharper_align_tuple_components = false +resharper_allow_alias = true +resharper_allow_comment_after_lbrace = false +resharper_allow_far_alignment = false +resharper_always_use_end_of_line_brace_style = false +resharper_apply_auto_detected_rules = true +resharper_apply_on_completion = false +resharper_arguments_anonymous_function = positional +resharper_arguments_literal = positional +resharper_arguments_named = positional +resharper_arguments_other = positional +resharper_arguments_skip_single = true +resharper_arguments_string_literal = positional +resharper_attribute_style = do_not_touch +resharper_autodetect_indent_settings = true +resharper_blank_lines_after_access_specifier = 0 +resharper_blank_lines_after_block_statements = 1 +resharper_blank_lines_after_case = 0 +resharper_blank_lines_after_control_transfer_statements = 0 +resharper_blank_lines_after_file_scoped_namespace_directive = 1 +resharper_blank_lines_after_imports = 1 +resharper_blank_lines_after_multiline_statements = 0 +resharper_blank_lines_after_options = 1 +resharper_blank_lines_after_start_comment = 1 +resharper_blank_lines_after_using_list = 1 +resharper_blank_lines_around_accessor = 0 +resharper_blank_lines_around_auto_property = 1 +resharper_blank_lines_around_block_case_section = 0 +resharper_blank_lines_around_class_definition = 1 +resharper_blank_lines_around_field = 1 +resharper_blank_lines_around_function_declaration = 0 +resharper_blank_lines_around_function_definition = 1 +resharper_blank_lines_around_global_attribute = 0 +resharper_blank_lines_around_invocable = 1 +resharper_blank_lines_around_local_method = 1 +resharper_blank_lines_around_multiline_case_section = 0 +resharper_blank_lines_around_namespace = 1 +resharper_blank_lines_around_other_declaration = 0 +resharper_blank_lines_around_property = 1 +resharper_blank_lines_around_razor_functions = 1 +resharper_blank_lines_around_razor_helpers = 1 +resharper_blank_lines_around_razor_sections = 1 +resharper_blank_lines_around_region = 1 +resharper_blank_lines_around_single_line_accessor = 0 +resharper_blank_lines_around_single_line_auto_property = 0 +resharper_blank_lines_around_single_line_field = 0 +resharper_blank_lines_around_single_line_function_definition = 0 +resharper_blank_lines_around_single_line_invocable = 0 +resharper_blank_lines_around_single_line_local_method = 0 +resharper_blank_lines_around_single_line_property = 0 +resharper_blank_lines_around_single_line_type = 1 +resharper_blank_lines_around_type = 1 +resharper_blank_lines_before_access_specifier = 1 +resharper_blank_lines_before_block_statements = 0 +resharper_blank_lines_before_case = 0 +resharper_blank_lines_before_control_transfer_statements = 0 +resharper_blank_lines_before_multiline_statements = 0 +resharper_blank_lines_before_single_line_comment = 0 +resharper_blank_lines_inside_namespace = 0 +resharper_blank_lines_inside_region = 1 +resharper_blank_lines_inside_type = 0 +resharper_blank_line_after_pi = true +resharper_braces_redundant = false +resharper_break_template_declaration = line_break +resharper_builtin_type_apply_to_native_integer = false +resharper_can_use_global_alias = true +resharper_configure_await_analysis_mode = disabled +resharper_continuous_indent_multiplier = 1 +resharper_continuous_line_indent = single +resharper_csharp_align_multiline_argument = false +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_extends_list = false +resharper_csharp_align_multiline_for_stmt = false +resharper_csharp_align_multiline_parameter = false +resharper_csharp_align_multiple_declaration = false +resharper_csharp_insert_final_newline = true +resharper_csharp_keep_blank_lines_in_code = 1 +resharper_csharp_max_line_length = 120 +resharper_csharp_naming_rule.enum_member = AaBb +resharper_csharp_naming_rule.method_property_event = AaBb +resharper_csharp_naming_rule.other = AaBb +resharper_csharp_new_line_before_while = false +resharper_csharp_prefer_qualified_reference = false +resharper_csharp_space_after_unary_operator = false +resharper_csharp_wrap_before_first_type_parameter_constraint = true +resharper_csharp_wrap_lines = true +resharper_default_exception_variable_name = e +resharper_default_value_when_type_evident = default_literal +resharper_default_value_when_type_not_evident = default_literal +resharper_delete_quotes_from_solid_values = false +resharper_disable_blank_line_changes = false +resharper_disable_formatter = false +resharper_disable_indenter = false +resharper_disable_int_align = false +resharper_disable_line_break_changes = false +resharper_disable_line_break_removal = false +resharper_disable_space_changes = false +resharper_disable_space_changes_before_trailing_comment = false +resharper_dont_remove_extra_blank_lines = false +resharper_empty_block_style = multiline +resharper_enable_wrapping = false +resharper_enforce_line_ending_style = false +resharper_event_handler_pattern_long = $object$On$event$ +resharper_event_handler_pattern_short = On$event$ +resharper_export_declaration_braces = next_line +resharper_expression_braces = inside +resharper_expression_pars = inside +resharper_extra_spaces = remove_all +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 = @formatter:off +resharper_formatter_on_tag = @formatter:on +resharper_formatter_tags_accept_regexp = false +resharper_formatter_tags_enabled = true +resharper_format_leading_spaces_decl = false +resharper_free_block_braces = next_line +resharper_function_declaration_return_type_style = do_not_change +resharper_function_definition_return_type_style = do_not_change +resharper_generator_mode = false +resharper_ignore_space_preservation = false +resharper_include_prefix_comment_in_indent = false +resharper_indent_access_specifiers_from_class = false +resharper_indent_aligned_ternary = true +resharper_indent_anonymous_method_block = false +resharper_indent_braces_inside_statement_conditions = true +resharper_indent_case_from_select = true +resharper_indent_child_elements = OneIndent +resharper_indent_class_members_from_access_specifiers = false +resharper_indent_comment = true +resharper_indent_export_declaration_members = true +resharper_indent_inside_namespace = true +resharper_indent_invocation_pars = inside +resharper_indent_method_decl_pars = inside +resharper_indent_nested_fixed_stmt = false +resharper_indent_nested_foreach_stmt = false +resharper_indent_nested_for_stmt = false +resharper_indent_nested_lock_stmt = false +resharper_indent_nested_usings_stmt = false +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 = no_indent +resharper_indent_preprocessor_region = usual_indent +resharper_indent_statement_pars = inside +resharper_indent_text = OneIndent +resharper_indent_typearg_angles = inside +resharper_indent_typeparam_angles = inside +resharper_indent_type_constraints = true +resharper_indent_wrapped_function_names = false +resharper_instance_members_qualify_declared_in = this_class, base_class +resharper_int_align = false +resharper_int_align_comments = false +resharper_int_align_declaration_names = false +resharper_int_align_enum_initializers = false +resharper_int_align_eq = false +resharper_int_align_fix_in_adjacent = true +resharper_keep_blank_lines_in_declarations = 2 +resharper_keep_existing_arrangement = true +resharper_keep_nontrivial_alias = true +resharper_keep_user_linebreaks = true +resharper_keep_user_wrapping = true +resharper_linebreaks_around_razor_statements = true +resharper_linebreaks_inside_tags_for_elements_longer_than = 2147483647 +resharper_linebreaks_inside_tags_for_elements_with_child_elements = true +resharper_linebreaks_inside_tags_for_multiline_elements = true +resharper_linebreak_before_all_elements = false +resharper_linebreak_before_multiline_elements = true +resharper_linebreak_before_singleline_elements = false +resharper_line_break_after_colon_in_member_initializer_lists = do_not_change +resharper_line_break_after_comma_in_member_initializer_lists = false +resharper_line_break_after_init_statement = do_not_change +resharper_line_break_before_comma_in_member_initializer_lists = false +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 = expression_body +resharper_macro_block_begin = +resharper_macro_block_end = +resharper_max_array_initializer_elements_on_line = 10000 +resharper_max_attribute_length_for_same_line = 38 +resharper_max_enum_members_on_line = 3 +resharper_max_formal_parameters_on_line = 10000 +resharper_max_initializer_elements_on_line = 4 +resharper_max_invocation_arguments_on_line = 10000 +resharper_member_initializer_list_style = do_not_change +resharper_namespace_declaration_braces = next_line +resharper_namespace_indentation = all +resharper_nested_ternary_style = autodetect +resharper_new_line_before_catch = true +resharper_new_line_before_else = true +resharper_new_line_before_enumerators = true +resharper_normalize_tag_names = false +resharper_no_indent_inside_elements = html, body, thead, tbody, tfoot +resharper_no_indent_inside_if_element_longer_than = 200 +resharper_null_checking_pattern_style = not_null_pattern +resharper_object_creation_when_type_evident = target_typed +resharper_object_creation_when_type_not_evident = explicitly_typed +resharper_old_engine = false +resharper_outdent_binary_ops = false +resharper_outdent_binary_pattern_ops = false +resharper_outdent_commas = false +resharper_outdent_dots = false +resharper_outdent_namespace_member = false +resharper_outdent_statement_labels = false +resharper_outdent_ternary_ops = false +resharper_parentheses_non_obvious_operations = none, shift, bitwise_and, bitwise_exclusive_or, bitwise_inclusive_or, bitwise +resharper_parentheses_redundancy_style = remove_if_not_clarifies_precedence +resharper_parentheses_same_type_operations = false +resharper_pi_attributes_indent = align_by_first_attribute +resharper_place_attribute_on_same_line = false +resharper_place_comments_at_first_column = false +resharper_place_constructor_initializer_on_same_line = true +resharper_place_event_attribute_on_same_line = false +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_linq_into_on_new_line = true +resharper_place_namespace_definitions_on_same_line = false +resharper_place_property_attribute_on_same_line = false +resharper_place_simple_case_statement_on_same_line = false +resharper_place_simple_embedded_statement_on_same_line = true +resharper_place_simple_initializer_on_single_line = true +resharper_place_simple_list_pattern_on_single_line = true +resharper_place_simple_property_pattern_on_single_line = true +resharper_place_simple_switch_expression_on_single_line = true +resharper_place_type_constraints_on_same_line = true +resharper_prefer_explicit_discard_declaration = false +resharper_prefer_separate_deconstructed_variables_declaration = true +resharper_preserve_spaces_inside_tags = pre, textarea +resharper_qualified_using_at_nested_scope = false +resharper_quote_style = doublequoted +resharper_razor_prefer_qualified_reference = true +resharper_remove_blank_lines_near_braces = false +resharper_remove_blank_lines_near_braces_in_code = true +resharper_remove_blank_lines_near_braces_in_declarations = true +resharper_remove_this_qualifier = true +resharper_requires_expression_braces = next_line +resharper_resx_attribute_indent = single_indent +resharper_resx_insert_final_newline = true +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 +resharper_resx_space_before_self_closing = false +resharper_resx_wrap_lines = false +resharper_resx_wrap_tags_and_pi = false +resharper_resx_wrap_text = false +resharper_show_autodetect_configure_formatting_tip = false +resharper_simple_block_style = do_not_change +resharper_simple_case_statement_style = do_not_change +resharper_simple_embedded_statement_style = do_not_change +resharper_sort_attributes = false +resharper_sort_class_selectors = false +resharper_sort_usings = true +resharper_sort_usings_lowercase_first = false +resharper_spaces_around_eq_in_attribute = false +resharper_spaces_around_eq_in_pi_attribute = false +resharper_spaces_inside_tags = false +resharper_space_after_attributes = true +resharper_space_after_attribute_target_colon = true +resharper_space_after_cast = false +resharper_space_after_colon = true +resharper_space_after_colon_in_case = true +resharper_space_after_colon_in_inheritance_clause = true +resharper_space_after_comma = true +resharper_space_after_ellipsis_in_parameter_pack = true +resharper_space_after_for_colon = true +resharper_space_after_keywords_in_control_flow_statements = true +resharper_space_after_last_attribute = false +resharper_space_after_last_pi_attribute = false +resharper_space_after_operator_keyword = true +resharper_space_after_operator_not = false +resharper_space_after_ptr_in_data_member = true +resharper_space_after_ptr_in_data_members = false +resharper_space_after_ptr_in_method = true +resharper_space_after_ptr_in_nested_declarator = false +resharper_space_after_ref_in_data_member = true +resharper_space_after_ref_in_data_members = false +resharper_space_after_ref_in_method = true +resharper_space_after_semicolon_in_for_statement = true +resharper_space_after_ternary_colon = true +resharper_space_after_ternary_quest = true +resharper_space_after_triple_slash = true +resharper_space_after_type_parameter_constraint_colon = true +resharper_space_around_additive_op = true +resharper_space_around_alias_eq = true +resharper_space_around_assignment_op = true +resharper_space_around_assignment_operator = true +resharper_space_around_deref_in_trailing_return_type = true +resharper_space_around_lambda_arrow = true +resharper_space_around_member_access_operator = false +resharper_space_around_relational_op = true +resharper_space_around_shift_op = true +resharper_space_around_stmt_colon = true +resharper_space_around_ternary_operator = true +resharper_space_before_array_rank_parentheses = false +resharper_space_before_attribute_target_colon = false +resharper_space_before_checked_parentheses = false +resharper_space_before_colon = false +resharper_space_before_colon_in_case = false +resharper_space_before_colon_in_inheritance_clause = true +resharper_space_before_comma = false +resharper_space_before_default_parentheses = false +resharper_space_before_ellipsis_in_parameter_pack = false +resharper_space_before_empty_invocation_parentheses = false +resharper_space_before_empty_method_parentheses = false +resharper_space_before_for_colon = true +resharper_space_before_initializer_braces = false +resharper_space_before_invocation_parentheses = false +resharper_space_before_label_colon = false +resharper_space_before_lambda_parentheses = false +resharper_space_before_method_parentheses = false +resharper_space_before_nameof_parentheses = false +resharper_space_before_new_parentheses = false +resharper_space_before_nullable_mark = false +resharper_space_before_open_square_brackets = false +resharper_space_before_pointer_asterik_declaration = false +resharper_space_before_postfix_operator = false +resharper_space_before_ptr_in_abstract_decl = false +resharper_space_before_ptr_in_data_member = false +resharper_space_before_ptr_in_data_members = true +resharper_space_before_ptr_in_method = false +resharper_space_before_ref_in_abstract_decl = false +resharper_space_before_ref_in_data_member = false +resharper_space_before_ref_in_data_members = true +resharper_space_before_ref_in_method = false +resharper_space_before_semicolon = false +resharper_space_before_semicolon_in_for_statement = false +resharper_space_before_singleline_accessorholder = true +resharper_space_before_sizeof_parentheses = false +resharper_space_before_template_args = false +resharper_space_before_template_params = true +resharper_space_before_ternary_colon = true +resharper_space_before_ternary_quest = true +resharper_space_before_trailing_comment = true +resharper_space_before_typeof_parentheses = false +resharper_space_before_type_argument_angle = false +resharper_space_before_type_parameter_angle = false +resharper_space_before_type_parameter_constraint_colon = true +resharper_space_before_type_parameter_parentheses = true +resharper_space_between_accessors_in_singleline_property = true +resharper_space_between_attribute_sections = true +resharper_space_between_closing_angle_brackets_in_template_args = false +resharper_space_between_keyword_and_expression = true +resharper_space_between_keyword_and_type = true +resharper_space_between_method_call_empty_parameter_list_parentheses = false +resharper_space_between_method_call_name_and_opening_parenthesis = false +resharper_space_between_method_call_parameter_list_parentheses = false +resharper_space_between_method_declaration_empty_parameter_list_parentheses = false +resharper_space_between_method_declaration_name_and_open_parenthesis = false +resharper_space_between_method_declaration_parameter_list_parentheses = false +resharper_space_between_parentheses_of_control_flow_statements = false +resharper_space_between_square_brackets = false +resharper_space_between_typecast_parentheses = false +resharper_space_in_singleline_accessorholder = true +resharper_space_in_singleline_anonymous_method = true +resharper_space_in_singleline_method = true +resharper_space_near_postfix_and_prefix_op = false +resharper_space_within_array_initialization_braces = false +resharper_space_within_array_rank_empty_parentheses = false +resharper_space_within_array_rank_parentheses = false +resharper_space_within_attribute_angles = false +resharper_space_within_checked_parentheses = false +resharper_space_within_declaration_parentheses = false +resharper_space_within_default_parentheses = false +resharper_space_within_empty_braces = true +resharper_space_within_empty_initializer_braces = false +resharper_space_within_empty_invocation_parentheses = false +resharper_space_within_empty_method_parentheses = false +resharper_space_within_empty_template_params = false +resharper_space_within_expression_parentheses = false +resharper_space_within_initializer_braces = false +resharper_space_within_invocation_parentheses = false +resharper_space_within_method_parentheses = false +resharper_space_within_nameof_parentheses = false +resharper_space_within_new_parentheses = false +resharper_space_within_parentheses = false +resharper_space_within_single_line_array_initializer_braces = true +resharper_space_within_sizeof_parentheses = false +resharper_space_within_slice_pattern = true +resharper_space_within_template_args = false +resharper_space_within_template_params = false +resharper_space_within_tuple_parentheses = false +resharper_space_within_typeof_parentheses = false +resharper_space_within_type_argument_angles = false +resharper_space_within_type_parameter_angles = false +resharper_space_within_type_parameter_parentheses = false +resharper_special_else_if_treatment = true +resharper_static_members_qualify_members = none +resharper_static_members_qualify_with = declared_type +resharper_stick_comment = true +resharper_support_vs_event_naming_pattern = true +resharper_toplevel_function_declaration_return_type_style = do_not_change +resharper_toplevel_function_definition_return_type_style = do_not_change +resharper_trailing_comma_in_multiline_lists = false +resharper_trailing_comma_in_singleline_lists = false +resharper_use_continuous_indent_inside_initializer_braces = true +resharper_use_continuous_indent_inside_parens = true +resharper_use_continuous_line_indent_in_expression_braces = false +resharper_use_continuous_line_indent_in_method_pars = false +resharper_use_indents_from_main_language_in_file = true +resharper_use_indent_from_previous_element = true +resharper_use_indent_from_vs = false +resharper_use_roslyn_logic_for_evident_types = false +resharper_wrap_after_binary_opsign = true +resharper_wrap_after_declaration_lpar = false +resharper_wrap_after_dot = false +resharper_wrap_after_dot_in_method_calls = false +resharper_wrap_after_expression_lbrace = true +resharper_wrap_after_invocation_lpar = false +resharper_wrap_arguments_style = wrap_if_long +resharper_wrap_around_elements = true +resharper_wrap_array_initializer_style = wrap_if_long +resharper_wrap_base_clause_style = wrap_if_long +resharper_wrap_before_arrow_with_expressions = false +resharper_wrap_before_binary_opsign = false +resharper_wrap_before_binary_pattern_op = true +resharper_wrap_before_colon = false +resharper_wrap_before_comma = false +resharper_wrap_before_comma_in_base_clause = false +resharper_wrap_before_declaration_lpar = false +resharper_wrap_before_declaration_rpar = false +resharper_wrap_before_eq = false +resharper_wrap_before_expression_rbrace = true +resharper_wrap_before_extends_colon = false +resharper_wrap_before_invocation_lpar = false +resharper_wrap_before_invocation_rpar = false +resharper_wrap_before_linq_expression = false +resharper_wrap_before_ternary_opsigns = true +resharper_wrap_before_type_parameter_langle = false +resharper_wrap_braced_init_list_style = wrap_if_long +resharper_wrap_chained_binary_expressions = wrap_if_long +resharper_wrap_chained_binary_patterns = wrap_if_long +resharper_wrap_chained_method_calls = wrap_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_extends_list_style = wrap_if_long +resharper_wrap_for_stmt_header_style = chop_if_long +resharper_wrap_list_pattern = wrap_if_long +resharper_wrap_multiple_declaration_style = chop_if_long +resharper_wrap_multiple_type_parameter_constraints_style = chop_if_long +resharper_wrap_object_and_collection_initializer_style = chop_always +resharper_wrap_parameters_style = wrap_if_long +resharper_wrap_property_pattern = chop_if_long +resharper_wrap_switch_expression = chop_always +resharper_wrap_ternary_expr_style = chop_if_long +resharper_wrap_verbatim_interpolated_strings = no_wrap +resharper_xmldoc_attribute_indent = single_indent +resharper_xmldoc_insert_final_newline = true +resharper_xmldoc_linebreak_before_elements = summary, remarks, example, returns, param, typeparam, value, para +resharper_xmldoc_max_blank_lines_between_tags = 0 +resharper_xmldoc_max_line_length = 120 +resharper_xmldoc_pi_attribute_style = do_not_touch +resharper_xmldoc_space_before_self_closing = true +resharper_xmldoc_wrap_lines = true +resharper_xmldoc_wrap_tags_and_pi = true +resharper_xmldoc_wrap_text = true + +# ReSharper inspection severities +resharper_access_rights_in_text_highlighting = warning +resharper_access_to_disposed_closure_highlighting = warning +resharper_access_to_for_each_variable_in_closure_highlighting = warning +resharper_access_to_modified_closure_highlighting = warning +resharper_access_to_static_member_via_derived_type_highlighting = warning +resharper_address_of_marshal_by_ref_object_highlighting = warning +resharper_angular_html_banana_highlighting = warning +resharper_annotate_can_be_null_parameter_highlighting = warning +resharper_annotate_can_be_null_type_member_highlighting = warning +resharper_annotate_not_null_parameter_highlighting = warning +resharper_annotate_not_null_type_member_highlighting = warning +resharper_annotation_conflict_in_hierarchy_highlighting = warning +resharper_annotation_redundancy_at_value_type_highlighting = warning +resharper_annotation_redundancy_in_hierarchy_highlighting = warning +resharper_anonymous_object_destructuring_problem_highlighting = warning +resharper_arguments_style_anonymous_function_highlighting = warning +resharper_arguments_style_literal_highlighting = warning +resharper_arguments_style_named_expression_highlighting = warning +resharper_arguments_style_other_highlighting = warning +resharper_arguments_style_string_literal_highlighting = warning +resharper_arrange_attributes_highlighting = warning +resharper_arrange_default_value_when_type_evident_highlighting = warning +resharper_arrange_default_value_when_type_not_evident_highlighting = warning +resharper_arrange_local_function_body_highlighting = warning +resharper_arrange_namespace_body_highlighting = warning +resharper_arrange_null_checking_pattern_highlighting = warning +resharper_arrange_object_creation_when_type_evident_highlighting = warning +resharper_arrange_object_creation_when_type_not_evident_highlighting = warning +resharper_arrange_redundant_parentheses_highlighting = warning +resharper_arrange_static_member_qualifier_highlighting = warning +resharper_arrange_this_qualifier_highlighting = warning +resharper_arrange_trailing_comma_in_multiline_lists_highlighting = warning +resharper_arrange_trailing_comma_in_singleline_lists_highlighting = warning +resharper_arrange_type_member_modifiers_highlighting = warning +resharper_arrange_type_modifiers_highlighting = warning +resharper_arrange_var_keywords_in_deconstructing_declaration_highlighting = warning +resharper_asp_content_placeholder_not_resolved_highlighting = error +resharper_asp_custom_page_parser_filter_type_highlighting = warning +resharper_asp_dead_code_highlighting = warning +resharper_asp_entity_highlighting = warning +resharper_asp_image_highlighting = warning +resharper_asp_invalid_control_type_highlighting = error +resharper_asp_not_resolved_highlighting = error +resharper_asp_ods_method_reference_resolve_error_highlighting = error +resharper_asp_resolve_warning_highlighting = warning +resharper_asp_skin_not_resolved_highlighting = error +resharper_asp_tag_attribute_with_optional_value_highlighting = warning +resharper_asp_theme_not_resolved_highlighting = error +resharper_asp_unused_register_directive_highlighting_highlighting = warning +resharper_asp_warning_highlighting = warning +resharper_assignment_in_conditional_expression_highlighting = warning +resharper_assignment_is_fully_discarded_highlighting = warning +resharper_assign_null_to_not_null_attribute_highlighting = warning +resharper_asxx_path_error_highlighting = warning +resharper_async_iterator_invocation_without_await_foreach_highlighting = warning +resharper_async_void_lambda_highlighting = warning +resharper_async_void_method_highlighting = warning +resharper_auto_property_can_be_made_get_only_global_highlighting = warning +resharper_auto_property_can_be_made_get_only_local_highlighting = warning +resharper_bad_attribute_brackets_spaces_highlighting = warning +resharper_bad_braces_spaces_highlighting = warning +resharper_bad_child_statement_indent_highlighting = warning +resharper_bad_colon_spaces_highlighting = warning +resharper_bad_comma_spaces_highlighting = warning +resharper_bad_control_braces_indent_highlighting = warning +resharper_bad_control_braces_line_breaks_highlighting = warning +resharper_bad_declaration_braces_indent_highlighting = warning +resharper_bad_declaration_braces_line_breaks_highlighting = warning +resharper_bad_empty_braces_line_breaks_highlighting = warning +resharper_bad_expression_braces_indent_highlighting = warning +resharper_bad_expression_braces_line_breaks_highlighting = warning +resharper_bad_generic_brackets_spaces_highlighting = warning +resharper_bad_indent_highlighting = warning +resharper_bad_linq_line_breaks_highlighting = warning +resharper_bad_list_line_breaks_highlighting = warning +resharper_bad_member_access_spaces_highlighting = warning +resharper_bad_namespace_braces_indent_highlighting = warning +resharper_bad_parens_line_breaks_highlighting = warning +resharper_bad_parens_spaces_highlighting = warning +resharper_bad_preprocessor_indent_highlighting = warning +resharper_bad_semicolon_spaces_highlighting = warning +resharper_bad_spaces_after_keyword_highlighting = warning +resharper_bad_square_brackets_spaces_highlighting = warning +resharper_bad_switch_braces_indent_highlighting = warning +resharper_bad_symbol_spaces_highlighting = warning +resharper_base_member_has_params_highlighting = warning +resharper_base_method_call_with_default_parameter_highlighting = warning +resharper_base_object_equals_is_object_equals_highlighting = warning +resharper_base_object_get_hash_code_call_in_get_hash_code_highlighting = warning +resharper_bitwise_operator_on_enum_without_flags_highlighting = warning +resharper_blazor_editor_required_highlighting = warning +resharper_built_in_type_reference_style_for_member_access_highlighting = warning +resharper_built_in_type_reference_style_highlighting = warning +resharper_by_ref_argument_is_volatile_field_highlighting = warning +resharper_cannot_apply_equality_operator_to_type_highlighting = warning +resharper_can_simplify_dictionary_lookup_with_try_add_highlighting = warning +resharper_can_simplify_dictionary_lookup_with_try_get_value_highlighting = warning +resharper_center_tag_is_obsolete_highlighting = warning +resharper_check_for_reference_equality_instead_1_highlighting = warning +resharper_check_for_reference_equality_instead_2_highlighting = warning +resharper_check_for_reference_equality_instead_3_highlighting = warning +resharper_check_for_reference_equality_instead_4_highlighting = warning +resharper_check_namespace_highlighting = warning +resharper_class_cannot_be_instantiated_highlighting = warning +resharper_class_can_be_sealed_global_highlighting = warning +resharper_class_can_be_sealed_local_highlighting = warning +resharper_class_never_instantiated_global_highlighting = warning +resharper_class_never_instantiated_local_highlighting = warning +resharper_class_with_virtual_members_never_inherited_global_highlighting = warning +resharper_class_with_virtual_members_never_inherited_local_highlighting = warning +resharper_clear_attribute_is_obsolete_all_highlighting = warning +resharper_clear_attribute_is_obsolete_highlighting = warning +resharper_cognitive_complexity_highlighting = warning +resharper_collection_never_queried_global_highlighting = warning +resharper_collection_never_queried_local_highlighting = warning +resharper_collection_never_updated_global_highlighting = warning +resharper_collection_never_updated_local_highlighting = warning +resharper_comment_typo_highlighting = none +resharper_compare_non_constrained_generic_with_null_highlighting = warning +resharper_compare_of_floats_by_equality_operator_highlighting = warning +resharper_complex_object_destructuring_problem_highlighting = warning +resharper_complex_object_in_context_destructuring_problem_highlighting = warning +resharper_conditional_access_qualifier_is_non_nullable_according_to_api_contract_highlighting = warning +resharper_conditional_ternary_equal_branch_highlighting = warning +resharper_condition_is_always_true_or_false_according_to_nullable_api_contract_highlighting = warning +resharper_condition_is_always_true_or_false_highlighting = warning +resharper_confusing_char_as_integer_in_constructor_highlighting = warning +resharper_constant_conditional_access_qualifier_highlighting = warning +resharper_constant_null_coalescing_condition_highlighting = warning +resharper_constructor_initializer_loop_highlighting = warning +resharper_container_annotation_redundancy_highlighting = warning +resharper_contextual_logger_problem_highlighting = warning +resharper_context_value_is_provided_highlighting = warning +resharper_contract_annotation_not_parsed_highlighting = warning +resharper_convert_closure_to_method_group_highlighting = warning +resharper_convert_conditional_ternary_expression_to_switch_expression_highlighting = warning +resharper_convert_if_do_to_while_highlighting = warning +resharper_convert_if_statement_to_conditional_ternary_expression_highlighting = warning +resharper_convert_if_statement_to_null_coalescing_assignment_highlighting = warning +resharper_convert_if_statement_to_null_coalescing_expression_highlighting = warning +resharper_convert_if_statement_to_return_statement_highlighting = warning +resharper_convert_if_statement_to_switch_statement_highlighting = hint +resharper_convert_if_to_or_expression_highlighting = warning +resharper_convert_nullable_to_short_form_highlighting = warning +resharper_convert_switch_statement_to_switch_expression_highlighting = warning +resharper_convert_to_auto_property_highlighting = warning +resharper_convert_to_auto_property_when_possible_highlighting = warning +resharper_convert_to_auto_property_with_private_setter_highlighting = warning +resharper_convert_to_compound_assignment_highlighting = warning +resharper_convert_to_constant_global_highlighting = warning +resharper_convert_to_constant_local_highlighting = warning +resharper_convert_to_lambda_expression_highlighting = warning +resharper_convert_to_local_function_highlighting = warning +resharper_convert_to_null_coalescing_compound_assignment_highlighting = warning +resharper_convert_to_primary_constructor_highlighting = warning +resharper_convert_to_static_class_highlighting = warning +resharper_convert_to_using_declaration_highlighting = warning +resharper_convert_type_check_pattern_to_null_check_highlighting = warning +resharper_convert_type_check_to_null_check_highlighting = warning +resharper_co_variant_array_conversion_highlighting = warning +resharper_default_value_attribute_for_optional_parameter_highlighting = warning +resharper_double_negation_in_pattern_highlighting = warning +resharper_double_negation_operator_highlighting = warning +resharper_duplicate_resource_highlighting = warning +resharper_empty_constructor_highlighting = warning +resharper_empty_destructor_highlighting = warning +resharper_empty_embedded_statement_highlighting = warning +resharper_empty_for_statement_highlighting = warning +resharper_empty_general_catch_clause_highlighting = warning +resharper_empty_namespace_highlighting = warning +resharper_empty_region_highlighting = warning +resharper_empty_statement_highlighting = warning +resharper_empty_title_tag_highlighting = warning +resharper_entity_name_captured_only_global_highlighting = warning +resharper_entity_name_captured_only_local_highlighting = warning +resharper_enumerable_sum_in_explicit_unchecked_context_highlighting = warning +resharper_enum_underlying_type_is_int_highlighting = warning +resharper_equal_expression_comparison_highlighting = warning +resharper_event_never_invoked_global_highlighting = warning +resharper_event_never_subscribed_to_global_highlighting = warning +resharper_event_never_subscribed_to_local_highlighting = warning +resharper_event_unsubscription_via_anonymous_delegate_highlighting = warning +resharper_exception_passed_as_template_argument_problem_highlighting = warning +resharper_explicit_caller_info_argument_highlighting = warning +resharper_expression_is_always_null_highlighting = warning +resharper_extract_common_property_pattern_highlighting = warning +resharper_field_can_be_made_read_only_global_highlighting = warning +resharper_field_can_be_made_read_only_local_highlighting = warning +resharper_field_hides_interface_property_with_default_implementation_highlighting = warning +resharper_foreach_can_be_converted_to_query_using_another_get_enumerator_highlighting = warning +resharper_foreach_can_be_partly_converted_to_query_using_another_get_enumerator_highlighting = warning +resharper_format_string_placeholders_mismatch_highlighting = warning +resharper_format_string_problem_highlighting = warning +resharper_for_can_be_converted_to_foreach_highlighting = warning +resharper_for_statement_condition_is_true_highlighting = warning +resharper_function_complexity_overflow_highlighting = warning +resharper_function_never_returns_highlighting = warning +resharper_function_recursive_on_all_paths_highlighting = warning +resharper_gc_suppress_finalize_for_type_without_destructor_highlighting = warning +resharper_generic_enumerator_not_disposed_highlighting = warning +resharper_heap_view_boxing_allocation_highlighting = hint +resharper_heap_view_can_avoid_closure_highlighting = suggestion +resharper_heap_view_closure_allocation_highlighting = hint +resharper_heap_view_delegate_allocation_highlighting = hint +resharper_heap_view_implicit_capture_highlighting = hint +resharper_heap_view_object_allocation_evident_highlighting = hint +resharper_heap_view_object_allocation_highlighting = hint +resharper_heap_view_object_allocation_possible_highlighting = hint +resharper_heap_view_possible_boxing_allocation_highlighting = hint +resharper_heuristic_unreachable_code_highlighting = warning +resharper_html_attributes_quotes_highlighting = warning +resharper_html_attribute_not_resolved_highlighting = warning +resharper_html_attribute_value_not_resolved_highlighting = warning +resharper_html_dead_code_highlighting = warning +resharper_html_event_not_resolved_highlighting = warning +resharper_html_id_duplication_highlighting = warning +resharper_html_id_not_resolved_highlighting = warning +resharper_html_obsolete_highlighting = warning +resharper_html_path_error_highlighting = warning +resharper_html_tag_not_closed_highlighting = error +resharper_html_tag_not_resolved_highlighting = warning +resharper_html_tag_should_be_self_closed_highlighting = warning +resharper_html_tag_should_not_be_self_closed_highlighting = warning +resharper_html_warning_highlighting = warning +resharper_identifier_typo_highlighting = none +resharper_inactive_preprocessor_branch_highlighting = warning +resharper_inconsistently_synchronized_field_highlighting = warning +resharper_inconsistent_context_log_property_naming_highlighting = warning +resharper_inconsistent_log_property_naming_highlighting = warning +resharper_inconsistent_naming_highlighting = warning +resharper_inconsistent_order_of_locks_highlighting = warning +resharper_incorrect_blank_lines_near_braces_highlighting = warning +resharper_indexing_by_invalid_range_highlighting = warning +resharper_inheritdoc_consider_usage_highlighting = none +resharper_inheritdoc_invalid_usage_highlighting = warning +resharper_inline_out_variable_declaration_highlighting = warning +resharper_inline_temporary_variable_highlighting = warning +resharper_internal_or_private_member_not_documented_highlighting = warning +resharper_interpolated_string_expression_is_not_i_formattable_highlighting = warning +resharper_introduce_optional_parameters_global_highlighting = warning +resharper_introduce_optional_parameters_local_highlighting = warning +resharper_int_division_by_zero_highlighting = warning +resharper_int_variable_overflow_highlighting = warning +resharper_int_variable_overflow_in_checked_context_highlighting = warning +resharper_int_variable_overflow_in_unchecked_context_highlighting = warning +resharper_invalid_value_type_highlighting = warning +resharper_invalid_xml_doc_comment_highlighting = warning +resharper_invert_condition_1_highlighting = warning +resharper_invert_if_highlighting = hint +resharper_invocation_is_skipped_highlighting = warning +resharper_invoke_as_extension_method_highlighting = warning +resharper_is_expression_always_false_highlighting = warning +resharper_is_expression_always_true_highlighting = warning +resharper_iterator_method_result_is_ignored_highlighting = warning +resharper_iterator_never_returns_highlighting = warning +resharper_join_declaration_and_initializer_highlighting = warning +resharper_join_null_check_with_usage_highlighting = warning +resharper_lambda_expression_can_be_made_static_highlighting = none +resharper_lambda_expression_must_be_static_highlighting = warning +resharper_lambda_should_not_capture_context_highlighting = warning +resharper_localizable_element_highlighting = warning +resharper_local_function_can_be_made_static_highlighting = warning +resharper_local_function_hides_method_highlighting = warning +resharper_local_variable_hides_member_highlighting = warning +resharper_log_message_is_sentence_problem_highlighting = warning +resharper_long_literal_ending_lower_l_highlighting = warning +resharper_loop_can_be_converted_to_query_highlighting = warning +resharper_loop_can_be_partly_converted_to_query_highlighting = warning +resharper_loop_variable_is_never_changed_inside_loop_highlighting = warning +resharper_markup_attribute_typo_highlighting = none +resharper_markup_text_typo_highlighting = none +resharper_math_abs_method_is_redundant_highlighting = warning +resharper_math_clamp_min_greater_than_max_highlighting = warning +resharper_meaningless_default_parameter_value_highlighting = warning +resharper_member_can_be_file_local_highlighting = warning +resharper_member_can_be_internal_highlighting = none +resharper_member_can_be_made_static_global_highlighting = warning +resharper_member_can_be_made_static_local_highlighting = warning +resharper_member_can_be_private_global_highlighting = warning +resharper_member_can_be_private_local_highlighting = warning +resharper_member_can_be_protected_global_highlighting = warning +resharper_member_can_be_protected_local_highlighting = warning +resharper_member_hides_interface_member_with_default_implementation_highlighting = warning +resharper_member_hides_static_from_outer_class_highlighting = warning +resharper_member_initializer_value_ignored_highlighting = warning +resharper_merge_and_pattern_highlighting = warning +resharper_merge_cast_with_type_check_highlighting = warning +resharper_merge_conditional_expression_highlighting = warning +resharper_merge_into_logical_pattern_highlighting = warning +resharper_merge_into_negated_pattern_highlighting = warning +resharper_merge_into_pattern_highlighting = warning +resharper_merge_nested_property_patterns_highlighting = warning +resharper_merge_sequential_checks_highlighting = warning +resharper_method_has_async_overload_highlighting = warning +resharper_method_has_async_overload_with_cancellation_highlighting = warning +resharper_method_overload_with_optional_parameter_highlighting = warning +resharper_method_supports_cancellation_highlighting = warning +resharper_missing_alt_attribute_in_img_tag_highlighting = warning +resharper_missing_blank_lines_highlighting = warning +resharper_missing_body_tag_highlighting = warning +resharper_missing_head_and_body_tags_highlighting = warning +resharper_missing_head_tag_highlighting = warning +resharper_missing_indent_highlighting = warning +resharper_missing_linebreak_highlighting = warning +resharper_missing_space_highlighting = warning +resharper_more_specific_foreach_variable_type_available_highlighting = warning +resharper_move_to_existing_positional_deconstruction_pattern_highlighting = warning +resharper_move_variable_declaration_inside_loop_condition_highlighting = warning +resharper_multiple_nullable_attributes_usage_highlighting = warning +resharper_multiple_order_by_highlighting = warning +resharper_multiple_resolve_candidates_in_text_highlighting = warning +resharper_multiple_spaces_highlighting = warning +resharper_multiple_statements_on_one_line_highlighting = warning +resharper_multiple_type_members_on_one_line_highlighting = warning +resharper_must_use_return_value_highlighting = warning +resharper_mvc_action_not_resolved_highlighting = error +resharper_mvc_area_not_resolved_highlighting = error +resharper_mvc_controller_not_resolved_highlighting = error +resharper_mvc_invalid_model_type_highlighting = error +resharper_mvc_masterpage_not_resolved_highlighting = error +resharper_mvc_partial_view_not_resolved_highlighting = error +resharper_mvc_template_not_resolved_highlighting = error +resharper_mvc_view_component_not_resolved_highlighting = error +resharper_mvc_view_component_view_not_resolved_highlighting = error +resharper_mvc_view_not_resolved_highlighting = error +resharper_negation_of_relational_pattern_highlighting = warning +resharper_negative_equality_expression_highlighting = warning +resharper_negative_index_highlighting = warning +resharper_nested_string_interpolation_highlighting = warning +resharper_non_atomic_compound_operator_highlighting = warning +resharper_non_constant_equality_expression_has_constant_result_highlighting = warning +resharper_non_parsable_element_highlighting = warning +resharper_non_readonly_member_in_get_hash_code_highlighting = warning +resharper_non_volatile_field_in_double_check_locking_highlighting = warning +resharper_not_accessed_field_global_highlighting = warning +resharper_not_accessed_field_local_highlighting = warning +resharper_not_accessed_out_parameter_variable_highlighting = warning +resharper_not_accessed_positional_property_global_highlighting = warning +resharper_not_accessed_positional_property_local_highlighting = warning +resharper_not_accessed_variable_highlighting = warning +resharper_not_assigned_out_parameter_highlighting = warning +resharper_not_declared_in_parent_culture_highlighting = warning +resharper_not_null_or_required_member_is_not_initialized_highlighting = warning +resharper_not_observable_annotation_redundancy_highlighting = warning +resharper_not_overridden_in_specific_culture_highlighting = warning +resharper_not_resolved_in_text_highlighting = warning +resharper_nullable_warning_suppression_is_used_highlighting = warning +resharper_null_coalescing_condition_is_always_not_null_according_to_api_contract_highlighting = warning +resharper_n_unit_async_method_must_be_task_highlighting = warning +resharper_n_unit_attribute_produces_too_many_tests_highlighting = warning +resharper_n_unit_auto_fixture_incorrect_argument_type_highlighting = warning +resharper_n_unit_auto_fixture_missed_test_attribute_highlighting = warning +resharper_n_unit_auto_fixture_missed_test_or_test_fixture_attribute_highlighting = warning +resharper_n_unit_auto_fixture_redundant_argument_in_inline_auto_data_attribute_highlighting = warning +resharper_n_unit_duplicate_values_highlighting = warning +resharper_n_unit_ignored_parameter_attribute_highlighting = warning +resharper_n_unit_implicit_unspecified_null_values_highlighting = warning +resharper_n_unit_incorrect_argument_type_highlighting = warning +resharper_n_unit_incorrect_expected_result_type_highlighting = warning +resharper_n_unit_incorrect_range_bounds_highlighting = warning +resharper_n_unit_method_with_parameters_and_test_attribute_highlighting = warning +resharper_n_unit_missing_arguments_in_test_case_attribute_highlighting = warning +resharper_n_unit_non_public_method_with_test_attribute_highlighting = warning +resharper_n_unit_no_values_provided_highlighting = warning +resharper_n_unit_parameter_type_is_not_compatible_with_attribute_highlighting = warning +resharper_n_unit_range_attribute_bounds_are_out_of_range_highlighting = warning +resharper_n_unit_range_step_sign_mismatch_highlighting = warning +resharper_n_unit_range_step_value_must_not_be_zero_highlighting = warning +resharper_n_unit_range_to_value_is_not_reachable_highlighting = warning +resharper_n_unit_redundant_argument_instead_of_expected_result_highlighting = warning +resharper_n_unit_redundant_argument_in_test_case_attribute_highlighting = warning +resharper_n_unit_redundant_expected_result_in_test_case_attribute_highlighting = warning +resharper_n_unit_test_case_attribute_requires_expected_result_highlighting = warning +resharper_n_unit_test_case_result_property_duplicates_expected_result_highlighting = warning +resharper_n_unit_test_case_result_property_is_obsolete_highlighting = warning +resharper_n_unit_test_case_source_cannot_be_resolved_highlighting = warning +resharper_n_unit_test_case_source_must_be_field_property_method_highlighting = warning +resharper_n_unit_test_case_source_must_be_static_highlighting = warning +resharper_n_unit_test_case_source_should_implement_i_enumerable_highlighting = warning +resharper_object_creation_as_statement_highlighting = warning +resharper_obsolete_element_error_highlighting = error +resharper_obsolete_element_highlighting = warning +resharper_one_way_operation_contract_with_return_type_highlighting = warning +resharper_operation_contract_without_service_contract_highlighting = warning +resharper_operator_is_can_be_used_highlighting = warning +resharper_operator_without_matched_checked_operator_highlighting = warning +resharper_optional_parameter_hierarchy_mismatch_highlighting = warning +resharper_optional_parameter_ref_out_highlighting = warning +resharper_other_tags_inside_script1_highlighting = error +resharper_other_tags_inside_script2_highlighting = error +resharper_other_tags_inside_unclosed_script_highlighting = error +resharper_outdent_is_off_prev_level_highlighting = warning +resharper_out_parameter_value_is_always_discarded_global_highlighting = warning +resharper_out_parameter_value_is_always_discarded_local_highlighting = warning +resharper_overridden_with_empty_value_highlighting = warning +resharper_overridden_with_same_value_highlighting = warning +resharper_parameter_hides_member_highlighting = warning +resharper_parameter_only_used_for_precondition_check_global_highlighting = warning +resharper_parameter_only_used_for_precondition_check_local_highlighting = warning +resharper_parameter_type_can_be_enumerable_global_highlighting = warning +resharper_parameter_type_can_be_enumerable_local_highlighting = warning +resharper_partial_method_parameter_name_mismatch_highlighting = warning +resharper_partial_method_with_single_part_highlighting = warning +resharper_partial_type_with_single_part_highlighting = warning +resharper_pass_string_interpolation_highlighting = warning +resharper_pattern_always_matches_highlighting = warning +resharper_pattern_is_always_true_or_false_highlighting = warning +resharper_pattern_is_redundant_highlighting = warning +resharper_pattern_never_matches_highlighting = warning +resharper_place_assignment_expression_into_block_highlighting = warning +resharper_polymorphic_field_like_event_invocation_highlighting = warning +resharper_positional_property_used_problem_highlighting = warning +resharper_possible_infinite_inheritance_highlighting = warning +resharper_possible_intended_rethrow_highlighting = warning +resharper_possible_interface_member_ambiguity_highlighting = warning +resharper_possible_invalid_cast_exception_highlighting = warning +resharper_possible_invalid_cast_exception_in_foreach_loop_highlighting = warning +resharper_possible_invalid_operation_exception_highlighting = warning +resharper_possible_loss_of_fraction_highlighting = warning +resharper_possible_mistaken_argument_highlighting = warning +resharper_possible_mistaken_call_to_get_type_1_highlighting = warning +resharper_possible_mistaken_call_to_get_type_2_highlighting = warning +resharper_possible_multiple_enumeration_highlighting = warning +resharper_possible_multiple_write_access_in_double_check_locking_highlighting = warning +resharper_possible_null_reference_exception_highlighting = warning +resharper_possible_struct_member_modification_of_non_variable_struct_highlighting = warning +resharper_possible_unintended_linear_search_in_set_highlighting = warning +resharper_possible_unintended_queryable_as_enumerable_highlighting = warning +resharper_possible_unintended_reference_comparison_highlighting = warning +resharper_possible_write_to_me_highlighting = warning +resharper_possibly_impure_method_call_on_readonly_variable_highlighting = warning +resharper_possibly_missing_indexer_initializer_comma_highlighting = warning +resharper_possibly_mistaken_use_of_interpolated_string_insert_highlighting = warning +resharper_private_field_can_be_converted_to_local_variable_highlighting = warning +resharper_property_can_be_made_init_only_global_highlighting = warning +resharper_property_can_be_made_init_only_local_highlighting = warning +resharper_property_field_keyword_is_never_assigned_highlighting = warning +resharper_property_field_keyword_is_never_used_highlighting = warning +resharper_property_not_resolved_highlighting = error +resharper_public_constructor_in_abstract_class_highlighting = warning +resharper_pure_attribute_on_void_method_highlighting = warning +resharper_razor_layout_not_resolved_highlighting = error +resharper_razor_section_not_resolved_highlighting = error +resharper_read_access_in_double_check_locking_highlighting = warning +resharper_redundant_abstract_modifier_highlighting = warning +resharper_redundant_accessor_body_highlighting = warning +resharper_redundant_always_match_subpattern_highlighting = warning +resharper_redundant_anonymous_type_property_name_highlighting = warning +resharper_redundant_argument_default_value_highlighting = warning +resharper_redundant_array_creation_expression_highlighting = warning +resharper_redundant_array_lower_bound_specification_highlighting = warning +resharper_redundant_assignment_highlighting = warning +resharper_redundant_attribute_parentheses_highlighting = warning +resharper_redundant_attribute_suffix_highlighting = warning +resharper_redundant_attribute_usage_property_highlighting = warning +resharper_redundant_base_constructor_call_highlighting = warning +resharper_redundant_base_qualifier_highlighting = warning +resharper_redundant_blank_lines_highlighting = warning +resharper_redundant_bool_compare_highlighting = warning +resharper_redundant_caller_argument_expression_default_value_highlighting = warning +resharper_redundant_case_label_highlighting = warning +resharper_redundant_cast_highlighting = warning +resharper_redundant_catch_clause_highlighting = warning +resharper_redundant_check_before_assignment_highlighting = warning +resharper_redundant_collection_initializer_element_braces_highlighting = warning +resharper_redundant_configure_await_highlighting = warning +resharper_redundant_declaration_semicolon_highlighting = warning +resharper_redundant_default_member_initializer_highlighting = warning +resharper_redundant_delegate_creation_highlighting = warning +resharper_redundant_dictionary_contains_key_before_adding_highlighting = warning +resharper_redundant_disable_warning_comment_highlighting = warning +resharper_redundant_discard_designation_highlighting = warning +resharper_redundant_empty_case_else_highlighting = warning +resharper_redundant_empty_finally_block_highlighting = warning +resharper_redundant_empty_object_creation_argument_list_highlighting = warning +resharper_redundant_empty_object_or_collection_initializer_highlighting = warning +resharper_redundant_empty_switch_section_highlighting = warning +resharper_redundant_enumerable_cast_call_highlighting = warning +resharper_redundant_enum_case_label_for_default_section_highlighting = warning +resharper_redundant_explicit_array_creation_highlighting = warning +resharper_redundant_explicit_array_size_highlighting = warning +resharper_redundant_explicit_nullable_creation_highlighting = warning +resharper_redundant_explicit_params_array_creation_highlighting = warning +resharper_redundant_explicit_positional_property_declaration_highlighting = warning +resharper_redundant_explicit_tuple_component_name_highlighting = warning +resharper_redundant_extends_list_entry_highlighting = warning +resharper_redundant_fixed_pointer_declaration_highlighting = warning +resharper_redundant_if_else_block_highlighting = warning +resharper_redundant_if_statement_then_keyword_highlighting = warning +resharper_redundant_immediate_delegate_invocation_highlighting = warning +resharper_redundant_is_before_relational_pattern_highlighting = warning +resharper_redundant_iterator_keyword_highlighting = warning +resharper_redundant_jump_statement_highlighting = warning +resharper_redundant_lambda_parameter_type_highlighting = warning +resharper_redundant_lambda_signature_parentheses_highlighting = warning +resharper_redundant_linebreak_highlighting = warning +resharper_redundant_logical_conditional_expression_operand_highlighting = warning +resharper_redundant_me_qualifier_highlighting = warning +resharper_redundant_my_base_qualifier_highlighting = warning +resharper_redundant_my_class_qualifier_highlighting = warning +resharper_redundant_name_qualifier_highlighting = warning +resharper_redundant_not_null_constraint_highlighting = warning +resharper_redundant_nullable_annotation_on_reference_type_constraint_highlighting = warning +resharper_redundant_nullable_annotation_on_type_constraint_has_non_nullable_base_type_highlighting = warning +resharper_redundant_nullable_annotation_on_type_constraint_has_non_nullable_type_kind_highlighting = warning +resharper_redundant_nullable_flow_attribute_highlighting = warning +resharper_redundant_nullable_type_mark_highlighting = warning +resharper_redundant_nullness_attribute_with_nullable_reference_types_highlighting = warning +resharper_redundant_overflow_checking_context_highlighting = warning +resharper_redundant_overload_global_highlighting = warning +resharper_redundant_overload_local_highlighting = warning +resharper_redundant_overridden_member_highlighting = warning +resharper_redundant_params_highlighting = warning +resharper_redundant_parentheses_highlighting = warning +resharper_redundant_pattern_parentheses_highlighting = warning +resharper_redundant_property_parentheses_highlighting = warning +resharper_redundant_property_pattern_clause_highlighting = warning +resharper_redundant_qualifier_highlighting = warning +resharper_redundant_query_order_by_ascending_keyword_highlighting = warning +resharper_redundant_range_bound_highlighting = warning +resharper_redundant_readonly_modifier_highlighting = warning +resharper_redundant_record_body_highlighting = warning +resharper_redundant_record_class_keyword_highlighting = warning +resharper_redundant_scoped_parameter_modifier_highlighting = warning +resharper_redundant_setter_value_parameter_declaration_highlighting = warning +resharper_redundant_set_contains_before_adding_highlighting = warning +resharper_redundant_space_highlighting = warning +resharper_redundant_string_format_call_highlighting = warning +resharper_redundant_string_interpolation_highlighting = warning +resharper_redundant_string_to_char_array_call_highlighting = warning +resharper_redundant_string_type_highlighting = warning +resharper_redundant_suppress_nullable_warning_expression_highlighting = warning +resharper_redundant_ternary_expression_highlighting = warning +resharper_redundant_to_string_call_for_value_type_highlighting = warning +resharper_redundant_to_string_call_highlighting = warning +resharper_redundant_type_arguments_of_method_highlighting = warning +resharper_redundant_type_check_in_pattern_highlighting = warning +resharper_redundant_unsafe_context_highlighting = warning +resharper_redundant_using_directive_global_highlighting = warning +resharper_redundant_using_directive_highlighting = warning +resharper_redundant_verbatim_prefix_highlighting = warning +resharper_redundant_verbatim_string_prefix_highlighting = warning +resharper_redundant_virtual_modifier_highlighting = warning +resharper_redundant_with_expression_highlighting = warning +resharper_reference_equals_with_value_type_highlighting = warning +resharper_reg_exp_inspections_highlighting = warning +resharper_remove_constructor_invocation_highlighting = warning +resharper_remove_redundant_or_statement_false_highlighting = warning +resharper_remove_redundant_or_statement_true_highlighting = warning +resharper_remove_to_list_1_highlighting = warning +resharper_remove_to_list_2_highlighting = warning +resharper_replace_auto_property_with_computed_property_highlighting = warning +resharper_replace_conditional_expression_with_null_coalescing_highlighting = warning +resharper_replace_object_pattern_with_var_pattern_highlighting = warning +resharper_replace_sequence_equal_with_constant_pattern_highlighting = warning +resharper_replace_slice_with_range_indexer_highlighting = warning +resharper_replace_substring_with_range_indexer_highlighting = warning +resharper_replace_with_field_keyword_highlighting = warning +resharper_replace_with_first_or_default_1_highlighting = warning +resharper_replace_with_first_or_default_2_highlighting = warning +resharper_replace_with_first_or_default_3_highlighting = warning +resharper_replace_with_first_or_default_4_highlighting = warning +resharper_replace_with_last_or_default_1_highlighting = warning +resharper_replace_with_last_or_default_2_highlighting = warning +resharper_replace_with_last_or_default_3_highlighting = warning +resharper_replace_with_last_or_default_4_highlighting = warning +resharper_replace_with_of_type_1_highlighting = warning +resharper_replace_with_of_type_2_highlighting = warning +resharper_replace_with_of_type_3_highlighting = warning +resharper_replace_with_of_type_any_1_highlighting = warning +resharper_replace_with_of_type_any_2_highlighting = warning +resharper_replace_with_of_type_count_1_highlighting = warning +resharper_replace_with_of_type_count_2_highlighting = warning +resharper_replace_with_of_type_first_1_highlighting = warning +resharper_replace_with_of_type_first_2_highlighting = warning +resharper_replace_with_of_type_first_or_default_1_highlighting = warning +resharper_replace_with_of_type_first_or_default_2_highlighting = warning +resharper_replace_with_of_type_last_1_highlighting = warning +resharper_replace_with_of_type_last_2_highlighting = warning +resharper_replace_with_of_type_last_or_default_1_highlighting = warning +resharper_replace_with_of_type_last_or_default_2_highlighting = warning +resharper_replace_with_of_type_long_count_highlighting = warning +resharper_replace_with_of_type_single_1_highlighting = warning +resharper_replace_with_of_type_single_2_highlighting = warning +resharper_replace_with_of_type_single_or_default_1_highlighting = warning +resharper_replace_with_of_type_single_or_default_2_highlighting = warning +resharper_replace_with_of_type_where_highlighting = warning +resharper_replace_with_simple_assignment_false_highlighting = warning +resharper_replace_with_simple_assignment_true_highlighting = warning +resharper_replace_with_single_assignment_false_highlighting = warning +resharper_replace_with_single_assignment_true_highlighting = warning +resharper_replace_with_single_call_to_any_highlighting = warning +resharper_replace_with_single_call_to_count_highlighting = warning +resharper_replace_with_single_call_to_first_highlighting = warning +resharper_replace_with_single_call_to_first_or_default_highlighting = warning +resharper_replace_with_single_call_to_last_highlighting = warning +resharper_replace_with_single_call_to_last_or_default_highlighting = warning +resharper_replace_with_single_call_to_single_highlighting = warning +resharper_replace_with_single_call_to_single_or_default_highlighting = warning +resharper_replace_with_single_or_default_1_highlighting = warning +resharper_replace_with_single_or_default_2_highlighting = warning +resharper_replace_with_single_or_default_3_highlighting = warning +resharper_replace_with_single_or_default_4_highlighting = warning +resharper_replace_with_string_is_null_or_empty_highlighting = warning +resharper_required_base_types_conflict_highlighting = warning +resharper_required_base_types_direct_conflict_highlighting = warning +resharper_required_base_types_is_not_inherited_highlighting = warning +resharper_resource_item_not_resolved_highlighting = error +resharper_resource_not_resolved_highlighting = error +resharper_resx_not_resolved_highlighting = warning +resharper_return_type_can_be_enumerable_global_highlighting = warning +resharper_return_type_can_be_enumerable_local_highlighting = warning +resharper_return_type_can_be_not_nullable_highlighting = warning +resharper_return_value_of_pure_method_is_not_used_highlighting = warning +resharper_route_templates_action_route_prefix_can_be_extracted_to_controller_route_highlighting = warning +resharper_route_templates_ambiguous_matching_constraint_constructor_highlighting = warning +resharper_route_templates_constraint_argument_cannot_be_converted_highlighting = warning +resharper_route_templates_controller_route_parameter_is_not_passed_to_methods_highlighting = warning +resharper_route_templates_duplicated_parameter_highlighting = warning +resharper_route_templates_matching_constraint_constructor_not_resolved_highlighting = warning +resharper_route_templates_method_missing_route_parameters_highlighting = warning +resharper_route_templates_optional_parameter_can_be_preceded_only_by_single_period_highlighting = warning +resharper_route_templates_optional_parameter_must_be_at_the_end_of_segment_highlighting = warning +resharper_route_templates_parameter_constraint_can_be_specified_highlighting = warning +resharper_route_templates_parameter_type_and_constraints_mismatch_highlighting = warning +resharper_route_templates_parameter_type_can_be_made_stricter_highlighting = warning +resharper_route_templates_route_parameter_constraint_not_resolved_highlighting = warning +resharper_route_templates_route_parameter_is_not_passed_to_method_highlighting = warning +resharper_route_templates_route_token_not_resolved_highlighting = warning +resharper_route_templates_symbol_not_resolved_highlighting = warning +resharper_route_templates_syntax_error_highlighting = warning +resharper_safe_cast_is_used_as_type_check_highlighting = warning +resharper_script_tag_has_both_src_and_content_attributes_highlighting = error +resharper_sealed_member_in_sealed_class_highlighting = warning +resharper_separate_control_transfer_statement_highlighting = warning +resharper_service_contract_without_operations_highlighting = warning +resharper_shift_expression_real_shift_count_is_zero_highlighting = warning +resharper_shift_expression_result_equals_zero_highlighting = warning +resharper_shift_expression_right_operand_not_equal_real_count_highlighting = warning +resharper_shift_expression_zero_left_operand_highlighting = warning +resharper_similar_anonymous_type_nearby_highlighting = warning +resharper_simplify_conditional_operator_highlighting = warning +resharper_simplify_conditional_ternary_expression_highlighting = warning +resharper_simplify_i_if_highlighting = warning +resharper_simplify_linq_expression_use_all_highlighting = warning +resharper_simplify_linq_expression_use_any_highlighting = warning +resharper_simplify_linq_expression_use_min_by_and_max_by_highlighting = warning +resharper_simplify_string_interpolation_highlighting = warning +resharper_specify_a_culture_in_string_conversion_explicitly_highlighting = warning +resharper_specify_string_comparison_highlighting = warning +resharper_spin_lock_in_readonly_field_highlighting = warning +resharper_stack_alloc_inside_loop_highlighting = warning +resharper_static_member_initializer_referes_to_member_below_highlighting = warning +resharper_static_member_in_generic_type_highlighting = warning +resharper_static_problem_in_text_highlighting = warning +resharper_string_compare_is_culture_specific_1_highlighting = warning +resharper_string_compare_is_culture_specific_2_highlighting = warning +resharper_string_compare_is_culture_specific_3_highlighting = warning +resharper_string_compare_is_culture_specific_4_highlighting = warning +resharper_string_compare_is_culture_specific_5_highlighting = warning +resharper_string_compare_is_culture_specific_6_highlighting = warning +resharper_string_compare_to_is_culture_specific_highlighting = warning +resharper_string_ends_with_is_culture_specific_highlighting = warning +resharper_string_index_of_is_culture_specific_1_highlighting = warning +resharper_string_index_of_is_culture_specific_2_highlighting = warning +resharper_string_index_of_is_culture_specific_3_highlighting = warning +resharper_string_last_index_of_is_culture_specific_1_highlighting = warning +resharper_string_last_index_of_is_culture_specific_2_highlighting = warning +resharper_string_last_index_of_is_culture_specific_3_highlighting = warning +resharper_string_literal_as_interpolation_argument_highlighting = warning +resharper_string_literal_typo_highlighting = none +resharper_string_starts_with_is_culture_specific_highlighting = warning +resharper_structured_message_template_problem_highlighting = warning +resharper_struct_can_be_made_read_only_highlighting = warning +resharper_struct_member_can_be_made_read_only_highlighting = warning +resharper_suggest_base_type_for_parameter_highlighting = warning +resharper_suggest_base_type_for_parameter_in_constructor_highlighting = warning +resharper_suggest_discard_declaration_var_style_highlighting = warning +resharper_suggest_var_or_type_built_in_types_highlighting = warning +resharper_suggest_var_or_type_deconstruction_declarations_highlighting = warning +resharper_suggest_var_or_type_elsewhere_highlighting = warning +resharper_suggest_var_or_type_simple_types_highlighting = warning +resharper_suppress_nullable_warning_expression_as_inverted_is_expression_highlighting = warning +resharper_suspicious_lock_over_synchronization_primitive_highlighting = warning +resharper_suspicious_math_sign_method_highlighting = warning +resharper_suspicious_parameter_name_in_argument_null_exception_highlighting = warning +resharper_suspicious_type_conversion_global_highlighting = warning +resharper_swap_via_deconstruction_highlighting = warning +resharper_switch_expression_handles_some_known_enum_values_with_exception_in_default_highlighting = warning +resharper_switch_statement_for_enum_misses_default_section_highlighting = warning +resharper_switch_statement_handles_some_known_enum_values_with_default_highlighting = warning +resharper_switch_statement_missing_some_enum_cases_no_default_highlighting = warning +resharper_symbol_from_not_copied_locally_reference_used_warning_highlighting = warning +resharper_tabs_and_spaces_mismatch_highlighting = warning +resharper_tabs_are_disallowed_highlighting = warning +resharper_tabs_outside_indent_highlighting = warning +resharper_tail_recursive_call_highlighting = warning +resharper_template_duplicate_property_problem_highlighting = warning +resharper_template_format_string_problem_highlighting = warning +resharper_template_is_not_compile_time_constant_problem_highlighting = warning +resharper_thread_static_at_instance_field_highlighting = warning +resharper_thread_static_field_has_initializer_highlighting = warning +resharper_too_wide_local_variable_scope_highlighting = warning +resharper_try_cast_always_succeeds_highlighting = warning +resharper_try_statements_can_be_merged_highlighting = warning +resharper_type_parameter_can_be_variant_highlighting = warning +resharper_unassigned_field_global_highlighting = warning +resharper_unassigned_field_local_highlighting = warning +resharper_unassigned_get_only_auto_property_highlighting = warning +resharper_unassigned_readonly_field_highlighting = warning +resharper_unclosed_script_highlighting = error +resharper_unnecessary_whitespace_highlighting = warning +resharper_unreachable_switch_arm_due_to_integer_analysis_highlighting = warning +resharper_unreachable_switch_case_due_to_integer_analysis_highlighting = warning +resharper_unreal_header_tool_error_highlighting = error +resharper_unreal_header_tool_warning_highlighting = warning +resharper_unsupported_required_base_type_highlighting = warning +resharper_unused_anonymous_method_signature_highlighting = warning +resharper_unused_auto_property_accessor_global_highlighting = warning +resharper_unused_auto_property_accessor_local_highlighting = warning +resharper_unused_import_clause_highlighting = warning +resharper_unused_local_function_highlighting = warning +resharper_unused_local_function_parameter_highlighting = warning +resharper_unused_local_function_return_value_highlighting = warning +resharper_unused_member_global_highlighting = warning +resharper_unused_member_hierarchy_global_highlighting = warning +resharper_unused_member_hierarchy_local_highlighting = warning +resharper_unused_member_in_super_global_highlighting = warning +resharper_unused_member_in_super_local_highlighting = warning +resharper_unused_member_local_highlighting = warning +resharper_unused_method_return_value_global_highlighting = warning +resharper_unused_method_return_value_local_highlighting = warning +resharper_unused_parameter_global_highlighting = warning +resharper_unused_parameter_in_partial_method_highlighting = warning +resharper_unused_parameter_local_highlighting = warning +resharper_unused_tuple_component_in_return_value_highlighting = warning +resharper_unused_type_global_highlighting = warning +resharper_unused_type_local_highlighting = warning +resharper_unused_type_parameter_highlighting = warning +resharper_unused_variable_highlighting = warning +resharper_useless_binary_operation_highlighting = warning +resharper_useless_comparison_to_integral_constant_highlighting = warning +resharper_use_array_creation_expression_1_highlighting = warning +resharper_use_array_creation_expression_2_highlighting = warning +resharper_use_array_empty_method_highlighting = warning +resharper_use_await_using_highlighting = warning +resharper_use_cancellation_token_for_i_async_enumerable_highlighting = warning +resharper_use_collection_count_property_highlighting = warning +resharper_use_configure_await_false_for_async_disposable_highlighting = warning +resharper_use_configure_await_false_highlighting = warning +resharper_use_deconstruction_highlighting = warning +resharper_use_empty_types_field_highlighting = warning +resharper_use_event_args_empty_field_highlighting = warning +resharper_use_format_specifier_in_format_string_highlighting = warning +resharper_use_implicitly_typed_variable_evident_highlighting = warning +resharper_use_implicitly_typed_variable_highlighting = warning +resharper_use_implicit_by_val_modifier_highlighting = warning +resharper_use_indexed_property_highlighting = warning +resharper_use_index_from_end_expression_highlighting = warning +resharper_use_is_operator_1_highlighting = warning +resharper_use_is_operator_2_highlighting = warning +resharper_use_method_any_0_highlighting = warning +resharper_use_method_any_1_highlighting = warning +resharper_use_method_any_2_highlighting = warning +resharper_use_method_any_3_highlighting = warning +resharper_use_method_any_4_highlighting = warning +resharper_use_method_is_instance_of_type_highlighting = warning +resharper_use_nameof_expression_for_part_of_the_string_highlighting = warning +resharper_use_nameof_expression_highlighting = warning +resharper_use_nameof_for_dependency_property_highlighting = warning +resharper_use_name_of_instead_of_type_of_highlighting = warning +resharper_use_negated_pattern_in_is_expression_highlighting = warning +resharper_use_negated_pattern_matching_highlighting = warning +resharper_use_nullable_annotation_instead_of_attribute_highlighting = warning +resharper_use_nullable_attributes_supported_by_compiler_highlighting = warning +resharper_use_nullable_reference_types_annotation_syntax_highlighting = warning +resharper_use_null_propagation_highlighting = warning +resharper_use_object_or_collection_initializer_highlighting = warning +resharper_use_pattern_matching_highlighting = warning +resharper_use_positional_deconstruction_pattern_highlighting = warning +resharper_use_string_interpolation_highlighting = warning +resharper_use_string_interpolation_when_possible_highlighting = warning +resharper_use_switch_case_pattern_variable_highlighting = warning +resharper_use_throw_if_null_method_highlighting = warning +resharper_use_unsigned_right_shift_operator_highlighting = warning +resharper_use_verbatim_string_highlighting = warning +resharper_use_with_expression_to_copy_anonymous_object_highlighting = warning +resharper_use_with_expression_to_copy_record_highlighting = warning +resharper_use_with_expression_to_copy_struct_highlighting = warning +resharper_use_with_expression_to_copy_tuple_highlighting = warning +resharper_value_parameter_not_used_highlighting = warning +resharper_value_range_attribute_violation_highlighting = warning +resharper_variable_can_be_not_nullable_highlighting = warning +resharper_variable_hides_outer_variable_highlighting = warning +resharper_virtual_member_call_in_constructor_highlighting = warning +resharper_virtual_member_never_overridden_global_highlighting = warning +resharper_virtual_member_never_overridden_local_highlighting = warning +resharper_void_method_with_must_use_return_value_attribute_highlighting = warning +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_module_qualification_resolve_highlighting = warning +resharper_web_config_redundant_add_namespace_tag_highlighting = warning +resharper_web_config_redundant_location_tag_highlighting = warning +resharper_web_config_tag_prefix_redundand_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_unused_add_tag_highlighting = warning +resharper_web_config_unused_element_due_to_config_source_attribute_highlighting = warning +resharper_web_config_unused_remove_or_clear_tag_highlighting = warning +resharper_web_config_web_config_path_warning_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning +resharper_web_ignored_path_highlighting = warning +resharper_web_mapped_path_highlighting = warning +resharper_with_expression_instead_of_initializer_highlighting = warning +resharper_with_expression_modifies_all_members_highlighting = warning +resharper_wrong_indent_size_highlighting = warning +resharper_xaml_assign_null_to_not_null_attribute_highlighting = warning +resharper_xaml_avalonia_wrong_binding_mode_for_stream_binding_operator_highlighting = warning +resharper_xaml_binding_without_context_not_resolved_highlighting = warning +resharper_xaml_binding_with_context_not_resolved_highlighting = warning +resharper_xaml_compiled_binding_missing_data_type_error_highlighting_highlighting = error +resharper_xaml_constructor_warning_highlighting = warning +resharper_xaml_decimal_parsing_is_culture_dependent_highlighting = warning +resharper_xaml_dependency_property_resolve_error_highlighting = warning +resharper_xaml_duplicate_style_setter_highlighting = warning +resharper_xaml_dynamic_resource_error_highlighting = error +resharper_xaml_element_name_reference_not_resolved_highlighting = error +resharper_xaml_empty_grid_length_definition_highlighting = error +resharper_xaml_field_modifier_requires_name_attribute_highlighting = warning +resharper_xaml_grid_definitions_can_be_converted_to_attribute_highlighting = warning +resharper_xaml_ignored_path_highlighting_highlighting = warning +resharper_xaml_index_out_of_grid_definition_highlighting = warning +resharper_xaml_invalid_member_type_highlighting = error +resharper_xaml_invalid_resource_target_type_highlighting = error +resharper_xaml_invalid_resource_type_highlighting = error +resharper_xaml_invalid_type_highlighting = error +resharper_xaml_language_level_highlighting = error +resharper_xaml_mapped_path_highlighting_highlighting = warning +resharper_xaml_method_arguments_will_be_ignored_highlighting = warning +resharper_xaml_missing_grid_index_highlighting = warning +resharper_xaml_overloads_collision_highlighting = warning +resharper_xaml_parent_is_out_of_current_component_tree_highlighting = warning +resharper_xaml_path_error_highlighting = warning +resharper_xaml_possible_null_reference_exception_highlighting = warning +resharper_xaml_redundant_attached_property_highlighting = warning +resharper_xaml_redundant_binding_mode_attribute_highlighting = warning +resharper_xaml_redundant_collection_property_highlighting = warning +resharper_xaml_redundant_freeze_attribute_highlighting = warning +resharper_xaml_redundant_grid_definitions_highlighting = warning +resharper_xaml_redundant_grid_span_highlighting = warning +resharper_xaml_redundant_modifiers_attribute_highlighting = warning +resharper_xaml_redundant_namespace_alias_highlighting = warning +resharper_xaml_redundant_name_attribute_highlighting = warning +resharper_xaml_redundant_property_type_qualifier_highlighting = warning +resharper_xaml_redundant_resource_highlighting = warning +resharper_xaml_redundant_styled_value_highlighting = warning +resharper_xaml_redundant_update_source_trigger_attribute_highlighting = warning +resharper_xaml_redundant_xamarin_forms_class_declaration_highlighting = warning +resharper_xaml_resource_file_path_case_mismatch_highlighting = warning +resharper_xaml_routed_event_resolve_error_highlighting = warning +resharper_xaml_static_resource_not_resolved_highlighting = warning +resharper_xaml_style_class_not_found_highlighting = warning +resharper_xaml_style_invalid_target_type_highlighting = error +resharper_xaml_unexpected_element_highlighting = error +resharper_xaml_unexpected_text_token_highlighting = error +resharper_xaml_xaml_duplicate_device_family_type_view_highlighting_highlighting = error +resharper_xaml_xaml_mismatched_device_family_view_clr_name_highlighting_highlighting = warning +resharper_xaml_xaml_relative_source_default_mode_warning_highlighting_highlighting = warning +resharper_xaml_xaml_unknown_device_family_type_highlighting_highlighting = warning +resharper_xaml_xaml_xamarin_forms_data_type_and_binding_context_type_mismatched_highlighting_highlighting = warning +resharper_xaml_x_key_attribute_disallowed_highlighting = error +resharper_xunit_xunit_test_with_console_output_highlighting = warning +resharper_zero_index_from_end_highlighting = warning + +[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,fx,fxh,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/locale/Messages.resx b/locale/Messages.resx index 974d87f..f463a3b 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -47,46 +47,48 @@ : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> - - + + - + - - - - + + + + - - + + - - + + - - - - + + + + - + - + @@ -100,10 +102,14 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + I'm ready! diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index b5eacac..d045bc3 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -47,46 +47,48 @@ : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> - - + + - + - - - - + + + + - - + + - - + + - - - - + + + + - + - + @@ -100,10 +102,14 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Я запустился! diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index d7d7520..1909d27 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -47,46 +47,48 @@ : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> - - + + - + - - - - + + + + - - + + - - + + - - - - + + + + - + - + @@ -100,10 +102,14 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + я родился! diff --git a/src/Boyfriend.cs b/src/Boyfriend.cs index 322b65a..a1b6fbd 100644 --- a/src/Boyfriend.cs +++ b/src/Boyfriend.cs @@ -22,11 +22,13 @@ using Serilog.Extensions.Logging; namespace Boyfriend; -public class Boyfriend { +public sealed class Boyfriend +{ public static readonly AllowedMentions NoMentions = new( Array.Empty(), Array.Empty(), Array.Empty()); - public static async Task Main(string[] args) { + public static async Task Main(string[] args) + { var host = CreateHostBuilder(args).UseConsoleLifetime().Build(); var services = host.Services; @@ -40,10 +42,12 @@ public class Boyfriend { await host.RunAsync(); } - private static IHostBuilder CreateHostBuilder(string[] args) { + private static IHostBuilder CreateHostBuilder(string[] args) + { return Host.CreateDefaultBuilder(args) .AddDiscordService( - services => { + services => + { var configuration = services.GetRequiredService(); return configuration.GetValue("BOT_TOKEN") @@ -52,13 +56,18 @@ public class Boyfriend { + "BOT_TOKEN environment variable to a valid token."); } ).ConfigureServices( - (_, services) => { + (_, services) => + { services.Configure( - options => options.Intents |= GatewayIntents.MessageContents - | GatewayIntents.GuildMembers - | GatewayIntents.GuildScheduledEvents); + options => + { + options.Intents |= GatewayIntents.MessageContents + | GatewayIntents.GuildMembers + | GatewayIntents.GuildScheduledEvents; + }); services.Configure( - cSettings => { + cSettings => + { cSettings.SetDefaultAbsoluteExpiration(TimeSpan.FromHours(1)); cSettings.SetDefaultSlidingExpiration(TimeSpan.FromMinutes(30)); cSettings.SetAbsoluteExpiration(TimeSpan.FromDays(7)); @@ -92,12 +101,15 @@ public class Boyfriend { var responderTypes = typeof(Boyfriend).Assembly .GetExportedTypes() .Where(t => t.IsResponder()); - foreach (var responderType in responderTypes) services.AddResponder(responderType); + foreach (var responderType in responderTypes) + { + services.AddResponder(responderType); + } } ).ConfigureLogging( c => c.AddConsole() .AddFile("Logs/Boyfriend-{Date}.log", - outputTemplate: "{Timestamp:o} [{Level:u4}] {Message} {NewLine}{Exception}") + outputTemplate: "{Timestamp:o} [{Level:u4}] {Message} {NewLine}{Exception}") .AddFilter("System.Net.Http.HttpClient.*.LogicalHandler", LogLevel.Warning) .AddFilter("System.Net.Http.HttpClient.*.ClientHandler", LogLevel.Warning) .AddFilter("System.Net.Http.HttpClient.*.LogicalHandler", LogLevel.Warning) diff --git a/src/ColorsList.cs b/src/ColorsList.cs index bdd5bce..fc32274 100644 --- a/src/ColorsList.cs +++ b/src/ColorsList.cs @@ -5,14 +5,15 @@ namespace Boyfriend; /// /// Contains all colors used in embeds. /// -public static class ColorsList { +public static class ColorsList +{ public static readonly Color Default = Color.Gray; - public static readonly Color Red = Color.Firebrick; - public static readonly Color Green = Color.PaleGreen; - public static readonly Color Yellow = Color.Gold; - public static readonly Color Blue = Color.RoyalBlue; + public static readonly Color Red = Color.Firebrick; + public static readonly Color Green = Color.PaleGreen; + public static readonly Color Yellow = Color.Gold; + public static readonly Color Blue = Color.RoyalBlue; public static readonly Color Magenta = Color.Orchid; - public static readonly Color Cyan = Color.LightSkyBlue; - public static readonly Color Black = Color.Black; - public static readonly Color White = Color.WhiteSmoke; + public static readonly Color Cyan = Color.LightSkyBlue; + public static readonly Color Black = Color.Black; + public static readonly Color White = Color.WhiteSmoke; } diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs index a41cf11..34ba68d 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/src/Commands/AboutCommandGroup.cs @@ -21,19 +21,21 @@ namespace Boyfriend.Commands; /// Handles the command to show information about this bot: /about. /// [UsedImplicitly] -public class AboutCommandGroup : CommandGroup { - private static readonly string[] Developers = { "Octol1ttle", "mctaylors", "neroduckale" }; - private readonly ICommandContext _context; - private readonly GuildDataService _dataService; - private readonly FeedbackService _feedbackService; - private readonly IDiscordRestUserAPI _userApi; +public class AboutCommandGroup : CommandGroup +{ + private static readonly string[] Developers = { "Octol1ttle", "mctaylors", "neroduckale" }; + private readonly ICommandContext _context; + private readonly FeedbackService _feedback; + private readonly GuildDataService _guildData; + private readonly IDiscordRestUserAPI _userApi; public AboutCommandGroup( - ICommandContext context, GuildDataService dataService, - FeedbackService feedbackService, IDiscordRestUserAPI userApi) { + ICommandContext context, GuildDataService guildData, + FeedbackService feedback, IDiscordRestUserAPI userApi) + { _context = context; - _dataService = dataService; - _feedbackService = feedbackService; + _guildData = guildData; + _feedback = feedback; _userApi = userApi; } @@ -48,24 +50,32 @@ public class AboutCommandGroup : CommandGroup { [RequireContext(ChannelContext.Guild)] [Description("Shows Boyfriend's developers")] [UsedImplicitly] - public async Task ExecuteAboutAsync() { + public async Task ExecuteAboutAsync() + { if (!_context.TryGetContextIDs(out var guildId, out _, out _)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } - var cfg = await _dataService.GetSettings(guildId, CancellationToken); + var cfg = await _guildData.GetSettings(guildId, CancellationToken); Messages.Culture = GuildSettings.Language.Get(cfg); return await SendAboutBotAsync(currentUser, CancellationToken); } - private async Task SendAboutBotAsync(IUser currentUser, CancellationToken ct = default) { + private async Task SendAboutBotAsync(IUser currentUser, CancellationToken ct = default) + { var builder = new StringBuilder().AppendLine(Markdown.Bold(Messages.AboutTitleDevelopers)); foreach (var dev in Developers) + { builder.AppendLine($"@{dev} — {$"AboutDeveloper@{dev}".Localized()}"); + } builder.AppendLine() .AppendLine(Markdown.Bold(Messages.AboutTitleWiki)) @@ -77,6 +87,6 @@ public class AboutCommandGroup : CommandGroup { .WithImageUrl("https://cdn.upload.systems/uploads/JFAaX5vr.png") .Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } } diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs index 465f8d1..ea03009 100644 --- a/src/Commands/BanCommandGroup.cs +++ b/src/Commands/BanCommandGroup.cs @@ -22,23 +22,25 @@ namespace Boyfriend.Commands; /// Handles commands related to ban management: /ban and /unban. /// [UsedImplicitly] -public class BanCommandGroup : CommandGroup { +public class BanCommandGroup : CommandGroup +{ private readonly IDiscordRestChannelAPI _channelApi; - private readonly ICommandContext _context; - private readonly GuildDataService _dataService; - private readonly FeedbackService _feedbackService; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly IDiscordRestUserAPI _userApi; - private readonly UtilityService _utility; + private readonly ICommandContext _context; + private readonly FeedbackService _feedback; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly GuildDataService _guildData; + private readonly IDiscordRestUserAPI _userApi; + private readonly UtilityService _utility; public BanCommandGroup( - ICommandContext context, IDiscordRestChannelAPI channelApi, GuildDataService dataService, - FeedbackService feedbackService, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, - UtilityService utility) { + ICommandContext context, IDiscordRestChannelAPI channelApi, GuildDataService guildData, + FeedbackService feedback, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, + UtilityService utility) + { _context = context; _channelApi = channelApi; - _dataService = dataService; - _feedbackService = feedbackService; + _guildData = guildData; + _feedback = feedback; _guildApi = guildApi; _userApi = userApi; _utility = utility; @@ -67,23 +69,35 @@ public class BanCommandGroup : CommandGroup { [Description("Ban user")] [UsedImplicitly] public async Task ExecuteBanAsync( - [Description("User to ban")] IUser target, - [Description("Ban reason")] string reason, - [Description("Ban duration")] TimeSpan? duration = null) { + [Description("User to ban")] IUser target, + [Description("Ban reason")] string reason, + [Description("Ban duration")] TimeSpan? duration = null) + { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } + // The current user's avatar is used when sending error messages var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } + var userResult = await _userApi.GetUserAsync(userId, CancellationToken); if (!userResult.IsDefined(out var user)) + { return Result.FromError(userResult); + } + var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); if (!guildResult.IsDefined(out var guild)) + { return Result.FromError(guildResult); + } - var data = await _dataService.GetData(guild.ID, CancellationToken); + var data = await _guildData.GetData(guild.ID, CancellationToken); Messages.Culture = GuildSettings.Language.Get(data.Settings); return await BanUserAsync( @@ -91,39 +105,48 @@ public class BanCommandGroup : CommandGroup { } private async Task BanUserAsync( - IUser target, string reason, TimeSpan? duration, IGuild guild, GuildData data, Snowflake channelId, - IUser user, IUser currentUser, CancellationToken ct = default) { + IUser target, string reason, TimeSpan? duration, IGuild guild, GuildData data, Snowflake channelId, + IUser user, IUser currentUser, CancellationToken ct = default) + { var existingBanResult = await _guildApi.GetGuildBanAsync(guild.ID, target.ID, ct); - if (existingBanResult.IsDefined()) { + if (existingBanResult.IsDefined()) + { var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserAlreadyBanned, currentUser) .WithColour(ColorsList.Red).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(failedEmbed, ct); + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct); } var interactionResult = await _utility.CheckInteractionsAsync(guild.ID, user.ID, target.ID, "Ban", ct); if (!interactionResult.IsSuccess) + { return Result.FromError(interactionResult); + } - if (interactionResult.Entity is not null) { + if (interactionResult.Entity is not null) + { var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, currentUser) .WithColour(ColorsList.Red).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(errorEmbed, ct); + return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct); } var builder = new StringBuilder().AppendLine(string.Format(Messages.DescriptionActionReason, reason)); if (duration is not null) + { builder.Append( string.Format( Messages.DescriptionActionExpiresAt, Markdown.Timestamp(DateTimeOffset.UtcNow.Add(duration.Value)))); + } + var title = string.Format(Messages.UserBanned, target.GetTag()); var description = builder.ToString(); var dmChannelResult = await _userApi.CreateDMAsync(target.ID, ct); - if (dmChannelResult.IsDefined(out var dmChannel)) { + if (dmChannelResult.IsDefined(out var dmChannel)) + { var dmEmbed = new EmbedBuilder().WithGuildTitle(guild) .WithTitle(Messages.YouWereBanned) .WithDescription(description) @@ -133,7 +156,10 @@ public class BanCommandGroup : CommandGroup { .Build(); if (!dmEmbed.IsDefined(out var dmBuilt)) + { return Result.FromError(dmEmbed); + } + await _channelApi.CreateMessageAsync(dmChannel.ID, embeds: new[] { dmBuilt }, ct: ct); } @@ -141,7 +167,10 @@ public class BanCommandGroup : CommandGroup { guild.ID, target.ID, reason: $"({user.GetTag()}) {reason}".EncodeHeader(), ct: ct); if (!banResult.IsSuccess) + { return Result.FromError(banResult.Error); + } + var memberData = data.GetMemberData(target.ID); memberData.BannedUntil = duration is not null ? DateTimeOffset.UtcNow.Add(duration.Value) : DateTimeOffset.MaxValue; @@ -154,9 +183,11 @@ public class BanCommandGroup : CommandGroup { var logResult = _utility.LogActionAsync( data.Settings, channelId, user, title, description, target, ColorsList.Red, ct: ct); if (!logResult.IsSuccess) + { return Result.FromError(logResult.Error); + } - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } /// @@ -172,7 +203,7 @@ public class BanCommandGroup : CommandGroup { /// was unbanned and vice-versa. /// /// - /// + /// [Command("unban")] [DiscordDefaultMemberPermissions(DiscordPermission.BanMembers)] [DiscordDefaultDMPermission(false)] @@ -182,20 +213,29 @@ public class BanCommandGroup : CommandGroup { [Description("Unban user")] [UsedImplicitly] public async Task ExecuteUnban( - [Description("User to unban")] IUser target, - [Description("Unban reason")] string reason) { + [Description("User to unban")] IUser target, + [Description("Unban reason")] string reason) + { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } + // The current user's avatar is used when sending error messages var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } + // Needed to get the tag and avatar var userResult = await _userApi.GetUserAsync(userId, CancellationToken); if (!userResult.IsDefined(out var user)) + { return Result.FromError(userResult); + } - var data = await _dataService.GetData(guildId, CancellationToken); + var data = await _guildData.GetData(guildId, CancellationToken); Messages.Culture = GuildSettings.Language.Get(data.Settings); return await UnbanUserAsync( @@ -203,21 +243,25 @@ public class BanCommandGroup : CommandGroup { } private async Task UnbanUserAsync( - IUser target, string reason, Snowflake guildId, GuildData data, Snowflake channelId, IUser user, - IUser currentUser, CancellationToken ct = default) { + IUser target, string reason, Snowflake guildId, GuildData data, Snowflake channelId, IUser user, + IUser currentUser, CancellationToken ct = default) + { var existingBanResult = await _guildApi.GetGuildBanAsync(guildId, target.ID, ct); - if (!existingBanResult.IsDefined()) { + if (!existingBanResult.IsDefined()) + { var errorEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserNotBanned, currentUser) .WithColour(ColorsList.Red).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(errorEmbed, ct); + return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct); } var unbanResult = await _guildApi.RemoveGuildBanAsync( guildId, target.ID, $"({user.GetTag()}) {reason}".EncodeHeader(), ct); if (!unbanResult.IsSuccess) + { return Result.FromError(unbanResult.Error); + } var embed = new EmbedBuilder().WithSmallTitle( string.Format(Messages.UserUnbanned, target.GetTag()), target) @@ -228,8 +272,10 @@ public class BanCommandGroup : CommandGroup { var logResult = _utility.LogActionAsync( data.Settings, channelId, user, title, description, target, ColorsList.Green, ct: ct); if (!logResult.IsSuccess) + { return Result.FromError(logResult.Error); + } - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } } diff --git a/src/Commands/ClearCommandGroup.cs b/src/Commands/ClearCommandGroup.cs index 0c241e3..f9ac630 100644 --- a/src/Commands/ClearCommandGroup.cs +++ b/src/Commands/ClearCommandGroup.cs @@ -22,21 +22,23 @@ namespace Boyfriend.Commands; /// Handles the command to clear messages in a channel: /clear. /// [UsedImplicitly] -public class ClearCommandGroup : CommandGroup { +public class ClearCommandGroup : CommandGroup +{ private readonly IDiscordRestChannelAPI _channelApi; - private readonly ICommandContext _context; - private readonly GuildDataService _dataService; - private readonly FeedbackService _feedbackService; - private readonly IDiscordRestUserAPI _userApi; - private readonly UtilityService _utility; + private readonly ICommandContext _context; + private readonly FeedbackService _feedback; + private readonly GuildDataService _guildData; + private readonly IDiscordRestUserAPI _userApi; + private readonly UtilityService _utility; public ClearCommandGroup( - IDiscordRestChannelAPI channelApi, ICommandContext context, GuildDataService dataService, - FeedbackService feedbackService, IDiscordRestUserAPI userApi, UtilityService utility) { + IDiscordRestChannelAPI channelApi, ICommandContext context, GuildDataService guildData, + FeedbackService feedback, IDiscordRestUserAPI userApi, UtilityService utility) + { _channelApi = channelApi; _context = context; - _dataService = dataService; - _feedbackService = feedbackService; + _guildData = guildData; + _feedback = feedback; _userApi = userApi; _utility = utility; } @@ -59,34 +61,47 @@ public class ClearCommandGroup : CommandGroup { [UsedImplicitly] public async Task ExecuteClear( [Description("Number of messages to remove (2-100)")] [MinValue(2)] [MaxValue(100)] - int amount) { + int amount) + { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } var messagesResult = await _channelApi.GetChannelMessagesAsync( channelId, limit: amount + 1, ct: CancellationToken); if (!messagesResult.IsDefined(out var messages)) + { return Result.FromError(messagesResult); + } + var userResult = await _userApi.GetUserAsync(userId, CancellationToken); if (!userResult.IsDefined(out var user)) + { return Result.FromError(userResult); + } + // The current user's avatar is used when sending messages var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } - var data = await _dataService.GetData(guildId, CancellationToken); + var data = await _guildData.GetData(guildId, CancellationToken); Messages.Culture = GuildSettings.Language.Get(data.Settings); return await ClearMessagesAsync(amount, data, channelId, messages, user, currentUser, CancellationToken); } private async Task ClearMessagesAsync( - int amount, GuildData data, Snowflake channelId, IReadOnlyList messages, - IUser user, IUser currentUser, CancellationToken ct = default) { + int amount, GuildData data, Snowflake channelId, IReadOnlyList messages, + IUser user, IUser currentUser, CancellationToken ct = default) + { var idList = new List(messages.Count); var builder = new StringBuilder().AppendLine(Mention.Channel(channelId)).AppendLine(); - for (var i = messages.Count - 1; i >= 1; i--) { // '>= 1' to skip last message ('Boyfriend is thinking...') + for (var i = messages.Count - 1; i >= 1; i--) // '>= 1' to skip last message ('Boyfriend is thinking...') + { var message = messages[i]; idList.Add(message.ID); builder.AppendLine(string.Format(Messages.MessageFrom, Mention.User(message.Author))); @@ -99,16 +114,20 @@ public class ClearCommandGroup : CommandGroup { var deleteResult = await _channelApi.BulkDeleteMessagesAsync( channelId, idList, user.GetTag().EncodeHeader(), ct); if (!deleteResult.IsSuccess) + { return Result.FromError(deleteResult.Error); + } var logResult = _utility.LogActionAsync( data.Settings, channelId, user, title, description, currentUser, ColorsList.Red, false, ct); if (!logResult.IsSuccess) + { return Result.FromError(logResult.Error); + } var embed = new EmbedBuilder().WithSmallTitle(title, currentUser) .WithColour(ColorsList.Green).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } } diff --git a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs index 51c2a8d..009bfa1 100644 --- a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs +++ b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs @@ -11,10 +11,12 @@ namespace Boyfriend.Commands.Events; /// Handles error logging for slash command groups. /// [UsedImplicitly] -public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent { +public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent +{ private readonly ILogger _logger; - public ErrorLoggingPostExecutionEvent(ILogger logger) { + public ErrorLoggingPostExecutionEvent(ILogger logger) + { _logger = logger; } @@ -27,11 +29,15 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent { /// The cancellation token for this operation. Unused. /// A result which has succeeded. public Task AfterExecutionAsync( - ICommandContext context, IResult commandResult, CancellationToken ct = default) { - if (!commandResult.IsSuccess && !commandResult.Error.IsUserOrEnvironmentError()) { + ICommandContext context, IResult commandResult, CancellationToken ct = default) + { + if (!commandResult.IsSuccess && !commandResult.Error.IsUserOrEnvironmentError()) + { _logger.LogWarning("Error in slash command execution.\n{ErrorMessage}", commandResult.Error.Message); if (commandResult.Error is ExceptionError exerr) + { _logger.LogError(exerr.Exception, "An exception has been thrown"); + } } return Task.FromResult(Result.FromSuccess()); diff --git a/src/Commands/Events/LoggingPreparationErrorEvent.cs b/src/Commands/Events/LoggingPreparationErrorEvent.cs index 7e8b2bb..f6c1e1f 100644 --- a/src/Commands/Events/LoggingPreparationErrorEvent.cs +++ b/src/Commands/Events/LoggingPreparationErrorEvent.cs @@ -11,10 +11,12 @@ namespace Boyfriend.Commands.Events; /// Handles error logging for slash commands that couldn't be successfully prepared. /// [UsedImplicitly] -public class LoggingPreparationErrorEvent : IPreparationErrorEvent { +public class LoggingPreparationErrorEvent : IPreparationErrorEvent +{ private readonly ILogger _logger; - public LoggingPreparationErrorEvent(ILogger logger) { + public LoggingPreparationErrorEvent(ILogger logger) + { _logger = logger; } @@ -27,11 +29,15 @@ public class LoggingPreparationErrorEvent : IPreparationErrorEvent { /// The cancellation token for this operation. Unused. /// A result which has succeeded. public Task PreparationFailed( - IOperationContext context, IResult preparationResult, CancellationToken ct = default) { - if (!preparationResult.IsSuccess && !preparationResult.Error.IsUserOrEnvironmentError()) { + IOperationContext context, IResult preparationResult, CancellationToken ct = default) + { + if (!preparationResult.IsSuccess && !preparationResult.Error.IsUserOrEnvironmentError()) + { _logger.LogWarning("Error in slash command preparation.\n{ErrorMessage}", preparationResult.Error.Message); if (preparationResult.Error is ExceptionError exerr) + { _logger.LogError(exerr.Exception, "An exception has been thrown"); + } } return Task.FromResult(Result.FromSuccess()); diff --git a/src/Commands/KickCommandGroup.cs b/src/Commands/KickCommandGroup.cs index 5d7909f..45da191 100644 --- a/src/Commands/KickCommandGroup.cs +++ b/src/Commands/KickCommandGroup.cs @@ -20,23 +20,25 @@ namespace Boyfriend.Commands; /// Handles the command to kick members of a guild: /kick. /// [UsedImplicitly] -public class KickCommandGroup : CommandGroup { +public class KickCommandGroup : CommandGroup +{ private readonly IDiscordRestChannelAPI _channelApi; - private readonly ICommandContext _context; - private readonly GuildDataService _dataService; - private readonly FeedbackService _feedbackService; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly IDiscordRestUserAPI _userApi; - private readonly UtilityService _utility; + private readonly ICommandContext _context; + private readonly FeedbackService _feedback; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly GuildDataService _guildData; + private readonly IDiscordRestUserAPI _userApi; + private readonly UtilityService _utility; public KickCommandGroup( - ICommandContext context, IDiscordRestChannelAPI channelApi, GuildDataService dataService, - FeedbackService feedbackService, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, - UtilityService utility) { + ICommandContext context, IDiscordRestChannelAPI channelApi, GuildDataService guildData, + FeedbackService feedback, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, + UtilityService utility) + { _context = context; _channelApi = channelApi; - _dataService = dataService; - _feedbackService = feedbackService; + _guildData = guildData; + _feedback = feedback; _guildApi = guildApi; _userApi = userApi; _utility = utility; @@ -63,30 +65,43 @@ public class KickCommandGroup : CommandGroup { [Description("Kick member")] [UsedImplicitly] public async Task ExecuteKick( - [Description("Member to kick")] IUser target, - [Description("Kick reason")] string reason) { + [Description("Member to kick")] IUser target, + [Description("Kick reason")] string reason) + { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } + // The current user's avatar is used when sending error messages var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } + var userResult = await _userApi.GetUserAsync(userId, CancellationToken); if (!userResult.IsDefined(out var user)) + { return Result.FromError(userResult); + } + var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); if (!guildResult.IsDefined(out var guild)) + { return Result.FromError(guildResult); + } - var data = await _dataService.GetData(guildId, CancellationToken); + var data = await _guildData.GetData(guildId, CancellationToken); Messages.Culture = GuildSettings.Language.Get(data.Settings); var memberResult = await _guildApi.GetGuildMemberAsync(guildId, target.ID, CancellationToken); - if (!memberResult.IsSuccess) { + if (!memberResult.IsSuccess) + { var embed = new EmbedBuilder().WithSmallTitle(Messages.UserNotFoundShort, currentUser) .WithColour(ColorsList.Red).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); + return await _feedback.SendContextualEmbedResultAsync(embed, CancellationToken); } return await KickUserAsync(target, reason, guild, channelId, data, user, currentUser, CancellationToken); @@ -94,21 +109,26 @@ public class KickCommandGroup : CommandGroup { private async Task KickUserAsync( IUser target, string reason, IGuild guild, Snowflake channelId, GuildData data, IUser user, IUser currentUser, - CancellationToken ct = default) { + CancellationToken ct = default) + { var interactionResult = await _utility.CheckInteractionsAsync(guild.ID, user.ID, target.ID, "Kick", ct); if (!interactionResult.IsSuccess) + { return Result.FromError(interactionResult); + } - if (interactionResult.Entity is not null) { + if (interactionResult.Entity is not null) + { var failedEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, currentUser) .WithColour(ColorsList.Red).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(failedEmbed, ct); + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct); } var dmChannelResult = await _userApi.CreateDMAsync(target.ID, ct); - if (dmChannelResult.IsDefined(out var dmChannel)) { + if (dmChannelResult.IsDefined(out var dmChannel)) + { var dmEmbed = new EmbedBuilder().WithGuildTitle(guild) .WithTitle(Messages.YouWereKicked) .WithDescription(string.Format(Messages.DescriptionActionReason, reason)) @@ -118,7 +138,10 @@ public class KickCommandGroup : CommandGroup { .Build(); if (!dmEmbed.IsDefined(out var dmBuilt)) + { return Result.FromError(dmEmbed); + } + await _channelApi.CreateMessageAsync(dmChannel.ID, embeds: new[] { dmBuilt }, ct: ct); } @@ -126,7 +149,10 @@ public class KickCommandGroup : CommandGroup { guild.ID, target.ID, $"({user.GetTag()}) {reason}".EncodeHeader(), ct); if (!kickResult.IsSuccess) + { return Result.FromError(kickResult.Error); + } + data.GetMemberData(target.ID).Roles.Clear(); var title = string.Format(Messages.UserKicked, target.GetTag()); @@ -134,12 +160,14 @@ public class KickCommandGroup : CommandGroup { var logResult = _utility.LogActionAsync( data.Settings, channelId, user, title, description, target, ColorsList.Red, ct: ct); if (!logResult.IsSuccess) + { return Result.FromError(logResult.Error); + } var embed = new EmbedBuilder().WithSmallTitle( string.Format(Messages.UserKicked, target.GetTag()), target) .WithColour(ColorsList.Green).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } } diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs index af967b9..a35699b 100644 --- a/src/Commands/MuteCommandGroup.cs +++ b/src/Commands/MuteCommandGroup.cs @@ -22,20 +22,22 @@ namespace Boyfriend.Commands; /// Handles commands related to mute management: /mute and /unmute. /// [UsedImplicitly] -public class MuteCommandGroup : CommandGroup { - private readonly ICommandContext _context; - private readonly GuildDataService _dataService; - private readonly FeedbackService _feedbackService; +public class MuteCommandGroup : CommandGroup +{ + private readonly ICommandContext _context; + private readonly FeedbackService _feedback; private readonly IDiscordRestGuildAPI _guildApi; - private readonly IDiscordRestUserAPI _userApi; - private readonly UtilityService _utility; + private readonly GuildDataService _guildData; + private readonly IDiscordRestUserAPI _userApi; + private readonly UtilityService _utility; public MuteCommandGroup( - ICommandContext context, GuildDataService dataService, FeedbackService feedbackService, - IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, UtilityService utility) { + ICommandContext context, GuildDataService guildData, FeedbackService feedback, + IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, UtilityService utility) + { _context = context; - _dataService = dataService; - _feedbackService = feedbackService; + _guildData = guildData; + _feedback = feedback; _guildApi = guildApi; _userApi = userApi; _utility = utility; @@ -64,30 +66,38 @@ public class MuteCommandGroup : CommandGroup { [Description("Mute member")] [UsedImplicitly] public async Task ExecuteMute( - [Description("Member to mute")] IUser target, - [Description("Mute reason")] string reason, - [Description("Mute duration")] TimeSpan duration) { + [Description("Member to mute")] IUser target, + [Description("Mute reason")] string reason, + [Description("Mute duration")] TimeSpan duration) + { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } // The current user's avatar is used when sending error messages var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } var userResult = await _userApi.GetUserAsync(userId, CancellationToken); if (!userResult.IsDefined(out var user)) + { return Result.FromError(userResult); + } - var data = await _dataService.GetData(guildId, CancellationToken); + var data = await _guildData.GetData(guildId, CancellationToken); Messages.Culture = GuildSettings.Language.Get(data.Settings); var memberResult = await _guildApi.GetGuildMemberAsync(guildId, target.ID, CancellationToken); - if (!memberResult.IsSuccess) { + if (!memberResult.IsSuccess) + { var embed = new EmbedBuilder().WithSmallTitle(Messages.UserNotFoundShort, currentUser) .WithColour(ColorsList.Red).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); + return await _feedback.SendContextualEmbedResultAsync(embed, CancellationToken); } return await MuteUserAsync( @@ -95,19 +105,23 @@ public class MuteCommandGroup : CommandGroup { } private async Task MuteUserAsync( - IUser target, string reason, TimeSpan duration, Snowflake guildId, GuildData data, Snowflake channelId, - IUser user, IUser currentUser, CancellationToken ct = default) { + IUser target, string reason, TimeSpan duration, Snowflake guildId, GuildData data, Snowflake channelId, + IUser user, IUser currentUser, CancellationToken ct = default) + { var interactionResult = await _utility.CheckInteractionsAsync( guildId, user.ID, target.ID, "Mute", ct); if (!interactionResult.IsSuccess) + { return Result.FromError(interactionResult); + } - if (interactionResult.Entity is not null) { + if (interactionResult.Entity is not null) + { var failedEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, currentUser) .WithColour(ColorsList.Red).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(failedEmbed, ct); + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct); } var until = DateTimeOffset.UtcNow.Add(duration); // >:) @@ -115,7 +129,9 @@ public class MuteCommandGroup : CommandGroup { guildId, target.ID, reason: $"({user.GetTag()}) {reason}".EncodeHeader(), communicationDisabledUntil: until, ct: ct); if (!muteResult.IsSuccess) + { return Result.FromError(muteResult.Error); + } var title = string.Format(Messages.UserMuted, target.GetTag()); var description = new StringBuilder().AppendLine(string.Format(Messages.DescriptionActionReason, reason)) @@ -126,13 +142,15 @@ public class MuteCommandGroup : CommandGroup { var logResult = _utility.LogActionAsync( data.Settings, channelId, user, title, description, target, ColorsList.Red, ct: ct); if (!logResult.IsSuccess) + { return Result.FromError(logResult.Error); + } var embed = new EmbedBuilder().WithSmallTitle( string.Format(Messages.UserMuted, target.GetTag()), target) .WithColour(ColorsList.Green).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } /// @@ -148,7 +166,7 @@ public class MuteCommandGroup : CommandGroup { /// was unmuted and vice-versa. /// /// - /// + /// [Command("unmute", "размут")] [DiscordDefaultMemberPermissions(DiscordPermission.ModerateMembers)] [DiscordDefaultDMPermission(false)] @@ -158,30 +176,38 @@ public class MuteCommandGroup : CommandGroup { [Description("Unmute member")] [UsedImplicitly] public async Task ExecuteUnmute( - [Description("Member to unmute")] IUser target, - [Description("Unmute reason")] string reason) { + [Description("Member to unmute")] IUser target, + [Description("Unmute reason")] string reason) + { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } // The current user's avatar is used when sending error messages var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } // Needed to get the tag and avatar var userResult = await _userApi.GetUserAsync(userId, CancellationToken); if (!userResult.IsDefined(out var user)) + { return Result.FromError(userResult); + } - var data = await _dataService.GetData(guildId, CancellationToken); + var data = await _guildData.GetData(guildId, CancellationToken); Messages.Culture = GuildSettings.Language.Get(data.Settings); var memberResult = await _guildApi.GetGuildMemberAsync(guildId, target.ID, CancellationToken); - if (!memberResult.IsSuccess) { + if (!memberResult.IsSuccess) + { var embed = new EmbedBuilder().WithSmallTitle(Messages.UserNotFoundShort, currentUser) .WithColour(ColorsList.Red).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); + return await _feedback.SendContextualEmbedResultAsync(embed, CancellationToken); } return await UnmuteUserAsync( @@ -189,38 +215,46 @@ public class MuteCommandGroup : CommandGroup { } private async Task UnmuteUserAsync( - IUser target, string reason, Snowflake guildId, GuildData data, Snowflake channelId, IUser user, - IUser currentUser, CancellationToken ct = default) { + IUser target, string reason, Snowflake guildId, GuildData data, Snowflake channelId, IUser user, + IUser currentUser, CancellationToken ct = default) + { var interactionResult = await _utility.CheckInteractionsAsync( guildId, user.ID, target.ID, "Unmute", ct); if (!interactionResult.IsSuccess) + { return Result.FromError(interactionResult); + } - if (interactionResult.Entity is not null) { + if (interactionResult.Entity is not null) + { var failedEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, currentUser) .WithColour(ColorsList.Red).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(failedEmbed, ct); + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct); } var unmuteResult = await _guildApi.ModifyGuildMemberAsync( guildId, target.ID, $"({user.GetTag()}) {reason}".EncodeHeader(), communicationDisabledUntil: null, ct: ct); if (!unmuteResult.IsSuccess) + { return Result.FromError(unmuteResult.Error); + } var title = string.Format(Messages.UserUnmuted, target.GetTag()); var description = string.Format(Messages.DescriptionActionReason, reason); var logResult = _utility.LogActionAsync( data.Settings, channelId, user, title, description, target, ColorsList.Green, ct: ct); if (!logResult.IsSuccess) + { return Result.FromError(logResult.Error); + } var embed = new EmbedBuilder().WithSmallTitle( string.Format(Messages.UserUnmuted, target.GetTag()), target) .WithColour(ColorsList.Green).Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } } diff --git a/src/Commands/PingCommandGroup.cs b/src/Commands/PingCommandGroup.cs index f301edb..e52f22c 100644 --- a/src/Commands/PingCommandGroup.cs +++ b/src/Commands/PingCommandGroup.cs @@ -21,22 +21,24 @@ namespace Boyfriend.Commands; /// Handles the command to get the time taken for the gateway to respond to the last heartbeat: /ping /// [UsedImplicitly] -public class PingCommandGroup : CommandGroup { +public class PingCommandGroup : CommandGroup +{ private readonly IDiscordRestChannelAPI _channelApi; - private readonly DiscordGatewayClient _client; - private readonly ICommandContext _context; - private readonly GuildDataService _dataService; - private readonly FeedbackService _feedbackService; - private readonly IDiscordRestUserAPI _userApi; + private readonly DiscordGatewayClient _client; + private readonly ICommandContext _context; + private readonly FeedbackService _feedback; + private readonly GuildDataService _guildData; + private readonly IDiscordRestUserAPI _userApi; public PingCommandGroup( - IDiscordRestChannelAPI channelApi, ICommandContext context, DiscordGatewayClient client, - GuildDataService dataService, FeedbackService feedbackService, IDiscordRestUserAPI userApi) { + IDiscordRestChannelAPI channelApi, ICommandContext context, DiscordGatewayClient client, + GuildDataService guildData, FeedbackService feedback, IDiscordRestUserAPI userApi) + { _channelApi = channelApi; _context = context; _client = client; - _dataService = dataService; - _feedbackService = feedbackService; + _guildData = guildData; + _feedback = feedback; _userApi = userApi; } @@ -51,29 +53,39 @@ public class PingCommandGroup : CommandGroup { [DiscordDefaultDMPermission(false)] [RequireContext(ChannelContext.Guild)] [UsedImplicitly] - public async Task ExecutePingAsync() { + public async Task ExecutePingAsync() + { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out _)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } - var cfg = await _dataService.GetSettings(guildId, CancellationToken); + var cfg = await _guildData.GetSettings(guildId, CancellationToken); Messages.Culture = GuildSettings.Language.Get(cfg); return await SendLatencyAsync(channelId, currentUser, CancellationToken); } private async Task SendLatencyAsync( - Snowflake channelId, IUser currentUser, CancellationToken ct = default) { + Snowflake channelId, IUser currentUser, CancellationToken ct = default) + { var latency = _client.Latency.TotalMilliseconds; - if (latency is 0) { + if (latency is 0) + { // No heartbeat has occurred, estimate latency from local time and "Boyfriend is thinking..." message var lastMessageResult = await _channelApi.GetChannelMessagesAsync( channelId, limit: 1, ct: ct); if (!lastMessageResult.IsDefined(out var lastMessage)) + { return Result.FromError(lastMessageResult); + } + latency = DateTimeOffset.UtcNow.Subtract(lastMessage.Single().Timestamp).TotalMilliseconds; } @@ -84,6 +96,6 @@ public class PingCommandGroup : CommandGroup { .WithCurrentTimestamp() .Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } } diff --git a/src/Commands/RemindCommandGroup.cs b/src/Commands/RemindCommandGroup.cs index 9734ab5..3f588aa 100644 --- a/src/Commands/RemindCommandGroup.cs +++ b/src/Commands/RemindCommandGroup.cs @@ -21,18 +21,20 @@ namespace Boyfriend.Commands; /// Handles the command to manage reminders: /remind /// [UsedImplicitly] -public class RemindCommandGroup : CommandGroup { - private readonly ICommandContext _context; - private readonly GuildDataService _dataService; - private readonly FeedbackService _feedbackService; +public class RemindCommandGroup : CommandGroup +{ + private readonly ICommandContext _context; + private readonly FeedbackService _feedback; + private readonly GuildDataService _guildData; private readonly IDiscordRestUserAPI _userApi; public RemindCommandGroup( - ICommandContext context, GuildDataService dataService, FeedbackService feedbackService, - IDiscordRestUserAPI userApi) { + ICommandContext context, GuildDataService guildData, FeedbackService feedback, + IDiscordRestUserAPI userApi) + { _context = context; - _dataService = dataService; - _feedbackService = feedbackService; + _guildData = guildData; + _feedback = feedback; _userApi = userApi; } @@ -50,27 +52,34 @@ public class RemindCommandGroup : CommandGroup { public async Task ExecuteReminderAsync( [Description("After what period of time mention the reminder")] TimeSpan @in, - [Description("Reminder message")] string message) { + [Description("Reminder message")] string message) + { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } var userResult = await _userApi.GetUserAsync(userId, CancellationToken); if (!userResult.IsDefined(out var user)) + { return Result.FromError(userResult); + } - var data = await _dataService.GetData(guildId, CancellationToken); + var data = await _guildData.GetData(guildId, CancellationToken); Messages.Culture = GuildSettings.Language.Get(data.Settings); return await AddReminderAsync(@in, message, data, channelId, user, CancellationToken); } private async Task AddReminderAsync( - TimeSpan @in, string message, GuildData data, - Snowflake channelId, IUser user, CancellationToken ct = default) { + TimeSpan @in, string message, GuildData data, + Snowflake channelId, IUser user, CancellationToken ct = default) + { var remindAt = DateTimeOffset.UtcNow.Add(@in); data.GetMemberData(user.ID).Reminders.Add( - new Reminder { + new Reminder + { At = remindAt, Channel = channelId.Value, Text = message @@ -81,6 +90,6 @@ public class RemindCommandGroup : CommandGroup { .WithColour(ColorsList.Green) .Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } } diff --git a/src/Commands/SettingsCommandGroup.cs b/src/Commands/SettingsCommandGroup.cs index 5a9efad..687d49f 100644 --- a/src/Commands/SettingsCommandGroup.cs +++ b/src/Commands/SettingsCommandGroup.cs @@ -23,8 +23,10 @@ namespace Boyfriend.Commands; /// Handles the commands to list and modify per-guild settings: /settings and /settings list. /// [UsedImplicitly] -public class SettingsCommandGroup : CommandGroup { - private static readonly IOption[] AllOptions = { +public class SettingsCommandGroup : CommandGroup +{ + private static readonly IOption[] AllOptions = + { GuildSettings.Language, GuildSettings.WelcomeMessage, GuildSettings.ReceiveStartupMessages, @@ -41,17 +43,18 @@ public class SettingsCommandGroup : CommandGroup { GuildSettings.EventEarlyNotificationOffset }; - private readonly ICommandContext _context; - private readonly GuildDataService _dataService; - private readonly FeedbackService _feedbackService; + private readonly ICommandContext _context; + private readonly FeedbackService _feedback; + private readonly GuildDataService _guildData; private readonly IDiscordRestUserAPI _userApi; public SettingsCommandGroup( - ICommandContext context, GuildDataService dataService, - FeedbackService feedbackService, IDiscordRestUserAPI userApi) { + ICommandContext context, GuildDataService guildData, + FeedbackService feedback, IDiscordRestUserAPI userApi) + { _context = context; - _dataService = dataService; - _feedbackService = feedbackService; + _guildData = guildData; + _feedback = feedback; _userApi = userApi; } @@ -69,21 +72,28 @@ public class SettingsCommandGroup : CommandGroup { [Description("Shows settings list for this server")] [UsedImplicitly] public async Task ExecuteSettingsListAsync( - [Description("Settings list page")] int page) { + [Description("Settings list page")] int page) + { if (!_context.TryGetContextIDs(out var guildId, out _, out _)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } - var cfg = await _dataService.GetSettings(guildId, CancellationToken); + var cfg = await _guildData.GetSettings(guildId, CancellationToken); Messages.Culture = GuildSettings.Language.Get(cfg); return await SendSettingsListAsync(cfg, currentUser, page, CancellationToken); } - private async Task SendSettingsListAsync(JsonNode cfg, IUser currentUser, int page, CancellationToken ct = default) { + private async Task SendSettingsListAsync(JsonNode cfg, IUser currentUser, int page, + CancellationToken ct = default) + { var description = new StringBuilder(); var footer = new StringBuilder(); @@ -93,38 +103,43 @@ public class SettingsCommandGroup : CommandGroup { var lastOptionOnPage = Math.Min(optionsPerPage * page, AllOptions.Length); var firstOptionOnPage = optionsPerPage * page - optionsPerPage; - if (firstOptionOnPage >= AllOptions.Length) { - var embed = new EmbedBuilder().WithSmallTitle(Messages.PageNotFound, currentUser) + if (firstOptionOnPage >= AllOptions.Length) + { + var errorEmbed = new EmbedBuilder().WithSmallTitle(Messages.PageNotFound, currentUser) .WithDescription(string.Format(Messages.PagesAllowed, Markdown.Bold(totalPages.ToString()))) .WithColour(ColorsList.Red) .Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); - } else { - footer.Append($"{Messages.Page} {page}/{totalPages} "); - for (var i = 0; i < totalPages; i++) footer.Append(i + 1 == page ? "●" : "○"); - - for (var i = firstOptionOnPage; i < lastOptionOnPage; i++) { - var optionName = AllOptions[i].Name; - var optionValue = AllOptions[i].Display(cfg); - - description.AppendLine($"- {$"Settings{optionName}".Localized()}") - .Append($" - {Markdown.InlineCode(optionName)}: ") - .AppendLine(optionValue); - } - - var embed = new EmbedBuilder().WithSmallTitle(Messages.SettingsListTitle, currentUser) - .WithDescription(description.ToString()) - .WithColour(ColorsList.Default) - .WithFooter(footer.ToString()) - .Build(); - - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct); } + + footer.Append($"{Messages.Page} {page}/{totalPages} "); + for (var i = 0; i < totalPages; i++) + { + footer.Append(i + 1 == page ? "●" : "○"); + } + + for (var i = firstOptionOnPage; i < lastOptionOnPage; i++) + { + var optionName = AllOptions[i].Name; + var optionValue = AllOptions[i].Display(cfg); + + description.AppendLine($"- {$"Settings{optionName}".Localized()}") + .Append($" - {Markdown.InlineCode(optionName)}: ") + .AppendLine(optionValue); + } + + var embed = new EmbedBuilder().WithSmallTitle(Messages.SettingsListTitle, currentUser) + .WithDescription(description.ToString()) + .WithColour(ColorsList.Default) + .WithFooter(footer.ToString()) + .Build(); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } /// - /// A slash command that modifies per-guild GuildSettings. + /// A slash command that modifies per-guild GuildSettings. /// /// The setting to modify. /// The new value of the setting. @@ -139,33 +154,40 @@ public class SettingsCommandGroup : CommandGroup { public async Task ExecuteSettingsAsync( [Description("The setting whose value you want to change")] string setting, - [Description("Setting value")] string value) { + [Description("Setting value")] string value) + { if (!_context.TryGetContextIDs(out var guildId, out _, out _)) + { return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } - var data = await _dataService.GetData(guildId, CancellationToken); + var data = await _guildData.GetData(guildId, CancellationToken); Messages.Culture = GuildSettings.Language.Get(data.Settings); return await EditSettingAsync(setting, value, data, currentUser, CancellationToken); } private async Task EditSettingAsync( - string setting, string value, GuildData data, IUser currentUser, CancellationToken ct = default) { + string setting, string value, GuildData data, IUser currentUser, CancellationToken ct = default) + { var option = AllOptions.Single( o => string.Equals(setting, o.Name, StringComparison.InvariantCultureIgnoreCase)); var setResult = option.Set(data.Settings, value); - if (!setResult.IsSuccess) { + if (!setResult.IsSuccess) + { var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.SettingNotChanged, currentUser) .WithDescription(setResult.Error.Message) .WithColour(ColorsList.Red) .Build(); - return await _feedbackService.SendContextualEmbedResultAsync(failedEmbed, ct); + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct); } var builder = new StringBuilder(); @@ -179,6 +201,6 @@ public class SettingsCommandGroup : CommandGroup { .WithColour(ColorsList.Green) .Build(); - return await _feedbackService.SendContextualEmbedResultAsync(embed, ct); + return await _feedback.SendContextualEmbedResultAsync(embed, ct); } } diff --git a/src/Data/GuildData.cs b/src/Data/GuildData.cs index 7c81364..5adb869 100644 --- a/src/Data/GuildData.cs +++ b/src/Data/GuildData.cs @@ -7,19 +7,21 @@ namespace Boyfriend.Data; /// Stores information about a guild. This information is not accessible via the Discord API. /// /// This information is stored on disk as a JSON file. -public class GuildData { +public sealed class GuildData +{ public readonly Dictionary MemberData; - public readonly string MemberDataPath; + public readonly string MemberDataPath; public readonly Dictionary ScheduledEvents; - public readonly string ScheduledEventsPath; - public readonly JsonNode Settings; - public readonly string SettingsPath; + public readonly string ScheduledEventsPath; + public readonly JsonNode Settings; + public readonly string SettingsPath; public GuildData( - JsonNode settings, string settingsPath, + JsonNode settings, string settingsPath, Dictionary scheduledEvents, string scheduledEventsPath, - Dictionary memberData, string memberDataPath) { + Dictionary memberData, string memberDataPath) + { Settings = settings; SettingsPath = settingsPath; ScheduledEvents = scheduledEvents; @@ -28,8 +30,12 @@ public class GuildData { MemberDataPath = memberDataPath; } - public MemberData GetMemberData(Snowflake userId) { - if (MemberData.TryGetValue(userId.Value, out var existing)) return existing; + public MemberData GetMemberData(Snowflake userId) + { + if (MemberData.TryGetValue(userId.Value, out var existing)) + { + return existing; + } var newData = new MemberData(userId.Value, null); MemberData.Add(userId.Value, newData); diff --git a/src/Data/GuildSettings.cs b/src/Data/GuildSettings.cs index bd5757d..1c94835 100644 --- a/src/Data/GuildSettings.cs +++ b/src/Data/GuildSettings.cs @@ -8,7 +8,8 @@ namespace Boyfriend.Data; /// Contains all per-guild settings that can be set by a member /// with using the /settings command /// -public static class GuildSettings { +public static class GuildSettings +{ public static readonly LanguageOption Language = new("Language", "en"); /// @@ -56,9 +57,9 @@ public static class GuildSettings { public static readonly SnowflakeOption PrivateFeedbackChannel = new("PrivateFeedbackChannel"); public static readonly SnowflakeOption EventNotificationChannel = new("EventNotificationChannel"); - public static readonly SnowflakeOption DefaultRole = new("DefaultRole"); - public static readonly SnowflakeOption MuteRole = new("MuteRole"); - public static readonly SnowflakeOption EventNotificationRole = new("EventNotificationRole"); + public static readonly SnowflakeOption DefaultRole = new("DefaultRole"); + public static readonly SnowflakeOption MuteRole = new("MuteRole"); + public static readonly SnowflakeOption EventNotificationRole = new("EventNotificationRole"); /// /// Controls the amount of time before a scheduled event to send a reminder in . diff --git a/src/Data/MemberData.cs b/src/Data/MemberData.cs index 7d49ec7..f028938 100644 --- a/src/Data/MemberData.cs +++ b/src/Data/MemberData.cs @@ -3,14 +3,16 @@ namespace Boyfriend.Data; /// /// Stores information about a member /// -public class MemberData { - public MemberData(ulong id, DateTimeOffset? bannedUntil) { +public sealed class MemberData +{ + public MemberData(ulong id, DateTimeOffset? bannedUntil) + { Id = id; BannedUntil = bannedUntil; } - public ulong Id { get; } + public ulong Id { get; } public DateTimeOffset? BannedUntil { get; set; } - public List Roles { get; set; } = new(); - public List Reminders { get; } = new(); + public List Roles { get; set; } = new(); + public List Reminders { get; } = new(); } diff --git a/src/Data/Options/BoolOption.cs b/src/Data/Options/BoolOption.cs index edf6b26..51cd3a1 100644 --- a/src/Data/Options/BoolOption.cs +++ b/src/Data/Options/BoolOption.cs @@ -3,25 +3,31 @@ using Remora.Results; namespace Boyfriend.Data.Options; -public class BoolOption : Option { +public sealed class BoolOption : Option +{ public BoolOption(string name, bool defaultValue) : base(name, defaultValue) { } - public override string Display(JsonNode settings) { + public override string Display(JsonNode settings) + { return Get(settings) ? Messages.Yes : Messages.No; } - public override Result Set(JsonNode settings, string from) { + public override Result Set(JsonNode settings, string from) + { if (!TryParseBool(from, out var value)) + { return new ArgumentInvalidError(nameof(from), Messages.InvalidSettingValue); + } settings[Name] = value; return Result.FromSuccess(); } - private static bool TryParseBool(string from, out bool value) { - from = from.ToLowerInvariant(); + private static bool TryParseBool(string from, out bool value) + { value = false; - switch (from) { + switch (from.ToLowerInvariant()) + { case "true" or "1" or "y" or "yes" or "д" or "да": value = true; return true; diff --git a/src/Data/Options/IOption.cs b/src/Data/Options/IOption.cs index fc0f747..6f435e5 100644 --- a/src/Data/Options/IOption.cs +++ b/src/Data/Options/IOption.cs @@ -3,8 +3,9 @@ using Remora.Results; namespace Boyfriend.Data.Options; -public interface IOption { +public interface IOption +{ string Name { get; } string Display(JsonNode settings); - Result Set(JsonNode settings, string from); + Result Set(JsonNode settings, string from); } diff --git a/src/Data/Options/LanguageOption.cs b/src/Data/Options/LanguageOption.cs index 5c50899..a82d7f6 100644 --- a/src/Data/Options/LanguageOption.cs +++ b/src/Data/Options/LanguageOption.cs @@ -6,8 +6,10 @@ using Remora.Results; namespace Boyfriend.Data.Options; /// -public class LanguageOption : Option { - private static readonly Dictionary CultureInfoCache = new() { +public sealed class LanguageOption : Option +{ + private static readonly Dictionary CultureInfoCache = new() + { { "en", new CultureInfo("en-US") }, { "ru", new CultureInfo("ru-RU") }, { "mctaylors-ru", new CultureInfo("tt-RU") } @@ -15,18 +17,21 @@ public class LanguageOption : Option { public LanguageOption(string name, string defaultValue) : base(name, CultureInfoCache[defaultValue]) { } - public override string Display(JsonNode settings) { + public override string Display(JsonNode settings) + { return Markdown.InlineCode(settings[Name]?.GetValue() ?? "en"); } /// - public override CultureInfo Get(JsonNode settings) { + public override CultureInfo Get(JsonNode settings) + { var property = settings[Name]; return property != null ? CultureInfoCache[property.GetValue()] : DefaultValue; } /// - public override Result Set(JsonNode settings, string from) { + public override Result Set(JsonNode settings, string from) + { return CultureInfoCache.ContainsKey(from.ToLowerInvariant()) ? base.Set(settings, from.ToLowerInvariant()) : new ArgumentInvalidError(nameof(from), Messages.LanguageNotSupported); diff --git a/src/Data/Options/Option.cs b/src/Data/Options/Option.cs index 742d3a9..c96b6ac 100644 --- a/src/Data/Options/Option.cs +++ b/src/Data/Options/Option.cs @@ -9,18 +9,21 @@ namespace Boyfriend.Data.Options; /// /// The type of the option. public class Option : IOption -where T : notnull { - internal readonly T DefaultValue; + where T : notnull +{ + protected readonly T DefaultValue; - public Option(string name, T defaultValue) { + public Option(string name, T defaultValue) + { Name = name; DefaultValue = defaultValue; } public string Name { get; } - public virtual string Display(JsonNode settings) { - return Markdown.InlineCode(Get(settings).ToString()!); + public virtual string Display(JsonNode settings) + { + return Markdown.InlineCode(Get(settings).ToString() ?? throw new InvalidOperationException()); } /// @@ -29,7 +32,8 @@ where T : notnull { /// The to set the value to. /// The string from which the new value of the option will be parsed. /// A value setting result which may or may not have succeeded. - public virtual Result Set(JsonNode settings, string from) { + public virtual Result Set(JsonNode settings, string from) + { settings[Name] = from; return Result.FromSuccess(); } @@ -39,7 +43,8 @@ where T : notnull { /// /// The to get the value from. /// The value of the option. - public virtual T Get(JsonNode settings) { + public virtual T Get(JsonNode settings) + { var property = settings[Name]; return property != null ? property.GetValue() : DefaultValue; } diff --git a/src/Data/Options/SnowflakeOption.cs b/src/Data/Options/SnowflakeOption.cs index 66dfa8c..7391b00 100644 --- a/src/Data/Options/SnowflakeOption.cs +++ b/src/Data/Options/SnowflakeOption.cs @@ -6,21 +6,29 @@ using Remora.Results; namespace Boyfriend.Data.Options; -public partial class SnowflakeOption : Option { +public sealed partial class SnowflakeOption : Option +{ public SnowflakeOption(string name) : base(name, 0UL.ToSnowflake()) { } - public override string Display(JsonNode settings) { - return Name.EndsWith("Channel") ? Mention.Channel(Get(settings)) : Mention.Role(Get(settings)); + public override string Display(JsonNode settings) + { + return Name.EndsWith("Channel", StringComparison.Ordinal) + ? Mention.Channel(Get(settings)) + : Mention.Role(Get(settings)); } - public override Snowflake Get(JsonNode settings) { + public override Snowflake Get(JsonNode settings) + { var property = settings[Name]; return property != null ? property.GetValue().ToSnowflake() : DefaultValue; } - public override Result Set(JsonNode settings, string from) { + public override Result Set(JsonNode settings, string from) + { if (!ulong.TryParse(NonNumbers().Replace(from, ""), out var parsed)) + { return new ArgumentInvalidError(nameof(from), Messages.InvalidSettingValue); + } settings[Name] = parsed; return Result.FromSuccess(); diff --git a/src/Data/Options/TimeSpanOption.cs b/src/Data/Options/TimeSpanOption.cs index 3aa1fd5..5e39cf0 100644 --- a/src/Data/Options/TimeSpanOption.cs +++ b/src/Data/Options/TimeSpanOption.cs @@ -4,25 +4,31 @@ using Remora.Results; namespace Boyfriend.Data.Options; -public class TimeSpanOption : Option { +public sealed class TimeSpanOption : Option +{ private static readonly TimeSpanParser Parser = new(); public TimeSpanOption(string name, TimeSpan defaultValue) : base(name, defaultValue) { } - public override TimeSpan Get(JsonNode settings) { + public override TimeSpan Get(JsonNode settings) + { var property = settings[Name]; return property != null ? ParseTimeSpan(property.GetValue()).Entity : DefaultValue; } - public override Result Set(JsonNode settings, string from) { + public override Result Set(JsonNode settings, string from) + { if (!ParseTimeSpan(from).IsDefined(out var span)) + { return new ArgumentInvalidError(nameof(from), Messages.InvalidSettingValue); + } settings[Name] = span.ToString(); return Result.FromSuccess(); } - private static Result ParseTimeSpan(string from) { + private static Result ParseTimeSpan(string from) + { return Parser.TryParseAsync(from).AsTask().GetAwaiter().GetResult(); } } diff --git a/src/Data/Reminder.cs b/src/Data/Reminder.cs index 2246b5e..a332003 100644 --- a/src/Data/Reminder.cs +++ b/src/Data/Reminder.cs @@ -1,7 +1,8 @@ namespace Boyfriend.Data; -public struct Reminder { +public struct Reminder +{ public DateTimeOffset At; - public string Text; - public ulong Channel; + public string Text; + public ulong Channel; } diff --git a/src/Data/ScheduledEventData.cs b/src/Data/ScheduledEventData.cs index 661eed0..29075f5 100644 --- a/src/Data/ScheduledEventData.cs +++ b/src/Data/ScheduledEventData.cs @@ -6,12 +6,14 @@ namespace Boyfriend.Data; /// Stores information about scheduled events. This information is not provided by the Discord API. /// /// This information is stored on disk as a JSON file. -public class ScheduledEventData { - public ScheduledEventData(GuildScheduledEventStatus status) { +public sealed class ScheduledEventData +{ + public ScheduledEventData(GuildScheduledEventStatus status) + { Status = status; } - public bool EarlyNotificationSent { get; set; } - public DateTimeOffset? ActualStartTime { get; set; } - public GuildScheduledEventStatus Status { get; set; } + public bool EarlyNotificationSent { get; set; } + public DateTimeOffset? ActualStartTime { get; set; } + public GuildScheduledEventStatus Status { get; set; } } diff --git a/src/Extensions.cs b/src/Extensions.cs index 2fa342c..df2e548 100644 --- a/src/Extensions.cs +++ b/src/Extensions.cs @@ -14,14 +14,16 @@ using Remora.Results; namespace Boyfriend; -public static class Extensions { +public static class Extensions +{ /// /// Adds a footer with the 's avatar and tag (@username or username#0000). /// /// The builder to add the footer to. /// The user whose tag and avatar to add. /// The builder with the added footer. - public static EmbedBuilder WithUserFooter(this EmbedBuilder builder, IUser user) { + public static EmbedBuilder WithUserFooter(this EmbedBuilder builder, IUser user) + { var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256); var avatarUrl = avatarUrlResult.IsSuccess ? avatarUrlResult.Entity.AbsoluteUri @@ -36,7 +38,8 @@ public static class Extensions { /// The builder to add the footer to. /// The user that performed the action whose tag and avatar to use. /// The builder with the added footer. - public static EmbedBuilder WithActionFooter(this EmbedBuilder builder, IUser user) { + public static EmbedBuilder WithActionFooter(this EmbedBuilder builder, IUser user) + { var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256); var avatarUrl = avatarUrlResult.IsSuccess ? avatarUrlResult.Entity.AbsoluteUri @@ -54,9 +57,11 @@ public static class Extensions { /// The user whose avatar to use in the small title. /// The builder with the added small title in the author field. public static EmbedBuilder WithSmallTitle( - this EmbedBuilder builder, string text, IUser? avatarSource = null) { + this EmbedBuilder builder, string text, IUser? avatarSource = null) + { Uri? avatarUrl = null; - if (avatarSource is not null) { + if (avatarSource is not null) + { var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256); avatarUrl = avatarUrlResult.IsSuccess @@ -74,7 +79,8 @@ public static class Extensions { /// The builder to add the footer to. /// The guild whose name and icon to use. /// The builder with the added footer. - public static EmbedBuilder WithGuildFooter(this EmbedBuilder builder, IGuild guild) { + public static EmbedBuilder WithGuildFooter(this EmbedBuilder builder, IGuild guild) + { var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256); var iconUrl = iconUrlResult.IsSuccess ? iconUrlResult.Entity.AbsoluteUri @@ -89,7 +95,8 @@ public static class Extensions { /// The builder to add the title to. /// The guild whose name and icon to use. /// The builder with the added title. - public static EmbedBuilder WithGuildTitle(this EmbedBuilder builder, IGuild guild) { + public static EmbedBuilder WithGuildTitle(this EmbedBuilder builder, IGuild guild) + { var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256); var iconUrl = iconUrlResult.IsSuccess ? iconUrlResult.Entity.AbsoluteUri @@ -107,8 +114,12 @@ public static class Extensions { /// The Optional containing the image hash. /// The builder with the added cover image. public static EmbedBuilder WithEventCover( - this EmbedBuilder builder, Snowflake eventId, Optional imageHashOptional) { - if (!imageHashOptional.IsDefined(out var imageHash)) return builder; + this EmbedBuilder builder, Snowflake eventId, Optional imageHashOptional) + { + if (!imageHashOptional.IsDefined(out var imageHash)) + { + return builder; + } var iconUrlResult = CDN.GetGuildScheduledEventCoverUrl(eventId, imageHash, imageSize: 1024); return iconUrlResult.IsDefined(out var iconUrl) ? builder.WithImageUrl(iconUrl.AbsoluteUri) : builder; @@ -120,25 +131,31 @@ public static class Extensions { /// /// The string to sanitize. /// The sanitized string that can be safely used in . - private static string SanitizeForBlockCode(this string s) { + private static string SanitizeForBlockCode(this string s) + { return s.Replace("```", "​`​`​`​"); } /// - /// Sanitizes a string (see ) and formats the string to use Markdown Block Code formatting with a specified - /// language for syntax highlighting. + /// Sanitizes a string (see ) and formats the string to use Markdown Block Code + /// formatting with a specified + /// language for syntax highlighting. /// /// The string to sanitize and format. /// - /// The sanitized string formatted to use Markdown Block Code with a specified - /// language for syntax highlighting. - public static string InBlockCode(this string s, string language = "") { + /// + /// The sanitized string formatted to use Markdown Block Code with a specified + /// language for syntax highlighting. + /// + public static string InBlockCode(this string s, string language = "") + { s = s.SanitizeForBlockCode(); return - $"```{language}\n{s.SanitizeForBlockCode()}{(s.EndsWith("`") || string.IsNullOrWhiteSpace(s) ? " " : "")}```"; + $"```{language}\n{s.SanitizeForBlockCode()}{(s.EndsWith("`", StringComparison.Ordinal) || string.IsNullOrWhiteSpace(s) ? " " : "")}```"; } - public static string Localized(this string key) { + public static string Localized(this string key) + { return Messages.ResourceManager.GetString(key, Messages.Culture) ?? key; } @@ -148,41 +165,56 @@ public static class Extensions { /// Used when encountering "Request headers must contain only ASCII characters". /// The string to encode. /// An encoded string with spaces kept intact. - public static string EncodeHeader(this string s) { + public static string EncodeHeader(this string s) + { return WebUtility.UrlEncode(s).Replace('+', ' '); } - public static string AsMarkdown(this DiffPaneModel model) { + public static string AsMarkdown(this DiffPaneModel model) + { var builder = new StringBuilder(); - foreach (var line in model.Lines) { + foreach (var line in model.Lines) + { if (line.Type is ChangeType.Deleted) + { builder.Append("-- "); + } + if (line.Type is ChangeType.Inserted) + { builder.Append("++ "); + } + if (line.Type is not ChangeType.Imaginary) + { builder.AppendLine(line.Text); + } } return InBlockCode(builder.ToString(), "diff"); } - public static string GetTag(this IUser user) { + public static string GetTag(this IUser user) + { return user.Discriminator is 0000 ? $"@{user.Username}" : $"{user.Username}#{user.Discriminator:0000}"; } - public static Snowflake ToSnowflake(this ulong id) { + public static Snowflake ToSnowflake(this ulong id) + { return DiscordSnowflake.New(id); } public static TResult? MaxOrDefault( - this IEnumerable source, Func selector) { + this IEnumerable source, Func selector) + { var list = source.ToList(); return list.Any() ? list.Max(selector) : default; } public static bool TryGetContextIDs( - this ICommandContext context, out Snowflake guildId, - out Snowflake channelId, out Snowflake userId) { + this ICommandContext context, out Snowflake guildId, + out Snowflake channelId, out Snowflake userId) + { channelId = default; userId = default; return context.TryGetGuildID(out guildId) @@ -195,7 +227,8 @@ public static class Extensions { /// /// The Snowflake to check. /// true if the Snowflake has no value set or it's set to 0, false otherwise. - public static bool Empty(this Snowflake snowflake) { + public static bool Empty(this Snowflake snowflake) + { return snowflake.Value is 0; } @@ -210,14 +243,18 @@ public static class Extensions { /// otherwise. /// /// - public static bool EmptyOrEqualTo(this Snowflake snowflake, Snowflake anotherSnowflake) { + public static bool EmptyOrEqualTo(this Snowflake snowflake, Snowflake anotherSnowflake) + { return snowflake.Empty() || snowflake == anotherSnowflake; } public static async Task SendContextualEmbedResultAsync( - this FeedbackService feedback, Result embedResult, CancellationToken ct = default) { + this FeedbackService feedback, Result embedResult, CancellationToken ct = default) + { if (!embedResult.IsDefined(out var embed)) + { return Result.FromError(embedResult); + } return (Result)await feedback.SendContextualEmbedAsync(embed, ct: ct); } diff --git a/src/InteractionResponders.cs b/src/InteractionResponders.cs index 977f00f..d3916b0 100644 --- a/src/InteractionResponders.cs +++ b/src/InteractionResponders.cs @@ -11,11 +11,13 @@ namespace Boyfriend; /// Handles responding to various interactions. /// [UsedImplicitly] -public class InteractionResponders : InteractionGroup { - private readonly FeedbackService _feedbackService; +public class InteractionResponders : InteractionGroup +{ + private readonly FeedbackService _feedback; - public InteractionResponders(FeedbackService feedbackService) { - _feedbackService = feedbackService; + public InteractionResponders(FeedbackService feedback) + { + _feedback = feedback; } /// @@ -25,11 +27,15 @@ public class InteractionResponders : InteractionGroup { /// An ephemeral feedback sending result which may or may not have succeeded. [Button("scheduled-event-details")] [UsedImplicitly] - public async Task OnStatefulButtonClicked(string? state = null) { - if (state is null) return new ArgumentNullError(nameof(state)); + public async Task OnStatefulButtonClicked(string? state = null) + { + if (state is null) + { + return new ArgumentNullError(nameof(state)); + } var idArray = state.Split(':'); - return (Result)await _feedbackService.SendContextualAsync( + return (Result)await _feedback.SendContextualAsync( $"https://discord.com/events/{idArray[0]}/{idArray[1]}", options: new FeedbackMessageOptions(MessageFlags: MessageFlags.Ephemeral), ct: CancellationToken); } diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs index 081f526..547e5ac 100644 --- a/src/Responders/GuildLoadedResponder.cs +++ b/src/Responders/GuildLoadedResponder.cs @@ -16,35 +16,49 @@ namespace Boyfriend.Responders; /// has enabled /// [UsedImplicitly] -public class GuildLoadedResponder : IResponder { - private readonly IDiscordRestChannelAPI _channelApi; - private readonly GuildDataService _dataService; +public class GuildLoadedResponder : IResponder +{ + private readonly IDiscordRestChannelAPI _channelApi; + private readonly GuildDataService _guildData; private readonly ILogger _logger; - private readonly IDiscordRestUserAPI _userApi; + private readonly IDiscordRestUserAPI _userApi; public GuildLoadedResponder( - IDiscordRestChannelAPI channelApi, GuildDataService dataService, ILogger logger, - IDiscordRestUserAPI userApi) { + IDiscordRestChannelAPI channelApi, GuildDataService guildData, ILogger logger, + IDiscordRestUserAPI userApi) + { _channelApi = channelApi; - _dataService = dataService; + _guildData = guildData; _logger = logger; _userApi = userApi; } - public async Task RespondAsync(IGuildCreate gatewayEvent, CancellationToken ct = default) { - if (!gatewayEvent.Guild.IsT0) return Result.FromSuccess(); // Guild is not IAvailableGuild + public async Task RespondAsync(IGuildCreate gatewayEvent, CancellationToken ct = default) + { + if (!gatewayEvent.Guild.IsT0) // Guild is not IAvailableGuild + { + return Result.FromSuccess(); + } var guild = gatewayEvent.Guild.AsT0; _logger.LogInformation("Joined guild \"{Name}\"", guild.Name); - var cfg = await _dataService.GetSettings(guild.ID, ct); + var cfg = await _guildData.GetSettings(guild.ID, ct); if (!GuildSettings.ReceiveStartupMessages.Get(cfg)) + { return Result.FromSuccess(); + } + if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) + { return Result.FromSuccess(); + } var currentUserResult = await _userApi.GetCurrentUserAsync(ct); - if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult); + if (!currentUserResult.IsDefined(out var currentUser)) + { + return Result.FromError(currentUserResult); + } Messages.Culture = GuildSettings.Language.Get(cfg); var i = Random.Shared.Next(1, 4); @@ -55,7 +69,10 @@ public class GuildLoadedResponder : IResponder { .WithCurrentTimestamp() .WithColour(ColorsList.Blue) .Build(); - if (!embed.IsDefined(out var built)) return Result.FromError(embed); + if (!embed.IsDefined(out var built)) + { + return Result.FromError(embed); + } return (Result)await _channelApi.CreateMessageAsync( GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { built }, ct: ct); diff --git a/src/Responders/GuildMemberJoinedResponder.cs b/src/Responders/GuildMemberJoinedResponder.cs index 5357363..bb60b0b 100644 --- a/src/Responders/GuildMemberJoinedResponder.cs +++ b/src/Responders/GuildMemberJoinedResponder.cs @@ -15,31 +15,44 @@ namespace Boyfriend.Responders; /// /// [UsedImplicitly] -public class GuildMemberJoinedResponder : IResponder { +public class GuildMemberJoinedResponder : IResponder +{ private readonly IDiscordRestChannelAPI _channelApi; - private readonly GuildDataService _dataService; - private readonly IDiscordRestGuildAPI _guildApi; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly GuildDataService _guildData; public GuildMemberJoinedResponder( - IDiscordRestChannelAPI channelApi, GuildDataService dataService, IDiscordRestGuildAPI guildApi) { + IDiscordRestChannelAPI channelApi, GuildDataService guildData, IDiscordRestGuildAPI guildApi) + { _channelApi = channelApi; - _dataService = dataService; + _guildData = guildData; _guildApi = guildApi; } - public async Task RespondAsync(IGuildMemberAdd gatewayEvent, CancellationToken ct = default) { + public async Task RespondAsync(IGuildMemberAdd gatewayEvent, CancellationToken ct = default) + { if (!gatewayEvent.User.IsDefined(out var user)) + { return new ArgumentNullError(nameof(gatewayEvent.User)); - var data = await _dataService.GetData(gatewayEvent.GuildID, ct); + } + + var data = await _guildData.GetData(gatewayEvent.GuildID, ct); var cfg = data.Settings; if (GuildSettings.PublicFeedbackChannel.Get(cfg).Empty() || GuildSettings.WelcomeMessage.Get(cfg) is "off" or "disable" or "disabled") + { return Result.FromSuccess(); - if (GuildSettings.ReturnRolesOnRejoin.Get(cfg)) { + } + + if (GuildSettings.ReturnRolesOnRejoin.Get(cfg)) + { var result = await _guildApi.ModifyGuildMemberAsync( gatewayEvent.GuildID, user.ID, roles: data.GetMemberData(user.ID).Roles.ConvertAll(r => r.ToSnowflake()), ct: ct); - if (!result.IsSuccess) return Result.FromError(result.Error); + if (!result.IsSuccess) + { + return Result.FromError(result.Error); + } } Messages.Culture = GuildSettings.Language.Get(cfg); @@ -48,7 +61,10 @@ public class GuildMemberJoinedResponder : IResponder { : GuildSettings.WelcomeMessage.Get(cfg); var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct); - if (!guildResult.IsDefined(out var guild)) return Result.FromError(guildResult); + if (!guildResult.IsDefined(out var guild)) + { + return Result.FromError(guildResult); + } var embed = new EmbedBuilder() .WithSmallTitle(string.Format(welcomeMessage, user.GetTag(), guild.Name), user) @@ -56,7 +72,10 @@ public class GuildMemberJoinedResponder : IResponder { .WithTimestamp(gatewayEvent.JoinedAt) .WithColour(ColorsList.Green) .Build(); - if (!embed.IsDefined(out var built)) return Result.FromError(embed); + if (!embed.IsDefined(out var built)) + { + return Result.FromError(embed); + } return (Result)await _channelApi.CreateMessageAsync( GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: new[] { built }, diff --git a/src/Responders/GuildMemberRolesUpdatedResponder.cs b/src/Responders/GuildMemberRolesUpdatedResponder.cs index b61ce32..dbd8b3a 100644 --- a/src/Responders/GuildMemberRolesUpdatedResponder.cs +++ b/src/Responders/GuildMemberRolesUpdatedResponder.cs @@ -11,15 +11,18 @@ namespace Boyfriend.Responders; /// Handles updating when a guild member is updated. /// [UsedImplicitly] -public class GuildMemberUpdateResponder : IResponder { - private readonly GuildDataService _dataService; +public class GuildMemberUpdateResponder : IResponder +{ + private readonly GuildDataService _guildData; - public GuildMemberUpdateResponder(GuildDataService dataService) { - _dataService = dataService; + public GuildMemberUpdateResponder(GuildDataService guildData) + { + _guildData = guildData; } - public async Task RespondAsync(IGuildMemberUpdate gatewayEvent, CancellationToken ct = default) { - var memberData = await _dataService.GetMemberData(gatewayEvent.GuildID, gatewayEvent.User.ID, ct); + public async Task RespondAsync(IGuildMemberUpdate gatewayEvent, CancellationToken ct = default) + { + var memberData = await _guildData.GetMemberData(gatewayEvent.GuildID, gatewayEvent.User.ID, ct); memberData.Roles = gatewayEvent.Roles.ToList().ConvertAll(r => r.Value); return Result.FromSuccess(); } diff --git a/src/Responders/MessageDeletedResponder.cs b/src/Responders/MessageDeletedResponder.cs index 652631e..3611f80 100644 --- a/src/Responders/MessageDeletedResponder.cs +++ b/src/Responders/MessageDeletedResponder.cs @@ -16,43 +16,68 @@ namespace Boyfriend.Responders; /// to a guild's if one is set. /// [UsedImplicitly] -public class MessageDeletedResponder : IResponder { +public class MessageDeletedResponder : IResponder +{ private readonly IDiscordRestAuditLogAPI _auditLogApi; - private readonly IDiscordRestChannelAPI _channelApi; - private readonly GuildDataService _dataService; - private readonly IDiscordRestUserAPI _userApi; + private readonly IDiscordRestChannelAPI _channelApi; + private readonly GuildDataService _guildData; + private readonly IDiscordRestUserAPI _userApi; public MessageDeletedResponder( IDiscordRestAuditLogAPI auditLogApi, IDiscordRestChannelAPI channelApi, - GuildDataService dataService, IDiscordRestUserAPI userApi) { + GuildDataService guildData, IDiscordRestUserAPI userApi) + { _auditLogApi = auditLogApi; _channelApi = channelApi; - _dataService = dataService; + _guildData = guildData; _userApi = userApi; } - public async Task RespondAsync(IMessageDelete gatewayEvent, CancellationToken ct = default) { - if (!gatewayEvent.GuildID.IsDefined(out var guildId)) return Result.FromSuccess(); + public async Task RespondAsync(IMessageDelete gatewayEvent, CancellationToken ct = default) + { + if (!gatewayEvent.GuildID.IsDefined(out var guildId)) + { + return Result.FromSuccess(); + } - var cfg = await _dataService.GetSettings(guildId, ct); - if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) return Result.FromSuccess(); + var cfg = await _guildData.GetSettings(guildId, ct); + if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) + { + return Result.FromSuccess(); + } var messageResult = await _channelApi.GetChannelMessageAsync(gatewayEvent.ChannelID, gatewayEvent.ID, ct); - if (!messageResult.IsDefined(out var message)) return Result.FromError(messageResult); - if (string.IsNullOrWhiteSpace(message.Content)) return Result.FromSuccess(); + if (!messageResult.IsDefined(out var message)) + { + return Result.FromError(messageResult); + } + + if (string.IsNullOrWhiteSpace(message.Content)) + { + return Result.FromSuccess(); + } var auditLogResult = await _auditLogApi.GetGuildAuditLogAsync( guildId, actionType: AuditLogEvent.MessageDelete, limit: 1, ct: ct); - if (!auditLogResult.IsDefined(out var auditLogPage)) return Result.FromError(auditLogResult); + if (!auditLogResult.IsDefined(out var auditLogPage)) + { + return Result.FromError(auditLogResult); + } var auditLog = auditLogPage.AuditLogEntries.Single(); var userResult = Result.FromSuccess(message.Author); - if (auditLog.Options.Value.ChannelID == gatewayEvent.ChannelID + if (auditLog.UserID is not null + && auditLog.Options.Value.ChannelID == gatewayEvent.ChannelID && DateTimeOffset.UtcNow.Subtract(auditLog.ID.Timestamp).TotalSeconds <= 2) - userResult = await _userApi.GetUserAsync(auditLog.UserID!.Value, ct); + { + userResult = await _userApi.GetUserAsync(auditLog.UserID.Value, ct); + } - if (!userResult.IsDefined(out var user)) return Result.FromError(userResult); + if (!userResult.IsDefined(out var user)) + { + return Result.FromError(userResult); + } Messages.Culture = GuildSettings.Language.Get(cfg); @@ -67,7 +92,10 @@ public class MessageDeletedResponder : IResponder { .WithTimestamp(message.Timestamp) .WithColour(ColorsList.Red) .Build(); - if (!embed.IsDefined(out var built)) return Result.FromError(embed); + if (!embed.IsDefined(out var built)) + { + return Result.FromError(embed); + } return (Result)await _channelApi.CreateMessageAsync( GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { built }, diff --git a/src/Responders/MessageEditedResponder.cs b/src/Responders/MessageEditedResponder.cs index e7ec215..7e02f9f 100644 --- a/src/Responders/MessageEditedResponder.cs +++ b/src/Responders/MessageEditedResponder.cs @@ -18,42 +18,68 @@ namespace Boyfriend.Responders; /// to a guild's if one is set. /// [UsedImplicitly] -public class MessageEditedResponder : IResponder { - private readonly CacheService _cacheService; +public class MessageEditedResponder : IResponder +{ + private readonly CacheService _cacheService; private readonly IDiscordRestChannelAPI _channelApi; - private readonly GuildDataService _dataService; - private readonly IDiscordRestUserAPI _userApi; + private readonly GuildDataService _guildData; + private readonly IDiscordRestUserAPI _userApi; public MessageEditedResponder( - CacheService cacheService, IDiscordRestChannelAPI channelApi, GuildDataService dataService, - IDiscordRestUserAPI userApi) { + CacheService cacheService, IDiscordRestChannelAPI channelApi, GuildDataService guildData, + IDiscordRestUserAPI userApi) + { _cacheService = cacheService; _channelApi = channelApi; - _dataService = dataService; + _guildData = guildData; _userApi = userApi; } - public async Task RespondAsync(IMessageUpdate gatewayEvent, CancellationToken ct = default) { + public async Task RespondAsync(IMessageUpdate gatewayEvent, CancellationToken ct = default) + { if (!gatewayEvent.GuildID.IsDefined(out var guildId)) + { return Result.FromSuccess(); - var cfg = await _dataService.GetSettings(guildId, ct); + } + + var cfg = await _guildData.GetSettings(guildId, ct); if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) + { return Result.FromSuccess(); + } + if (!gatewayEvent.Content.IsDefined(out var newContent)) + { return Result.FromSuccess(); + } + if (!gatewayEvent.EditedTimestamp.IsDefined(out var timestamp)) + { return Result.FromSuccess(); // The message wasn't actually edited + } if (!gatewayEvent.ChannelID.IsDefined(out var channelId)) + { return new ArgumentNullError(nameof(gatewayEvent.ChannelID)); + } + if (!gatewayEvent.ID.IsDefined(out var messageId)) + { return new ArgumentNullError(nameof(gatewayEvent.ID)); + } var cacheKey = new KeyHelpers.MessageCacheKey(channelId, messageId); var messageResult = await _cacheService.TryGetValueAsync( cacheKey, ct); - if (!messageResult.IsDefined(out var message)) return Result.FromError(messageResult); - if (message.Content == newContent) return Result.FromSuccess(); + if (!messageResult.IsDefined(out var message)) + { + return Result.FromError(messageResult); + } + + if (message.Content == newContent) + { + return Result.FromSuccess(); + } // Custom event responders are called earlier than responders responsible for message caching // This means that subsequent edit logs may contain the wrong content @@ -67,7 +93,10 @@ public class MessageEditedResponder : IResponder { _ = _channelApi.GetChannelMessageAsync(channelId, messageId, ct); var currentUserResult = await _userApi.GetCurrentUserAsync(ct); - if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult); + if (!currentUserResult.IsDefined(out var currentUser)) + { + return Result.FromError(currentUserResult); + } var diff = InlineDiffBuilder.Diff(message.Content, newContent); @@ -80,7 +109,10 @@ public class MessageEditedResponder : IResponder { .WithTimestamp(timestamp.Value) .WithColour(ColorsList.Yellow) .Build(); - if (!embed.IsDefined(out var built)) return Result.FromError(embed); + if (!embed.IsDefined(out var built)) + { + return Result.FromError(embed); + } return (Result)await _channelApi.CreateMessageAsync( GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { built }, diff --git a/src/Responders/MessageReceivedResponder.cs b/src/Responders/MessageReceivedResponder.cs index a45a2f4..cadef5f 100644 --- a/src/Responders/MessageReceivedResponder.cs +++ b/src/Responders/MessageReceivedResponder.cs @@ -11,23 +11,27 @@ namespace Boyfriend.Responders; /// Handles sending replies to easter egg messages. /// [UsedImplicitly] -public class MessageCreateResponder : IResponder { +public class MessageCreateResponder : IResponder +{ private readonly IDiscordRestChannelAPI _channelApi; - public MessageCreateResponder(IDiscordRestChannelAPI channelApi) { + public MessageCreateResponder(IDiscordRestChannelAPI channelApi) + { _channelApi = channelApi; } - public Task RespondAsync(IMessageCreate gatewayEvent, CancellationToken ct = default) { + public Task RespondAsync(IMessageCreate gatewayEvent, CancellationToken ct = default) + { _ = _channelApi.CreateMessageAsync( - gatewayEvent.ChannelID, ct: ct, content: gatewayEvent.Content.ToLowerInvariant() switch { - "whoami" => "`nobody`", + gatewayEvent.ChannelID, ct: ct, content: gatewayEvent.Content.ToLowerInvariant() switch + { + "whoami" => "`nobody`", "сука !!" => "`root`", - "воооо" => "`removing /...`", + "воооо" => "`removing /...`", "пон" => "https://cdn.upload.systems/uploads/2LNfUSwM.jpg", "++++" => "#", - "осу" => "https://github.com/ppy/osu", - _ => default(Optional) + "осу" => "https://github.com/ppy/osu", + _ => default(Optional) }); return Task.FromResult(Result.FromSuccess()); } diff --git a/src/Responders/ScheduledEventCancelledResponder.cs b/src/Responders/ScheduledEventCancelledResponder.cs index 86453ef..c35128a 100644 --- a/src/Responders/ScheduledEventCancelledResponder.cs +++ b/src/Responders/ScheduledEventCancelledResponder.cs @@ -14,21 +14,26 @@ namespace Boyfriend.Responders; /// in a guild's if one is set. /// [UsedImplicitly] -public class GuildScheduledEventDeleteResponder : IResponder { +public class GuildScheduledEventDeleteResponder : IResponder +{ private readonly IDiscordRestChannelAPI _channelApi; - private readonly GuildDataService _dataService; + private readonly GuildDataService _guildData; - public GuildScheduledEventDeleteResponder(IDiscordRestChannelAPI channelApi, GuildDataService dataService) { + public GuildScheduledEventDeleteResponder(IDiscordRestChannelAPI channelApi, GuildDataService guildData) + { _channelApi = channelApi; - _dataService = dataService; + _guildData = guildData; } - public async Task RespondAsync(IGuildScheduledEventDelete gatewayEvent, CancellationToken ct = default) { - var guildData = await _dataService.GetData(gatewayEvent.GuildID, ct); + public async Task RespondAsync(IGuildScheduledEventDelete gatewayEvent, CancellationToken ct = default) + { + var guildData = await _guildData.GetData(gatewayEvent.GuildID, ct); guildData.ScheduledEvents.Remove(gatewayEvent.ID.Value); if (GuildSettings.EventNotificationChannel.Get(guildData.Settings).Empty()) + { return Result.FromSuccess(); + } var embed = new EmbedBuilder() .WithSmallTitle(string.Format(Messages.EventCancelled, gatewayEvent.Name)) @@ -37,7 +42,10 @@ public class GuildScheduledEventDeleteResponder : IResponder /// Handles saving, loading, initializing and providing . /// -public class GuildDataService : IHostedService { +public sealed class GuildDataService : IHostedService +{ private readonly ConcurrentDictionary _datas = new(); - private readonly IDiscordRestGuildAPI _guildApi; + private readonly IDiscordRestGuildAPI _guildApi; // https://github.com/dotnet/aspnetcore/issues/39139 public GuildDataService( - IHostApplicationLifetime lifetime, IDiscordRestGuildAPI guildApi) { + IHostApplicationLifetime lifetime, IDiscordRestGuildAPI guildApi) + { _guildApi = guildApi; lifetime.ApplicationStopping.Register(ApplicationStopping); } - public Task StartAsync(CancellationToken ct) { + public Task StartAsync(CancellationToken ct) + { return Task.CompletedTask; } - public Task StopAsync(CancellationToken ct) { + public Task StopAsync(CancellationToken ct) + { return Task.CompletedTask; } - private void ApplicationStopping() { + private void ApplicationStopping() + { SaveAsync(CancellationToken.None).GetAwaiter().GetResult(); } - private async Task SaveAsync(CancellationToken ct) { + private async Task SaveAsync(CancellationToken ct) + { var tasks = new List(); - foreach (var data in _datas.Values) { + foreach (var data in _datas.Values) + { await using var settingsStream = File.OpenWrite(data.SettingsPath); tasks.Add(JsonSerializer.SerializeAsync(settingsStream, data.Settings, cancellationToken: ct)); await using var eventsStream = File.OpenWrite(data.ScheduledEventsPath); tasks.Add(JsonSerializer.SerializeAsync(eventsStream, data.ScheduledEvents, cancellationToken: ct)); - foreach (var memberData in data.MemberData.Values) { + foreach (var memberData in data.MemberData.Values) + { await using var memberDataStream = File.OpenWrite($"{data.MemberDataPath}/{memberData.Id}.json"); tasks.Add(JsonSerializer.SerializeAsync(memberDataStream, memberData, cancellationToken: ct)); } @@ -52,19 +60,36 @@ public class GuildDataService : IHostedService { await Task.WhenAll(tasks); } - public async Task GetData(Snowflake guildId, CancellationToken ct = default) { + public async Task GetData(Snowflake guildId, CancellationToken ct = default) + { return _datas.TryGetValue(guildId, out var data) ? data : await InitializeData(guildId, ct); } - private async Task InitializeData(Snowflake guildId, CancellationToken ct = default) { + private async Task InitializeData(Snowflake guildId, CancellationToken ct = default) + { var idString = $"{guildId}"; var memberDataPath = $"{guildId}/MemberData"; var settingsPath = $"{guildId}/Settings.json"; var scheduledEventsPath = $"{guildId}/ScheduledEvents.json"; - if (!Directory.Exists(idString)) Directory.CreateDirectory(idString); - if (!Directory.Exists(memberDataPath)) Directory.CreateDirectory(memberDataPath); - if (!File.Exists(settingsPath)) await File.WriteAllTextAsync(settingsPath, "{}", ct); - if (!File.Exists(scheduledEventsPath)) await File.WriteAllTextAsync(scheduledEventsPath, "{}", ct); + if (!Directory.Exists(idString)) + { + Directory.CreateDirectory(idString); + } + + if (!Directory.Exists(memberDataPath)) + { + Directory.CreateDirectory(memberDataPath); + } + + if (!File.Exists(settingsPath)) + { + await File.WriteAllTextAsync(settingsPath, "{}", ct); + } + + if (!File.Exists(scheduledEventsPath)) + { + await File.WriteAllTextAsync(scheduledEventsPath, "{}", ct); + } await using var settingsStream = File.OpenRead(settingsPath); var jsonSettings @@ -76,13 +101,20 @@ public class GuildDataService : IHostedService { eventsStream, cancellationToken: ct); var memberData = new Dictionary(); - foreach (var dataPath in Directory.GetFiles(memberDataPath)) { + foreach (var dataPath in Directory.GetFiles(memberDataPath)) + { await using var dataStream = File.OpenRead(dataPath); var data = await JsonSerializer.DeserializeAsync(dataStream, cancellationToken: ct); - if (data is null) continue; + if (data is null) + { + continue; + } + var memberResult = await _guildApi.GetGuildMemberAsync(guildId, data.Id.ToSnowflake(), ct); if (memberResult.IsSuccess) + { data.Roles = memberResult.Entity.Roles.ToList().ConvertAll(r => r.Value); + } memberData.Add(data.Id, data); } @@ -91,19 +123,26 @@ public class GuildDataService : IHostedService { jsonSettings ?? new JsonObject(), settingsPath, await events ?? new Dictionary(), scheduledEventsPath, memberData, memberDataPath); - while (!_datas.ContainsKey(guildId)) _datas.TryAdd(guildId, finalData); + while (!_datas.ContainsKey(guildId)) + { + _datas.TryAdd(guildId, finalData); + } + return finalData; } - public async Task GetSettings(Snowflake guildId, CancellationToken ct = default) { + public async Task GetSettings(Snowflake guildId, CancellationToken ct = default) + { return (await GetData(guildId, ct)).Settings; } - public async Task GetMemberData(Snowflake guildId, Snowflake userId, CancellationToken ct = default) { + public async Task GetMemberData(Snowflake guildId, Snowflake userId, CancellationToken ct = default) + { return (await GetData(guildId, ct)).GetMemberData(userId); } - public ICollection GetGuildIds() { + public ICollection GetGuildIds() + { return _datas.Keys; } } diff --git a/src/Services/GuildUpdateService.cs b/src/Services/GuildUpdateService.cs index da06a99..752f3c2 100644 --- a/src/Services/GuildUpdateService.cs +++ b/src/Services/GuildUpdateService.cs @@ -20,8 +20,10 @@ namespace Boyfriend.Services; /// /// Handles executing guild updates (also called "ticks") once per second. /// -public partial class GuildUpdateService : BackgroundService { - private static readonly (string Name, TimeSpan Duration)[] SongList = { +public sealed partial class GuildUpdateService : BackgroundService +{ + private static readonly (string Name, TimeSpan Duration)[] SongList = + { ("UNDEAD CORPORATION - The Empress", new TimeSpan(0, 4, 34)), ("UNDEAD CORPORATION - Everything will freeze", new TimeSpan(0, 3, 17)), ("Splatoon 3 - Rockagilly Blues (Yoko & the Gold Bazookas)", new TimeSpan(0, 3, 37)), @@ -36,7 +38,8 @@ public partial class GuildUpdateService : BackgroundService { ("Noisecream - Mist of Rage", new TimeSpan(0, 2, 25)) }; - private static readonly string[] GenericNicknames = { + private static readonly string[] GenericNicknames = + { "Albatross", "Alpha", "Anchor", "Banjo", "Bell", "Beta", "Blackbird", "Bulldog", "Canary", "Cat", "Calf", "Cyclone", "Daisy", "Dalmatian", "Dart", "Delta", "Diamond", "Donkey", "Duck", "Emu", "Eclipse", "Flamingo", "Flute", "Frog", "Goose", "Hatchet", "Heron", "Husky", "Hurricane", @@ -48,25 +51,26 @@ public partial class GuildUpdateService : BackgroundService { private readonly List _activityList = new(1) { new Activity("with Remora.Discord", ActivityType.Game) }; - private readonly IDiscordRestChannelAPI _channelApi; - private readonly DiscordGatewayClient _client; - private readonly GuildDataService _dataService; + private readonly IDiscordRestChannelAPI _channelApi; + private readonly DiscordGatewayClient _client; private readonly IDiscordRestGuildScheduledEventAPI _eventApi; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly ILogger _logger; - private readonly IDiscordRestUserAPI _userApi; - private readonly UtilityService _utility; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly GuildDataService _guildData; + private readonly ILogger _logger; + private readonly IDiscordRestUserAPI _userApi; + private readonly UtilityService _utility; private DateTimeOffset _nextSongAt = DateTimeOffset.MinValue; - private uint _nextSongIndex; + private uint _nextSongIndex; public GuildUpdateService( - IDiscordRestChannelAPI channelApi, DiscordGatewayClient client, GuildDataService dataService, + IDiscordRestChannelAPI channelApi, DiscordGatewayClient client, GuildDataService guildData, IDiscordRestGuildScheduledEventAPI eventApi, IDiscordRestGuildAPI guildApi, ILogger logger, - IDiscordRestUserAPI userApi, UtilityService utility) { + IDiscordRestUserAPI userApi, UtilityService utility) + { _channelApi = channelApi; _client = client; - _dataService = dataService; + _guildData = guildData; _eventApi = eventApi; _guildApi = guildApi; _logger = logger; @@ -76,17 +80,20 @@ public partial class GuildUpdateService : BackgroundService { /// /// Activates a periodic timer with a 1 second interval and adds guild update tasks on each timer tick. - /// Additionally, updates the current presence with songs from . + /// Additionally, updates the current presence with songs from . /// /// If update tasks take longer than 1 second, the next timer tick will be skipped. /// The cancellation token for this operation. - protected override async Task ExecuteAsync(CancellationToken ct) { + protected override async Task ExecuteAsync(CancellationToken ct) + { using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); var tasks = new List(); - while (await timer.WaitForNextTickAsync(ct)) { - var guildIds = _dataService.GetGuildIds(); - if (guildIds.Count > 0 && DateTimeOffset.UtcNow >= _nextSongAt) { + while (await timer.WaitForNextTickAsync(ct)) + { + var guildIds = _guildData.GetGuildIds(); + if (guildIds.Count > 0 && DateTimeOffset.UtcNow >= _nextSongAt) + { var nextSong = SongList[_nextSongIndex]; _activityList[0] = new Activity(nextSong.Name, ActivityType.Listening); _client.SubmitCommand( @@ -94,7 +101,10 @@ public partial class GuildUpdateService : BackgroundService { UserStatus.Online, false, DateTimeOffset.UtcNow, _activityList)); _nextSongAt = DateTimeOffset.UtcNow.Add(nextSong.Duration); _nextSongIndex++; - if (_nextSongIndex >= SongList.Length) _nextSongIndex = 0; + if (_nextSongIndex >= SongList.Length) + { + _nextSongIndex = 0; + } } tasks.AddRange(guildIds.Select(id => TickGuildAsync(id, ct))); @@ -111,9 +121,9 @@ public partial class GuildUpdateService : BackgroundService { /// This method does the following: /// /// Automatically unbans users once their ban period has expired. - /// Automatically grants members the guild's if one is set. + /// Automatically grants members the guild's if one is set. /// Sends reminders about an upcoming scheduled event. - /// Automatically starts scheduled events if is enabled. + /// Automatically starts scheduled events if is enabled. /// Sends scheduled event start notifications. /// Sends scheduled event completion notifications. /// Sends reminders to members. @@ -129,41 +139,62 @@ public partial class GuildUpdateService : BackgroundService { /// /// The ID of the guild to update. /// The cancellation token for this operation. - private async Task TickGuildAsync(Snowflake guildId, CancellationToken ct = default) { - var data = await _dataService.GetData(guildId, ct); + private async Task TickGuildAsync(Snowflake guildId, CancellationToken ct = default) + { + var data = await _guildData.GetData(guildId, ct); Messages.Culture = GuildSettings.Language.Get(data.Settings); var defaultRole = GuildSettings.DefaultRole.Get(data.Settings); - foreach (var memberData in data.MemberData.Values) { + foreach (var memberData in data.MemberData.Values) + { var guildMemberResult = await _guildApi.GetGuildMemberAsync(guildId, memberData.Id.ToSnowflake(), ct); - if (!guildMemberResult.IsDefined(out var guildMember)) return; - if (!guildMember.User.IsDefined(out var user)) return; + if (!guildMemberResult.IsDefined(out var guildMember)) + { + return; + } + + if (!guildMember.User.IsDefined(out var user)) + { + return; + } await TickMemberAsync(guildId, user, guildMember, memberData, defaultRole, data.Settings, ct); } var eventsResult = await _eventApi.ListScheduledEventsForGuildAsync(guildId, ct: ct); if (!eventsResult.IsSuccess) + { _logger.LogWarning("Error retrieving scheduled events.\n{ErrorMessage}", eventsResult.Error.Message); - else if (!GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) + return; + } + + if (!GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) + { await TickScheduledEventsAsync(guildId, data, eventsResult.Entity, ct); + } } private async Task TickScheduledEventsAsync( - Snowflake guildId, GuildData data, IEnumerable events, CancellationToken ct) { - foreach (var scheduledEvent in events) { + Snowflake guildId, GuildData data, IEnumerable events, CancellationToken ct) + { + foreach (var scheduledEvent in events) + { if (!data.ScheduledEvents.ContainsKey(scheduledEvent.ID.Value)) + { data.ScheduledEvents.Add(scheduledEvent.ID.Value, new ScheduledEventData(scheduledEvent.Status)); + } var storedEvent = data.ScheduledEvents[scheduledEvent.ID.Value]; - if (storedEvent.Status == scheduledEvent.Status) { + if (storedEvent.Status == scheduledEvent.Status) + { await TickScheduledEventAsync(guildId, data, scheduledEvent, storedEvent, ct); continue; } storedEvent.Status = scheduledEvent.Status; - var statusChangedResponseResult = storedEvent.Status switch { + var statusChangedResponseResult = storedEvent.Status switch + { GuildScheduledEventStatus.Scheduled => await SendScheduledEventCreatedMessage(scheduledEvent, data.Settings, ct), GuildScheduledEventStatus.Active or GuildScheduledEventStatus.Completed => @@ -172,16 +203,20 @@ public partial class GuildUpdateService : BackgroundService { }; if (!statusChangedResponseResult.IsSuccess) + { _logger.LogWarning( "Error handling scheduled event status update.\n{ErrorMessage}", statusChangedResponseResult.Error.Message); + } } } private async Task TickScheduledEventAsync( - Snowflake guildId, GuildData data, IGuildScheduledEvent scheduledEvent, ScheduledEventData eventData, - CancellationToken ct) { - if (DateTimeOffset.UtcNow >= scheduledEvent.ScheduledStartTime) { + Snowflake guildId, GuildData data, IGuildScheduledEvent scheduledEvent, ScheduledEventData eventData, + CancellationToken ct) + { + if (DateTimeOffset.UtcNow >= scheduledEvent.ScheduledStartTime) + { await TryAutoStartEventAsync(guildId, data, scheduledEvent, ct); return; } @@ -190,10 +225,14 @@ public partial class GuildUpdateService : BackgroundService { || eventData.EarlyNotificationSent || DateTimeOffset.UtcNow < scheduledEvent.ScheduledStartTime - - GuildSettings.EventEarlyNotificationOffset.Get(data.Settings)) return; + - GuildSettings.EventEarlyNotificationOffset.Get(data.Settings)) + { + return; + } var earlyResult = await SendEarlyEventNotificationAsync(scheduledEvent, data, ct); - if (earlyResult.IsSuccess) { + if (earlyResult.IsSuccess) + { eventData.EarlyNotificationSent = true; return; } @@ -204,54 +243,82 @@ public partial class GuildUpdateService : BackgroundService { } private async Task TryAutoStartEventAsync( - Snowflake guildId, GuildData data, IGuildScheduledEvent scheduledEvent, CancellationToken ct) { + Snowflake guildId, GuildData data, IGuildScheduledEvent scheduledEvent, CancellationToken ct) + { if (GuildSettings.AutoStartEvents.Get(data.Settings) - && scheduledEvent.Status is not GuildScheduledEventStatus.Active) { + && scheduledEvent.Status is not GuildScheduledEventStatus.Active) + { var startResult = await _eventApi.ModifyGuildScheduledEventAsync( guildId, scheduledEvent.ID, status: GuildScheduledEventStatus.Active, ct: ct); if (!startResult.IsSuccess) + { _logger.LogWarning( "Error in automatic scheduled event start request.\n{ErrorMessage}", startResult.Error.Message); + } } } private async Task TickMemberAsync( - Snowflake guildId, IUser user, IGuildMember member, MemberData memberData, Snowflake defaultRole, - JsonNode cfg, CancellationToken ct) { + Snowflake guildId, IUser user, IGuildMember member, MemberData memberData, Snowflake defaultRole, + JsonNode cfg, CancellationToken ct) + { if (defaultRole.Value is not 0 && !memberData.Roles.Contains(defaultRole.Value)) + { _ = _guildApi.AddGuildMemberRoleAsync( guildId, user.ID, defaultRole, ct: ct); + } - if (DateTimeOffset.UtcNow > memberData.BannedUntil) { + if (DateTimeOffset.UtcNow > memberData.BannedUntil) + { var unbanResult = await _guildApi.RemoveGuildBanAsync( guildId, user.ID, Messages.PunishmentExpired.EncodeHeader(), ct); - if (unbanResult.IsSuccess) - memberData.BannedUntil = null; - else + if (!unbanResult.IsSuccess) + { _logger.LogWarning( "Error in automatic user unban request.\n{ErrorMessage}", unbanResult.Error.Message); + return; + } + + memberData.BannedUntil = null; } for (var i = memberData.Reminders.Count - 1; i >= 0; i--) + { await TickReminderAsync(memberData.Reminders[i], user, memberData, ct); - if (GuildSettings.RenameHoistedUsers.Get(cfg)) await FilterNicknameAsync(guildId, user, member, ct); + } + + if (GuildSettings.RenameHoistedUsers.Get(cfg)) + { + await FilterNicknameAsync(guildId, user, member, ct); + } } - private Task FilterNicknameAsync(Snowflake guildId, IUser user, IGuildMember member, CancellationToken ct) { + private Task FilterNicknameAsync(Snowflake guildId, IUser user, IGuildMember member, CancellationToken ct) + { var currentNickname = member.Nickname.IsDefined(out var nickname) ? nickname : user.GlobalName ?? user.Username; var characterList = currentNickname.ToList(); var usernameChanged = false; foreach (var character in currentNickname) - if (IllegalCharsRegex().IsMatch(character.ToString())) { + { + if (IllegalChars().IsMatch(character.ToString())) + { characterList.Remove(character); usernameChanged = true; - } else { break; } + continue; + } + + break; + } + + if (!usernameChanged) + { + return Task.CompletedTask; + } - if (!usernameChanged) return Task.CompletedTask; var newNickname = string.Concat(characterList.ToArray()); _ = _guildApi.ModifyGuildMemberAsync( @@ -264,10 +331,14 @@ public partial class GuildUpdateService : BackgroundService { } [GeneratedRegex("[^0-9A-zЁА-яё]")] - private static partial Regex IllegalCharsRegex(); + private static partial Regex IllegalChars(); - private async Task TickReminderAsync(Reminder reminder, IUser user, MemberData memberData, CancellationToken ct) { - if (DateTimeOffset.UtcNow < reminder.At) return; + private async Task TickReminderAsync(Reminder reminder, IUser user, MemberData memberData, CancellationToken ct) + { + if (DateTimeOffset.UtcNow < reminder.At) + { + return; + } var embed = new EmbedBuilder().WithSmallTitle( string.Format(Messages.Reminder, user.GetTag()), user) @@ -276,13 +347,18 @@ public partial class GuildUpdateService : BackgroundService { .WithColour(ColorsList.Magenta) .Build(); - if (!embed.IsDefined(out var built)) return; + if (!embed.IsDefined(out var built)) + { + return; + } var messageResult = await _channelApi.CreateMessageAsync( reminder.Channel.ToSnowflake(), Mention.User(user), embeds: new[] { built }, ct: ct); if (!messageResult.IsSuccess) + { _logger.LogWarning( "Error in reminder send.\n{ErrorMessage}", messageResult.Error.Message); + } memberData.Reminders.Remove(reminder); } @@ -298,15 +374,19 @@ public partial class GuildUpdateService : BackgroundService { /// The cancellation token for this operation. /// A notification sending result which may or may not have succeeded. private async Task SendScheduledEventCreatedMessage( - IGuildScheduledEvent scheduledEvent, JsonNode settings, CancellationToken ct = default) { + IGuildScheduledEvent scheduledEvent, JsonNode settings, CancellationToken ct = default) + { if (!scheduledEvent.Creator.IsDefined(out var creator)) + { return new ArgumentNullError(nameof(scheduledEvent.Creator)); + } Result embedDescriptionResult; var eventDescription = scheduledEvent.Description is { HasValue: true, Value: not null } ? scheduledEvent.Description.Value : string.Empty; - embedDescriptionResult = scheduledEvent.EntityType switch { + embedDescriptionResult = scheduledEvent.EntityType switch + { GuildScheduledEventEntityType.StageInstance or GuildScheduledEventEntityType.Voice => GetLocalEventCreatedEmbedDescription(scheduledEvent, eventDescription), GuildScheduledEventEntityType.External => GetExternalScheduledEventCreatedEmbedDescription( @@ -315,7 +395,9 @@ public partial class GuildUpdateService : BackgroundService { }; if (!embedDescriptionResult.IsDefined(out var embedDescription)) + { return Result.FromError(embedDescriptionResult); + } var embed = new EmbedBuilder() .WithSmallTitle(string.Format(Messages.EventCreatedTitle, creator.GetTag()), creator) @@ -325,7 +407,10 @@ public partial class GuildUpdateService : BackgroundService { .WithCurrentTimestamp() .WithColour(ColorsList.White) .Build(); - if (!embed.IsDefined(out var built)) return Result.FromError(embed); + if (!embed.IsDefined(out var built)) + { + return Result.FromError(embed); + } var roleMention = !GuildSettings.EventNotificationRole.Get(settings).Empty() ? Mention.Role(GuildSettings.EventNotificationRole.Get(settings)) @@ -345,14 +430,23 @@ public partial class GuildUpdateService : BackgroundService { } private static Result GetExternalScheduledEventCreatedEmbedDescription( - IGuildScheduledEvent scheduledEvent, string eventDescription) { + IGuildScheduledEvent scheduledEvent, string eventDescription) + { Result embedDescription; if (!scheduledEvent.EntityMetadata.AsOptional().IsDefined(out var metadata)) + { return new ArgumentNullError(nameof(scheduledEvent.EntityMetadata)); + } + if (!scheduledEvent.ScheduledEndTime.AsOptional().IsDefined(out var endTime)) + { return new ArgumentNullError(nameof(scheduledEvent.ScheduledEndTime)); + } + if (!metadata.Location.IsDefined(out var location)) + { return new ArgumentNullError(nameof(metadata.Location)); + } embedDescription = $"{eventDescription}\n\n{Markdown.BlockQuote( string.Format( @@ -365,9 +459,12 @@ public partial class GuildUpdateService : BackgroundService { } private static Result GetLocalEventCreatedEmbedDescription( - IGuildScheduledEvent scheduledEvent, string eventDescription) { + IGuildScheduledEvent scheduledEvent, string eventDescription) + { if (!scheduledEvent.ChannelID.AsOptional().IsDefined(out var channelId)) + { return new ArgumentNullError(nameof(scheduledEvent.ChannelID)); + } return $"{eventDescription}\n\n{Markdown.BlockQuote( string.Format( @@ -378,7 +475,8 @@ public partial class GuildUpdateService : BackgroundService { } /// - /// Handles sending a notification, mentioning the and event subscribers, + /// Handles sending a notification, mentioning the and event + /// subscribers, /// when a scheduled event has started or completed /// in a guild's if one is set. /// @@ -387,11 +485,14 @@ public partial class GuildUpdateService : BackgroundService { /// The cancellation token for this operation /// A reminder/notification sending result which may or may not have succeeded. private async Task SendScheduledEventUpdatedMessage( - IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct = default) { - if (scheduledEvent.Status == GuildScheduledEventStatus.Active) { + IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct = default) + { + if (scheduledEvent.Status == GuildScheduledEventStatus.Active) + { data.ScheduledEvents[scheduledEvent.ID.Value].ActualStartTime = DateTimeOffset.UtcNow; - var embedDescriptionResult = scheduledEvent.EntityType switch { + var embedDescriptionResult = scheduledEvent.EntityType switch + { GuildScheduledEventEntityType.StageInstance or GuildScheduledEventEntityType.Voice => GetLocalEventStartedEmbedDescription(scheduledEvent), GuildScheduledEventEntityType.External => GetExternalEventStartedEmbedDescription(scheduledEvent), @@ -401,9 +502,14 @@ public partial class GuildUpdateService : BackgroundService { var contentResult = await _utility.GetEventNotificationMentions( scheduledEvent, data.Settings, ct); if (!contentResult.IsDefined(out var content)) + { return Result.FromError(contentResult); + } + if (!embedDescriptionResult.IsDefined(out var embedDescription)) + { return Result.FromError(embedDescriptionResult); + } var startedEmbed = new EmbedBuilder().WithTitle(string.Format(Messages.EventStarted, scheduledEvent.Name)) .WithDescription(embedDescription) @@ -411,7 +517,10 @@ public partial class GuildUpdateService : BackgroundService { .WithCurrentTimestamp() .Build(); - if (!startedEmbed.IsDefined(out var startedBuilt)) return Result.FromError(startedEmbed); + if (!startedEmbed.IsDefined(out var startedBuilt)) + { + return Result.FromError(startedEmbed); + } return (Result)await _channelApi.CreateMessageAsync( GuildSettings.EventNotificationChannel.Get(data.Settings), @@ -419,7 +528,10 @@ public partial class GuildUpdateService : BackgroundService { } if (scheduledEvent.Status != GuildScheduledEventStatus.Completed) + { return new ArgumentOutOfRangeError(nameof(scheduledEvent.Status)); + } + data.ScheduledEvents.Remove(scheduledEvent.ID.Value); var completedEmbed = new EmbedBuilder().WithTitle(string.Format(Messages.EventCompleted, scheduledEvent.Name)) @@ -434,17 +546,22 @@ public partial class GuildUpdateService : BackgroundService { .Build(); if (!completedEmbed.IsDefined(out var completedBuilt)) + { return Result.FromError(completedEmbed); + } return (Result)await _channelApi.CreateMessageAsync( GuildSettings.EventNotificationChannel.Get(data.Settings), embeds: new[] { completedBuilt }, ct: ct); } - private static Result GetLocalEventStartedEmbedDescription(IGuildScheduledEvent scheduledEvent) { + private static Result GetLocalEventStartedEmbedDescription(IGuildScheduledEvent scheduledEvent) + { Result embedDescription; if (!scheduledEvent.ChannelID.AsOptional().IsDefined(out var channelId)) + { return new ArgumentNullError(nameof(scheduledEvent.ChannelID)); + } embedDescription = string.Format( Messages.DescriptionLocalEventStarted, @@ -453,14 +570,23 @@ public partial class GuildUpdateService : BackgroundService { return embedDescription; } - private static Result GetExternalEventStartedEmbedDescription(IGuildScheduledEvent scheduledEvent) { + private static Result GetExternalEventStartedEmbedDescription(IGuildScheduledEvent scheduledEvent) + { Result embedDescription; if (!scheduledEvent.EntityMetadata.AsOptional().IsDefined(out var metadata)) + { return new ArgumentNullError(nameof(scheduledEvent.EntityMetadata)); + } + if (!scheduledEvent.ScheduledEndTime.AsOptional().IsDefined(out var endTime)) + { return new ArgumentNullError(nameof(scheduledEvent.ScheduledEndTime)); + } + if (!metadata.Location.IsDefined(out var location)) + { return new ArgumentNullError(nameof(metadata.Location)); + } embedDescription = string.Format( Messages.DescriptionExternalEventStarted, @@ -471,14 +597,20 @@ public partial class GuildUpdateService : BackgroundService { } private async Task SendEarlyEventNotificationAsync( - IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct) { + IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct) + { var currentUserResult = await _userApi.GetCurrentUserAsync(ct); - if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult); + if (!currentUserResult.IsDefined(out var currentUser)) + { + return Result.FromError(currentUserResult); + } var contentResult = await _utility.GetEventNotificationMentions( scheduledEvent, data.Settings, ct); if (!contentResult.IsDefined(out var content)) + { return Result.FromError(contentResult); + } var earlyResult = new EmbedBuilder() .WithSmallTitle(string.Format(Messages.EventEarlyNotification, scheduledEvent.Name), currentUser) @@ -486,7 +618,10 @@ public partial class GuildUpdateService : BackgroundService { .WithCurrentTimestamp() .Build(); - if (!earlyResult.IsDefined(out var earlyBuilt)) return Result.FromError(earlyResult); + if (!earlyResult.IsDefined(out var earlyBuilt)) + { + return Result.FromError(earlyResult); + } return (Result)await _channelApi.CreateMessageAsync( GuildSettings.EventNotificationChannel.Get(data.Settings), diff --git a/src/Services/UtilityService.cs b/src/Services/UtilityService.cs index 21aade1..35a11dd 100644 --- a/src/Services/UtilityService.cs +++ b/src/Services/UtilityService.cs @@ -16,26 +16,30 @@ namespace Boyfriend.Services; /// Provides utility methods that cannot be transformed to extension methods because they require usage /// of some Discord APIs. /// -public class UtilityService : IHostedService { - private readonly IDiscordRestChannelAPI _channelApi; +public sealed class UtilityService : IHostedService +{ + private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestGuildScheduledEventAPI _eventApi; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly IDiscordRestUserAPI _userApi; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly IDiscordRestUserAPI _userApi; public UtilityService( IDiscordRestChannelAPI channelApi, IDiscordRestGuildScheduledEventAPI eventApi, IDiscordRestGuildAPI guildApi, - IDiscordRestUserAPI userApi) { + IDiscordRestUserAPI userApi) + { _channelApi = channelApi; _eventApi = eventApi; _guildApi = guildApi; _userApi = userApi; } - public Task StartAsync(CancellationToken ct) { + public Task StartAsync(CancellationToken ct) + { return Task.CompletedTask; } - public Task StopAsync(CancellationToken ct) { + public Task StopAsync(CancellationToken ct) + { return Task.CompletedTask; } @@ -58,29 +62,42 @@ public class UtilityService : IHostedService { /// /// public async Task> CheckInteractionsAsync( - Snowflake guildId, Snowflake interacterId, Snowflake targetId, string action, CancellationToken ct = default) { + Snowflake guildId, Snowflake interacterId, Snowflake targetId, string action, CancellationToken ct = default) + { if (interacterId == targetId) + { return Result.FromSuccess($"UserCannot{action}Themselves".Localized()); + } var currentUserResult = await _userApi.GetCurrentUserAsync(ct); if (!currentUserResult.IsDefined(out var currentUser)) + { return Result.FromError(currentUserResult); + } var guildResult = await _guildApi.GetGuildAsync(guildId, ct: ct); if (!guildResult.IsDefined(out var guild)) + { return Result.FromError(guildResult); + } var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct); if (!targetMemberResult.IsDefined(out var targetMember)) + { return Result.FromSuccess(null); + } var currentMemberResult = await _guildApi.GetGuildMemberAsync(guildId, currentUser.ID, ct); if (!currentMemberResult.IsDefined(out var currentMember)) + { return Result.FromError(currentMemberResult); + } var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct); if (!rolesResult.IsDefined(out var roles)) + { return Result.FromError(rolesResult); + } var interacterResult = await _guildApi.GetGuildMemberAsync(guildId, interacterId, ct); return interacterResult.IsDefined(out var interacter) @@ -90,26 +107,41 @@ public class UtilityService : IHostedService { private static Result CheckInteractions( string action, IGuild guild, IReadOnlyList roles, IGuildMember targetMember, IGuildMember currentMember, - IGuildMember interacter) { + IGuildMember interacter) + { if (!targetMember.User.IsDefined(out var targetUser)) + { return new ArgumentNullError(nameof(targetMember.User)); + } + if (!interacter.User.IsDefined(out var interacterUser)) + { return new ArgumentNullError(nameof(interacter.User)); + } if (currentMember.User == targetMember.User) + { return Result.FromSuccess($"UserCannot{action}Bot".Localized()); + } - if (targetUser.ID == guild.OwnerID) return Result.FromSuccess($"UserCannot{action}Owner".Localized()); + if (targetUser.ID == guild.OwnerID) + { + return Result.FromSuccess($"UserCannot{action}Owner".Localized()); + } var targetRoles = roles.Where(r => targetMember.Roles.Contains(r.ID)).ToList(); var botRoles = roles.Where(r => currentMember.Roles.Contains(r.ID)); var targetBotRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - botRoles.MaxOrDefault(r => r.Position); if (targetBotRoleDiff >= 0) + { return Result.FromSuccess($"BotCannot{action}Target".Localized()); + } if (interacterUser.ID == guild.OwnerID) + { return Result.FromSuccess(null); + } var interacterRoles = roles.Where(r => interacter.Roles.Contains(r.ID)); var targetInteracterRoleDiff @@ -120,7 +152,8 @@ public class UtilityService : IHostedService { } /// - /// Gets the string mentioning the and event subscribers related to a scheduled + /// Gets the string mentioning the and event subscribers related to + /// a scheduled /// event. /// /// @@ -130,21 +163,24 @@ public class UtilityService : IHostedService { /// The cancellation token for this operation. /// A result containing the string which may or may not have succeeded. public async Task> GetEventNotificationMentions( - IGuildScheduledEvent scheduledEvent, JsonNode settings, CancellationToken ct = default) { + IGuildScheduledEvent scheduledEvent, JsonNode settings, CancellationToken ct = default) + { var builder = new StringBuilder(); var role = GuildSettings.EventNotificationRole.Get(settings); var usersResult = await _eventApi.GetGuildScheduledEventUsersAsync( scheduledEvent.GuildID, scheduledEvent.ID, withMember: true, ct: ct); - if (!usersResult.IsDefined(out var users)) return Result.FromError(usersResult); + if (!usersResult.IsDefined(out var users)) + { + return Result.FromError(usersResult); + } if (role.Value is not 0) + { builder.Append($"{Mention.Role(role)} "); + } builder = users.Where( - user => { - if (!user.GuildMember.IsDefined(out var member)) return true; - return !member.Roles.Contains(role); - }) + user => user.GuildMember.IsDefined(out var member) && !member.Roles.Contains(role)) .Aggregate(builder, (current, user) => current.Append($"{Mention.User(user.User)} ")); return builder.ToString(); } @@ -160,17 +196,22 @@ public class UtilityService : IHostedService { /// The description of the embed. /// The user whose avatar will be displayed next to the of the embed. /// The color of the embed. - /// Whether or not the embed should be sent in + /// + /// Whether or not the embed should be sent in + /// /// The cancellation token for this operation. /// A result which has succeeded. public Result LogActionAsync( - JsonNode cfg, Snowflake channelId, IUser user, string title, string description, IUser avatar, - Color color, bool isPublic = true, CancellationToken ct = default) { + JsonNode cfg, Snowflake channelId, IUser user, string title, string description, IUser avatar, + Color color, bool isPublic = true, CancellationToken ct = default) + { var publicChannel = GuildSettings.PublicFeedbackChannel.Get(cfg); var privateChannel = GuildSettings.PrivateFeedbackChannel.Get(cfg); if (GuildSettings.PublicFeedbackChannel.Get(cfg).EmptyOrEqualTo(channelId) && GuildSettings.PrivateFeedbackChannel.Get(cfg).EmptyOrEqualTo(channelId)) + { return Result.FromSuccess(); + } var logEmbed = new EmbedBuilder().WithSmallTitle(title, avatar) .WithDescription(description) @@ -180,20 +221,27 @@ public class UtilityService : IHostedService { .Build(); if (!logEmbed.IsDefined(out var logBuilt)) + { return Result.FromError(logEmbed); + } var builtArray = new[] { logBuilt }; // Not awaiting to reduce response time if (isPublic && publicChannel != channelId) + { _ = _channelApi.CreateMessageAsync( publicChannel, embeds: builtArray, ct: ct); + } + if (privateChannel != publicChannel && privateChannel != channelId) + { _ = _channelApi.CreateMessageAsync( privateChannel, embeds: builtArray, ct: ct); + } return Result.FromSuccess(); }