diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index adbec5a..0000000 --- a/.editorconfig +++ /dev/null @@ -1,1766 +0,0 @@ -[*] -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_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 = true: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.cs0282.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.cs8519.severity = warning -dotnet_diagnostic.cs8520.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.cs8974.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.cs9099.severity = warning -dotnet_diagnostic.cs9100.severity = warning -dotnet_diagnostic.cs9113.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_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_comments = true -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_member_initializer_list = true -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_raw_literal_string = align -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_bitfield_sizes = 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_bitfield_declarator = 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_bitfield_declarator = true -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_after_property_in_chained_method_calls = 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_first_method_call = false -resharper_wrap_before_invocation_lpar = false -resharper_wrap_before_invocation_rpar = false -resharper_wrap_before_linq_expression = false -resharper_wrap_before_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_all_underscore_local_parameter_name_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_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_static_member_qualifier_highlighting = warning -resharper_arrange_trailing_comma_in_multiline_lists_highlighting = warning -resharper_arrange_trailing_comma_in_singleline_lists_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_instead_of_discard_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_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_consteval_if_is_always_constant_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_constructor_to_member_initializers_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 = none -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_c_sharp_build_cs_invalid_module_name_highlighting = warning -resharper_c_sharp_missing_plugin_dependency_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_if_std_is_constant_evaluated_can_be_replaced_highlighting = warning -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_local_function_after_jump_statement_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_nullness_annotation_conflict_with_jet_brains_annotations_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_possibly_unintended_usage_parameterless_get_expression_type_highlighting = error -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_raw_string_can_be_simplified_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_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_directive_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_type_declaration_body_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_cancellation_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_primary_constructor_parameter_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_of_task_produced_by_using_variable_highlighting = warning -resharper_return_of_using_variable_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_separate_local_functions_with_jump_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_std_is_constant_evaluated_will_always_evaluate_to_constant_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_deconstruction_declarations_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_nullable_directive_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_discard_assignment_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_raw_string_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_using_statement_resource_initialization_expression_highlighting = warning -resharper_using_statement_resource_initialization_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 - -[*.yml] -indent_style = space -indent_size = 2 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 93a190c..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,2 +0,0 @@ -* @TeamOctolings/octobot -/docs/ @TeamOctolings/octobot-docs diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml deleted file mode 100644 index 9c0524f..0000000 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Bug Report -description: Create a report to help us improve -labels: [ "type: bug" ] -body: - - type: markdown - attributes: - value: | - We welcome bug reports! Please see our [contribution guidelines](docs/CONTRIBUTING.md#reporting-bugs) for more information on writing a good bug report. This template will help us gather the information we need to start the triage process. - - type: textarea - id: background - attributes: - label: Description - description: Please share a clear and concise description of the problem. - placeholder: Description - validations: - required: true - - type: textarea - id: expected-vs-actual-behavior - attributes: - label: Expected vs. Actual Behavior - description: | - Provide a description of the expected behavior compared to the actual behavior. - placeholder: Expected vs. Actual Behavior - validations: - required: true - - type: textarea - id: repro-steps - attributes: - label: Reproduction Steps - description: | - Please include minimal steps to reproduce the problem if possible. E.g.: the smallest possible command/action sequence. If possible include text as text rather than screenshots (so it shows up in searches). - placeholder: Minimal Reproduction - validations: - required: true - - type: textarea - id: other-info - attributes: - label: Other Information - description: | - If you have an idea where the problem might lie, let us know that here. Please include any pointers to code, relevant changes, or related issues you know of. - placeholder: Other Information - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 0086358..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1 +0,0 @@ -blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index 2fd43c5..0000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Feature Request -description: Create a request for a feature you would like -labels: [ "type: feature" ] -body: - - type: textarea - id: background - attributes: - label: Description - description: Please share a clear and concise description of the feature you want. - placeholder: Description - validations: - required: true - - type: textarea - id: proposed-solution - attributes: - label: Proposed Solution - description: Please describe the solution you would like. - placeholder: Proposed Solution - validations: - required: true - - type: textarea - id: other-info - attributes: - label: Other Information - description: | - Please add any other context or screenshots about the feature request here. - placeholder: Other Information - validations: - required: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index b961b81..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,41 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "github-actions" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" - allow: - # Allow both direct and indirect updates for all packages - - dependency-type: "all" - labels: - - "type: change" - - "area: build/ci" - # For all packages, ignore all patch updates - ignore: - - dependency-name: "*" - update-types: [ "version-update:semver-patch" ] - - - package-ecosystem: "nuget" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" - allow: - # Allow both direct and indirect updates for all packages - - dependency-type: "all" - labels: - - "type: change" - - "area: build/ci" - groups: - remora: - patterns: - - "Remora.Discord.*" - # For all packages, ignore all patch updates - ignore: - - dependency-name: "GitInfo" - - dependency-name: "*" - update-types: [ "version-update:semver-patch" ] diff --git a/.github/labels.yml b/.github/labels.yml deleted file mode 100644 index 8187db4..0000000 --- a/.github/labels.yml +++ /dev/null @@ -1,24 +0,0 @@ -XS: - name: size/XS - lines: 0 - color: 3CBF00 -S: - name: size/S - lines: 20 - color: 5D9801 -M: - name: size/M - lines: 100 - color: 7F7203 -L: - name: size/L - lines: 200 - color: A14C05 -XL: - name: size/XL - lines: 1000 - color: C32607 -XXL: - name: size/XXL - lines: 2000 - color: E50009 diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml deleted file mode 100644 index 07d5b90..0000000 --- a/.github/workflows/build-pr.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: "ReSharper" -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - pull_request: - branches: [ "master" ] - merge_group: - types: [ checks_requested ] - -jobs: - inspect-code: - name: Inspect code - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '9.0.x' - - - name: ReSharper CLI InspectCode - uses: muno92/resharper_inspectcode@1.13.0 - with: - solutionPath: ./Octobot.sln - ignoreIssueType: InvertIf, ConvertIfStatementToSwitchStatement, ConvertToPrimaryConstructor - extensions: ReSharperPlugin.CognitiveComplexity - solutionWideAnalysis: true diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml deleted file mode 100644 index e7afe8e..0000000 --- a/.github/workflows/build-push.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: "Publish and deploy" -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - push: - branches: [ "master", "deploy-test" ] - -jobs: - upload-image: - name: Upload Octobot Docker image - runs-on: ubuntu-latest - permissions: - packages: write - environment: production - - steps: - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push Docker image - uses: docker/build-push-action@v6 - with: - push: true - tags: ghcr.io/${{vars.NAMESPACE}}/${{vars.IMAGE_NAME}}:latest - build-args: | - BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 - PUBLISH_OPTIONS=${{vars.PUBLISH_OPTIONS}} - - update-production: - name: Update Octobot on production - runs-on: ubuntu-latest - environment: production - needs: upload-image - - steps: - - name: Copy SSH key - run: | - install -m 600 -D /dev/null ~/.ssh/id_ed25519 - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519 - shell: bash - env: - SSH_PRIVATE_KEY: ${{secrets.SSH_PRIVATE_KEY}} - - - name: Generate SSH known hosts file - run: | - ssh-keyscan -H -p $SSH_PORT $SSH_HOST > ~/.ssh/known_hosts - shell: bash - env: - SSH_HOST: ${{secrets.SSH_HOST}} - SSH_PORT: ${{secrets.SSH_PORT}} - - - name: Stop currently running instance - run: | - ssh -p $SSH_PORT $SSH_USER@$SSH_HOST $STOP_COMMAND - shell: bash - env: - SSH_PORT: ${{secrets.SSH_PORT}} - SSH_USER: ${{secrets.SSH_USER}} - SSH_HOST: ${{secrets.SSH_HOST}} - STOP_COMMAND: ${{vars.STOP_COMMAND}} - - - name: Update Docker image - run: | - ssh -p $SSH_PORT $SSH_USER@$SSH_HOST docker pull ghcr.io/$NAMESPACE/$IMAGE_NAME:latest - shell: bash - env: - SSH_PORT: ${{secrets.SSH_PORT}} - SSH_USER: ${{secrets.SSH_USER}} - SSH_HOST: ${{secrets.SSH_HOST}} - NAMESPACE: ${{vars.NAMESPACE}} - IMAGE_NAME: ${{vars.IMAGE_NAME}} - - - name: Start new instance - run: | - ssh -p $SSH_PORT $SSH_USER@$SSH_HOST $START_COMMAND - shell: bash - env: - SSH_PORT: ${{secrets.SSH_PORT}} - SSH_USER: ${{secrets.SSH_USER}} - SSH_HOST: ${{secrets.SSH_HOST}} - START_COMMAND: ${{vars.START_COMMAND}} diff --git a/.gitignore b/.gitignore index fcda727..9f11b75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1 @@ .idea/ -*.user -bin/ -obj/ -/packages/ -riderModule.iml -/_ReSharper.Caches/ -/.vs/ -GuildData/ -Logs/ -compose.yaml diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt deleted file mode 100644 index bf444a9..0000000 --- a/CodeAnalysis/BannedSymbols.txt +++ /dev/null @@ -1,22 +0,0 @@ -M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead. -M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable or EqualityComparer.Default instead. -M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead. -T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. -M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty. -M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. -P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. -M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. -M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture. -M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture. -M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. -M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. -M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead. -M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead. -M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead. -M:Humanizer.InflectorExtensions.Kebaberize(System.String);Humanizer's .Kebaberize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToKebabCase() instead. -P:System.DateTime.Now;Use System.DateTime.UtcNow instead. -P:System.DateTimeOffset.Now;Use System.DateTimeOffset.UtcNow instead. -P:System.DateTimeOffset.DateTime;Use System.DateTimeOffset.UtcDateTime instead. -M:System.IO.File.OpenWrite(System.String);File.OpenWrite(string) does not clear the file before writing to it. Use File.Create(string) instead. -M:System.Threading.Thread.Sleep(System.Int32);Use Task.Delay(int, CancellationToken) instead. -M:System.Threading.Thread.Sleep(System.TimeSpan);Use Task.Delay(TimeSpan, CancellationToken) instead. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 6cfeac6..0000000 --- a/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:7d24e90a392e88eb56093e4eb325ff883ad609382a55d42f17fd557b997022ca AS build-env -WORKDIR /Octobot - -# Copy everything -COPY . ./ -# Load build argument with publish options -ARG PUBLISH_OPTIONS="-c Release" -# Build and publish a release -RUN dotnet publish ./TeamOctolings.Octobot $PUBLISH_OPTIONS -o out - -# Build runtime image -FROM mcr.microsoft.com/dotnet/runtime:9.0@sha256:1e5eb0ed94ca96a34a914456db80e48bd1bb7bc3e3c8eda5e2c3d89c153c3081 -WORKDIR /Octobot -COPY --from=build-env /Octobot/out . -ENTRYPOINT ["./TeamOctolings.Octobot"] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 593265c..0000000 --- a/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/Octobot.sln b/Octobot.sln deleted file mode 100644 index b82f7a9..0000000 --- a/Octobot.sln +++ /dev/null @@ -1,16 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamOctolings.Octobot", "TeamOctolings.Octobot\TeamOctolings.Octobot.csproj", "{A1679BA2-3A36-4D98-80C0-EEE771398FBD}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/TeamOctolings.Octobot/Attributes/StaticCallersOnlyAttribute.cs b/TeamOctolings.Octobot/Attributes/StaticCallersOnlyAttribute.cs deleted file mode 100644 index 0256f62..0000000 --- a/TeamOctolings.Octobot/Attributes/StaticCallersOnlyAttribute.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace TeamOctolings.Octobot.Attributes; - -/// -/// Any property marked with should only be accessed by static methods. -/// Such properties may be used to provide dependencies where it is not possible to acquire them through normal means. -/// -[AttributeUsage(AttributeTargets.Property)] -public sealed class StaticCallersOnlyAttribute : Attribute; diff --git a/TeamOctolings.Octobot/BuildInfo.cs b/TeamOctolings.Octobot/BuildInfo.cs deleted file mode 100644 index a91e7f3..0000000 --- a/TeamOctolings.Octobot/BuildInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace TeamOctolings.Octobot; - -public static class BuildInfo -{ - public const string WebsiteUrl = "https://teamoctolings.github.io/Octobot"; - - private const string RepositoryUrl = "https://github.com/TeamOctolings/Octobot"; - - public const string IssuesUrl = $"{RepositoryUrl}/issues"; - - public const string WikiUrl = $"{RepositoryUrl}/wiki"; - - private const string Commit = ThisAssembly.Git.Commit; - - private const string Branch = ThisAssembly.Git.Branch; - - public static bool IsDirty => ThisAssembly.Git.IsDirty; - - public static string Version => IsDirty ? $"{Branch}-{Commit}-dirty" : $"{Branch}-{Commit}"; -} diff --git a/TeamOctolings.Octobot/ColorsList.cs b/TeamOctolings.Octobot/ColorsList.cs deleted file mode 100644 index 3b66c0a..0000000 --- a/TeamOctolings.Octobot/ColorsList.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Drawing; - -namespace TeamOctolings.Octobot; - -/// -/// Contains all colors used in embeds. -/// -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 Magenta = Color.Orchid; - public static readonly Color Cyan = Color.LightSkyBlue; - public static readonly Color Black = Color.Black; - public static readonly Color White = Color.WhiteSmoke; -} diff --git a/TeamOctolings.Octobot/Commands/AboutCommandGroup.cs b/TeamOctolings.Octobot/Commands/AboutCommandGroup.cs deleted file mode 100644 index 28caccf..0000000 --- a/TeamOctolings.Octobot/Commands/AboutCommandGroup.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System.ComponentModel; -using System.Text; -using JetBrains.Annotations; -using Remora.Commands.Attributes; -using Remora.Commands.Groups; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.API.Objects; -using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Conditions; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Messages; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Commands; - -/// -/// Handles the command to show information about this bot: /about. -/// -[UsedImplicitly] -public sealed class AboutCommandGroup : CommandGroup -{ - private static readonly (string Username, Snowflake Id)[] Developers = - [ - ("Octol1ttle", new Snowflake(504343489664909322)), - ("mctaylors", new Snowflake(326642240229474304)), - ("neroduckale", new Snowflake(474943797063843851)) - ]; - - private readonly ICommandContext _context; - private readonly IFeedbackService _feedback; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; - - public AboutCommandGroup( - ICommandContext context, GuildDataService guildData, - IFeedbackService feedback, IDiscordRestUserAPI userApi, - IDiscordRestGuildAPI guildApi) - { - _context = context; - _guildData = guildData; - _feedback = feedback; - _userApi = userApi; - _guildApi = guildApi; - } - - /// - /// A slash command that shows information about this bot. - /// - /// - /// A feedback sending result which may or may not have succeeded. - /// - [Command("about")] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [Description("Shows Octobot's developers")] - [UsedImplicitly] - 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 botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var cfg = await _guildData.GetSettings(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(cfg); - - return await SendAboutBotAsync(bot, guildId, CancellationToken); - } - - private async Task SendAboutBotAsync(IUser bot, Snowflake guildId, CancellationToken ct = default) - { - var builder = new StringBuilder().Append("### ").AppendLine(Messages.AboutTitleDevelopers); - foreach (var dev in Developers) - { - var guildMemberResult = await _guildApi.GetGuildMemberAsync( - guildId, dev.Id, ct); - var tag = guildMemberResult.IsSuccess - ? $"<@{dev.Id}>" - : Markdown.Hyperlink($"@{dev.Username}", $"https://github.com/{dev.Username}"); - - builder.AppendBulletPointLine($"{tag} — {$"AboutDeveloper@{dev.Username}".Localized()}"); - } - - var embed = new EmbedBuilder() - .WithSmallTitle(string.Format(Messages.AboutBot, bot.Username), bot) - .WithDescription(builder.ToString()) - .WithColour(ColorsList.Cyan) - .WithImageUrl("https://raw.githubusercontent.com/TeamOctolings/Octobot/HEAD/docs/octobot-banner.png") - .WithFooter(string.Format(Messages.Version, BuildInfo.Version)) - .Build(); - - var repositoryButton = new ButtonComponent( - ButtonComponentStyle.Link, - Messages.ButtonOpenWebsite, - new PartialEmoji(Name: "\ud83c\udf10"), // 'GLOBE WITH MERIDIANS' (U+1F310) - URL: BuildInfo.WebsiteUrl - ); - - var wikiButton = new ButtonComponent( - ButtonComponentStyle.Link, - Messages.ButtonOpenWiki, - new PartialEmoji(Name: "\ud83d\udcd6"), // 'OPEN BOOK' (U+1F4D6) - URL: BuildInfo.WikiUrl - ); - - var issuesButton = new ButtonComponent( - ButtonComponentStyle.Link, - BuildInfo.IsDirty - ? Messages.ButtonDirty - : Messages.ButtonReportIssue, - new PartialEmoji(Name: "\u26a0\ufe0f"), // 'WARNING SIGN' (U+26A0) - URL: BuildInfo.IssuesUrl, - IsDisabled: BuildInfo.IsDirty - ); - - return await _feedback.SendContextualEmbedResultAsync(embed, - new FeedbackMessageOptions(MessageComponents: new[] - { - new ActionRowComponent([repositoryButton, wikiButton, issuesButton]) - }), ct); - } -} diff --git a/TeamOctolings.Octobot/Commands/BanCommandGroup.cs b/TeamOctolings.Octobot/Commands/BanCommandGroup.cs deleted file mode 100644 index 1d6b26c..0000000 --- a/TeamOctolings.Octobot/Commands/BanCommandGroup.cs +++ /dev/null @@ -1,300 +0,0 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Text; -using JetBrains.Annotations; -using Remora.Commands.Attributes; -using Remora.Commands.Groups; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Conditions; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Parsers; -using TeamOctolings.Octobot.Services; -using TeamOctolings.Octobot.Services.Update; - -namespace TeamOctolings.Octobot.Commands; - -/// -/// Handles commands related to ban management: /ban and /unban. -/// -[UsedImplicitly] -public sealed class BanCommandGroup : CommandGroup -{ - private readonly AccessControlService _access; - private readonly IDiscordRestChannelAPI _channelApi; - private readonly ICommandContext _context; - private readonly IFeedbackService _feedback; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; - private readonly Utility _utility; - - public BanCommandGroup(AccessControlService access, IDiscordRestChannelAPI channelApi, ICommandContext context, - IFeedbackService feedback, IDiscordRestGuildAPI guildApi, GuildDataService guildData, - IDiscordRestUserAPI userApi, Utility utility) - { - _access = access; - _channelApi = channelApi; - _context = context; - _feedback = feedback; - _guildApi = guildApi; - _guildData = guildData; - _userApi = userApi; - _utility = utility; - } - - /// - /// A slash command that bans a Discord user with the specified reason. - /// - /// The user to ban. - /// The duration for this ban. The user will be automatically unbanned after this duration. - /// - /// The reason for this ban. Must be encoded with when passed to - /// . - /// - /// - /// A feedback sending result which may or may not have succeeded. A successful result does not mean that the user - /// was banned and vice versa. - /// - /// - [Command("ban", "бан")] - [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ManageMessages)] - [RequireBotDiscordPermissions(DiscordPermission.BanMembers)] - [Description("Ban user")] - [UsedImplicitly] - public async Task ExecuteBanAsync( - [Description("User to ban")] IUser target, - [Description("Ban reason")] [MaxLength(256)] - string reason, - [Description("Ban duration (e.g. 1h30m)")] - string? duration = null) - { - if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - // The bot's avatar is used when sending error messages - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); - if (!guildResult.IsDefined(out var guild)) - { - return ResultExtensions.FromError(guildResult); - } - - var data = await _guildData.GetData(guild.ID, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - if (duration is null) - { - return await BanUserAsync(executor, target, reason, null, guild, data, channelId, bot, - CancellationToken); - } - - var parseResult = TimeSpanParser.TryParse(duration); - if (!parseResult.IsDefined(out var timeSpan)) - { - var failedEmbed = new EmbedBuilder() - .WithSmallTitle(Messages.InvalidTimeSpan, bot) - .WithDescription(Messages.TimeSpanExample) - .WithColour(ColorsList.Red) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: CancellationToken); - } - - return await BanUserAsync(executor, target, reason, timeSpan, guild, data, channelId, bot, CancellationToken); - } - - private async Task BanUserAsync( - IUser executor, IUser target, string reason, TimeSpan? duration, IGuild guild, GuildData data, - Snowflake channelId, - IUser bot, CancellationToken ct = default) - { - var existingBanResult = await _guildApi.GetGuildBanAsync(guild.ID, target.ID, ct); - if (existingBanResult.IsDefined()) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserAlreadyBanned, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var interactionResult - = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Ban", ct); - if (!interactionResult.IsSuccess) - { - return ResultExtensions.FromError(interactionResult); - } - - if (interactionResult.Entity is not null) - { - var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: ct); - } - - var builder = - new StringBuilder().AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason)); - if (duration is not null) - { - builder.AppendBulletPoint( - 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)) - { - var dmEmbed = new EmbedBuilder().WithGuildTitle(guild) - .WithTitle(Messages.YouWereBanned) - .WithDescription(description) - .WithActionFooter(executor) - .WithCurrentTimestamp() - .WithColour(ColorsList.Red) - .Build(); - - await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct); - } - - var memberData = data.GetOrCreateMemberData(target.ID); - memberData.BannedUntil - = duration is not null ? DateTimeOffset.UtcNow.Add(duration.Value) : DateTimeOffset.MaxValue; - - var banResult = await _guildApi.CreateGuildBanAsync( - guild.ID, target.ID, reason: $"({executor.GetTag()}) {reason}".EncodeHeader(), - ct: ct); - if (!banResult.IsSuccess) - { - memberData.BannedUntil = null; - return ResultExtensions.FromError(banResult); - } - - memberData.Roles.Clear(); - - var embed = new EmbedBuilder().WithSmallTitle( - title, target) - .WithColour(ColorsList.Green).Build(); - - _utility.LogAction( - data.Settings, channelId, executor, title, description, target, ColorsList.Red, ct: ct); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - /// - /// A slash command that unbans a Discord user with the specified reason. - /// - /// The user to unban. - /// - /// The reason for this unban. Must be encoded with when passed to - /// . - /// - /// - /// A feedback sending result which may or may not have succeeded. A successful result does not mean that the user - /// was unbanned and vice versa. - /// - /// - /// - [Command("unban")] - [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ManageMessages)] - [RequireBotDiscordPermissions(DiscordPermission.BanMembers)] - [Description("Unban user")] - [UsedImplicitly] - public async Task ExecuteUnban( - [Description("User to unban")] IUser target, - [Description("Unban reason")] [MaxLength(256)] - string reason) - { - if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - // The bot's avatar is used when sending error messages - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - // Needed to get the tag and avatar - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await UnbanUserAsync(executor, target, reason, guildId, data, channelId, bot, CancellationToken); - } - - private async Task UnbanUserAsync( - IUser executor, IUser target, string reason, Snowflake guildId, GuildData data, Snowflake channelId, - IUser bot, CancellationToken ct = default) - { - var existingBanResult = await _guildApi.GetGuildBanAsync(guildId, target.ID, ct); - if (!existingBanResult.IsDefined()) - { - var errorEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserNotBanned, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: ct); - } - - var unbanResult = await _guildApi.RemoveGuildBanAsync( - guildId, target.ID, $"({executor.GetTag()}) {reason}".EncodeHeader(), - ct); - if (!unbanResult.IsSuccess) - { - return ResultExtensions.FromError(unbanResult); - } - - data.GetOrCreateMemberData(target.ID).BannedUntil = null; - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.UserUnbanned, target.GetTag()), target) - .WithColour(ColorsList.Green).Build(); - - var title = string.Format(Messages.UserUnbanned, target.GetTag()); - var description = - new StringBuilder().AppendBulletPoint(string.Format(Messages.DescriptionActionReason, reason)); - - _utility.LogAction( - data.Settings, channelId, executor, title, description.ToString(), target, ColorsList.Green, ct: ct); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs b/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs deleted file mode 100644 index 7f29581..0000000 --- a/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System.ComponentModel; -using System.Text; -using JetBrains.Annotations; -using Remora.Commands.Attributes; -using Remora.Commands.Groups; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Conditions; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Commands; - -/// -/// Handles the command to clear messages in a channel: /clear. -/// -[UsedImplicitly] -public sealed class ClearCommandGroup : CommandGroup -{ - private readonly IDiscordRestChannelAPI _channelApi; - private readonly ICommandContext _context; - private readonly IFeedbackService _feedback; - private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; - private readonly Utility _utility; - - public ClearCommandGroup( - IDiscordRestChannelAPI channelApi, ICommandContext context, GuildDataService guildData, - IFeedbackService feedback, IDiscordRestUserAPI userApi, Utility utility) - { - _channelApi = channelApi; - _context = context; - _guildData = guildData; - _feedback = feedback; - _userApi = userApi; - _utility = utility; - } - - /// - /// A slash command that clears messages in the channel it was executed, optionally filtering by message author. - /// - /// The amount of messages to clear. - /// The user whose messages will be cleared. - /// - /// A feedback sending result which may or may not have succeeded. A successful result does not mean that any messages - /// were cleared and vice versa. - /// - [Command("clear", "очистить")] - [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ManageMessages)] - [RequireBotDiscordPermissions(DiscordPermission.ManageMessages)] - [Description("Remove multiple messages")] - [UsedImplicitly] - public async Task ExecuteClear( - [Description("Number of messages to remove (2-100)")] [MinValue(2)] [MaxValue(100)] - int amount, - [Description("Ignore messages except from the specified author")] - IUser? author = null) - { - if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - // The bot's avatar is used when sending messages - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var messagesResult = await _channelApi.GetChannelMessagesAsync( - channelId, limit: amount + 1, ct: CancellationToken); - if (!messagesResult.IsDefined(out var messages)) - { - return ResultExtensions.FromError(messagesResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await ClearMessagesAsync(executor, author, data, channelId, messages, bot, CancellationToken); - } - - private async Task ClearMessagesAsync( - IUser executor, IUser? author, GuildData data, Snowflake channelId, IReadOnlyList messages, IUser bot, - CancellationToken ct = default) - { - var idList = new List(messages.Count); - - var logEntries = new List { new() }; - var currentLogEntry = 0; - for (var i = messages.Count - 1; i >= 1; i--) // '>= 1' to skip last message ('Octobot is thinking...') - { - var message = messages[i]; - if (author is not null && message.Author.ID != author.ID) - { - continue; - } - - idList.Add(message.ID); - - var entry = logEntries[currentLogEntry]; - var str = $"{string.Format(Messages.MessageFrom, Mention.User(message.Author))}\n{message.Content.InBlockCode()}"; - if (entry.Builder.Length + str.Length > EmbedConstants.MaxDescriptionLength) - { - logEntries.Add(entry = new ClearedMessageEntry()); - currentLogEntry++; - } - - entry.Builder.Append(str); - entry.DeletedCount++; - } - - if (idList.Count == 0) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.NoMessagesToClear, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var title = author is not null - ? string.Format(Messages.MessagesClearedFiltered, idList.Count.ToString(), author.GetTag()) - : string.Format(Messages.MessagesCleared, idList.Count.ToString()); - - var deleteResult = await _channelApi.BulkDeleteMessagesAsync( - channelId, idList, executor.GetTag().EncodeHeader(), ct); - if (!deleteResult.IsSuccess) - { - return ResultExtensions.FromError(deleteResult); - } - - foreach (var log in logEntries) - { - _utility.LogAction( - data.Settings, channelId, executor, author is not null - ? string.Format(Messages.MessagesClearedFiltered, log.DeletedCount.ToString(), author.GetTag()) - : string.Format(Messages.MessagesCleared, log.DeletedCount.ToString()), - log.Builder.ToString(), bot, ColorsList.Red, false, ct); - } - - var embed = new EmbedBuilder().WithSmallTitle(title, bot) - .WithColour(ColorsList.Green).Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - private sealed class ClearedMessageEntry - { - public StringBuilder Builder { get; } = new(); - public int DeletedCount { get; set; } - } -} diff --git a/TeamOctolings.Octobot/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/TeamOctolings.Octobot/Commands/Events/ErrorLoggingPostExecutionEvent.cs deleted file mode 100644 index ff7339f..0000000 --- a/TeamOctolings.Octobot/Commands/Events/ErrorLoggingPostExecutionEvent.cs +++ /dev/null @@ -1,88 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.API.Objects; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Messages; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Commands.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Results; -using TeamOctolings.Octobot.Extensions; - -namespace TeamOctolings.Octobot.Commands.Events; - -/// -/// Handles error logging for slash command groups. -/// -[UsedImplicitly] -public sealed class ErrorLoggingPostExecutionEvent : IPostExecutionEvent -{ - private readonly IFeedbackService _feedback; - private readonly ILogger _logger; - private readonly IDiscordRestUserAPI _userApi; - - public ErrorLoggingPostExecutionEvent(ILogger logger, IFeedbackService feedback, - IDiscordRestUserAPI userApi) - { - _logger = logger; - _feedback = feedback; - _userApi = userApi; - } - - /// - /// Logs a warning using the injected if the has not - /// succeeded. - /// - /// The context of the slash command. - /// The result whose success is checked. - /// The cancellation token for this operation. Unused. - /// A result which has succeeded. - public async Task AfterExecutionAsync( - ICommandContext context, IResult commandResult, CancellationToken ct = default) - { - _logger.LogResult(commandResult, $"Error in slash command execution for /{context.Command.Command.Node.Key}."); - - var result = commandResult; - while (result.Inner is not null) - { - result = result.Inner; - } - - if (result.IsSuccess) - { - return Result.Success; - } - - var botResult = await _userApi.GetCurrentUserAsync(ct); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var embed = new EmbedBuilder().WithSmallTitle(Messages.CommandExecutionFailed, bot) - .WithDescription(Markdown.InlineCode(result.Error.Message)) - .WithFooter(Messages.ContactDevelopers) - .WithColour(ColorsList.Red) - .Build(); - - var issuesButton = new ButtonComponent( - ButtonComponentStyle.Link, - BuildInfo.IsDirty - ? Messages.ButtonDirty - : Messages.ButtonReportIssue, - new PartialEmoji(Name: "\u26a0\ufe0f"), // 'WARNING SIGN' (U+26A0) - URL: BuildInfo.IssuesUrl, - IsDisabled: BuildInfo.IsDirty - ); - - return ResultExtensions.FromError(await _feedback.SendContextualEmbedResultAsync(embed, - new FeedbackMessageOptions(MessageComponents: new[] - { - new ActionRowComponent([issuesButton]) - }), ct) - ); - } -} diff --git a/TeamOctolings.Octobot/Commands/Events/LoggingPreparationErrorEvent.cs b/TeamOctolings.Octobot/Commands/Events/LoggingPreparationErrorEvent.cs deleted file mode 100644 index 9e69a7f..0000000 --- a/TeamOctolings.Octobot/Commands/Events/LoggingPreparationErrorEvent.cs +++ /dev/null @@ -1,38 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Services; -using Remora.Results; -using TeamOctolings.Octobot.Extensions; - -namespace TeamOctolings.Octobot.Commands.Events; - -/// -/// Handles error logging for slash commands that couldn't be successfully prepared. -/// -[UsedImplicitly] -public sealed class LoggingPreparationErrorEvent : IPreparationErrorEvent -{ - private readonly ILogger _logger; - - public LoggingPreparationErrorEvent(ILogger logger) - { - _logger = logger; - } - - /// - /// Logs a warning using the injected if the has not - /// succeeded. - /// - /// The context of the slash command. Unused. - /// The result whose success is checked. - /// The cancellation token for this operation. Unused. - /// A result which has succeeded. - public Task PreparationFailed( - IOperationContext context, IResult preparationResult, CancellationToken ct = default) - { - _logger.LogResult(preparationResult, "Error in slash command preparation."); - - return Task.FromResult(Result.Success); - } -} diff --git a/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs b/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs deleted file mode 100644 index f07b210..0000000 --- a/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs +++ /dev/null @@ -1,329 +0,0 @@ -using System.ComponentModel; -using System.Drawing; -using System.Text; -using JetBrains.Annotations; -using Remora.Commands.Attributes; -using Remora.Commands.Groups; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Commands; - -/// -/// Handles info commands: /userinfo, /guildinfo. -/// -[UsedImplicitly] -public sealed class InfoCommandGroup : CommandGroup -{ - private readonly ICommandContext _context; - private readonly IFeedbackService _feedback; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; - - public InfoCommandGroup( - ICommandContext context, IFeedbackService feedback, - GuildDataService guildData, IDiscordRestGuildAPI guildApi, - IDiscordRestUserAPI userApi) - { - _context = context; - _guildData = guildData; - _feedback = feedback; - _guildApi = guildApi; - _userApi = userApi; - } - - /// - /// A slash command that shows information about user. - /// - /// - /// Information in the output: - /// - /// Display name - /// Discord user since - /// Guild nickname - /// Guild member since - /// Nitro booster since - /// Guild roles - /// Active mute information - /// Active ban information - /// Is on guild status - /// - /// - /// The user to show info about. - /// - /// A feedback sending result which may or may not have succeeded. - /// - [Command("userinfo")] - [DiscordDefaultDMPermission(false)] - [Description("Shows info about user")] - [UsedImplicitly] - public async Task ExecuteUserInfoAsync( - [Description("User to show info about")] - IUser? target = null) - { - if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await ShowUserInfoAsync(target ?? executor, bot, data, guildId, CancellationToken); - } - - private async Task ShowUserInfoAsync( - IUser target, IUser bot, GuildData data, Snowflake guildId, CancellationToken ct = default) - { - var builder = new StringBuilder().AppendLine($"### <@{target.ID}>"); - - if (target.GlobalName.IsDefined(out var globalName)) - { - builder.AppendBulletPointLine(Messages.UserInfoDisplayName) - .AppendLine(Markdown.InlineCode(globalName)); - } - - builder.AppendBulletPointLine(Messages.UserInfoDiscordUserSince) - .AppendLine(Markdown.Timestamp(target.ID.Timestamp)); - - var memberData = data.GetOrCreateMemberData(target.ID); - - var embedColor = ColorsList.Cyan; - - var guildMemberResult = await _guildApi.GetGuildMemberAsync(guildId, target.ID, ct); - DateTimeOffset? communicationDisabledUntil = null; - if (guildMemberResult.IsDefined(out var guildMember)) - { - communicationDisabledUntil = guildMember.CommunicationDisabledUntil.OrDefault(null); - - embedColor = AppendGuildInformation(embedColor, guildMember, builder); - } - - var wasMuted = (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) || - communicationDisabledUntil is not null; - var wasBanned = memberData.BannedUntil is not null; - var wasKicked = memberData.Kicked; - - if (wasMuted || wasBanned || wasKicked) - { - builder.Append("### ") - .AppendLine(Markdown.Bold(Messages.UserInfoPunishments)); - - embedColor = AppendPunishmentsInformation(wasMuted, wasKicked, wasBanned, memberData, - builder, embedColor, communicationDisabledUntil); - } - - if (!guildMemberResult.IsSuccess && !wasBanned) - { - builder.Append("### ") - .AppendLine(Markdown.Bold(Messages.UserInfoNotOnGuild)); - - embedColor = ColorsList.Default; - } - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.InformationAbout, target.GetTag()), bot) - .WithDescription(builder.ToString()) - .WithColour(embedColor) - .WithLargeUserAvatar(target) - .WithFooter($"ID: {target.ID.ToString()}") - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - private static Color AppendPunishmentsInformation(bool wasMuted, bool wasKicked, bool wasBanned, - MemberData memberData, StringBuilder builder, Color embedColor, DateTimeOffset? communicationDisabledUntil) - { - if (wasMuted) - { - AppendMuteInformation(memberData, communicationDisabledUntil, builder); - embedColor = ColorsList.Red; - } - - if (wasKicked) - { - builder.AppendBulletPointLine(Messages.UserInfoKicked); - } - - if (wasBanned) - { - AppendBanInformation(memberData, builder); - embedColor = ColorsList.Black; - } - - return embedColor; - } - - private static Color AppendGuildInformation(Color color, IGuildMember guildMember, StringBuilder builder) - { - if (guildMember.Nickname.IsDefined(out var nickname)) - { - builder.AppendBulletPointLine(Messages.UserInfoGuildNickname) - .AppendLine(Markdown.InlineCode(nickname)); - } - - builder.AppendBulletPointLine(Messages.UserInfoGuildMemberSince) - .AppendLine(Markdown.Timestamp(guildMember.JoinedAt)); - - if (guildMember.PremiumSince.IsDefined(out var premiumSince)) - { - builder.AppendBulletPointLine(Messages.UserInfoGuildMemberPremiumSince) - .AppendLine(Markdown.Timestamp(premiumSince.Value)); - color = ColorsList.Magenta; - } - - if (guildMember.Roles.Count > 0) - { - builder.AppendBulletPointLine(Messages.UserInfoGuildRoles); - for (var i = 0; i < guildMember.Roles.Count - 1; i++) - { - builder.Append($"<@&{guildMember.Roles[i]}>, "); - } - - builder.AppendLine($"<@&{guildMember.Roles[^1]}>"); - } - - return color; - } - - private static void AppendBanInformation(MemberData memberData, StringBuilder builder) - { - if (memberData.BannedUntil < DateTimeOffset.MaxValue) - { - builder.AppendBulletPointLine(Messages.UserInfoBanned) - .AppendSubBulletPointLine(string.Format( - Messages.DescriptionActionExpiresAt, Markdown.Timestamp(memberData.BannedUntil.Value))); - return; - } - - builder.AppendBulletPointLine(Messages.UserInfoBannedPermanently); - } - - private static void AppendMuteInformation( - MemberData memberData, DateTimeOffset? communicationDisabledUntil, StringBuilder builder) - { - builder.AppendBulletPointLine(Messages.UserInfoMuted); - if (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) - { - builder.AppendSubBulletPointLine(Messages.UserInfoMutedByMuteRole) - .AppendSubBulletPointLine(string.Format( - Messages.DescriptionActionExpiresAt, Markdown.Timestamp(memberData.MutedUntil.Value))); - } - - if (communicationDisabledUntil is not null) - { - builder.AppendSubBulletPointLine(Messages.UserInfoMutedByTimeout) - .AppendSubBulletPointLine(string.Format( - Messages.DescriptionActionExpiresAt, Markdown.Timestamp(communicationDisabledUntil.Value))); - } - } - - /// - /// A slash command that shows guild information. - /// - /// - /// Information in the output: - /// - /// Guild description - /// Creation date - /// Guild's language - /// Guild's owner - /// Boost level - /// Boost count - /// - /// - /// - /// A feedback sending result which may or may not have succeeded. - /// - [Command("guildinfo")] - [DiscordDefaultDMPermission(false)] - [Description("Shows info about current guild")] - [UsedImplicitly] - public async Task ExecuteGuildInfoAsync() - { - if (!_context.TryGetContextIDs(out var guildId, out _, out _)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); - if (!guildResult.IsDefined(out var guild)) - { - return ResultExtensions.FromError(guildResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await ShowGuildInfoAsync(bot, guild, CancellationToken); - } - - private Task ShowGuildInfoAsync(IUser bot, IGuild guild, CancellationToken ct = default) - { - var description = new StringBuilder().AppendLine($"## {guild.Name}"); - - if (guild.Description is not null) - { - description.AppendBulletPointLine(Messages.GuildInfoDescription) - .AppendLine(Markdown.InlineCode(guild.Description)); - } - - description.AppendBulletPointLine(Messages.GuildInfoCreatedAt) - .AppendLine(Markdown.Timestamp(guild.ID.Timestamp)) - .AppendBulletPointLine(Messages.GuildInfoOwner) - .AppendLine(Mention.User(guild.OwnerID)); - - var embedColor = ColorsList.Cyan; - - if (guild.PremiumTier > PremiumTier.None) - { - description.Append("### ").AppendLine(Messages.GuildInfoServerBoost) - .AppendBulletPoint(Messages.GuildInfoBoostTier) - .Append(": ").AppendLine(Markdown.InlineCode(guild.PremiumTier.ToString())) - .AppendBulletPoint(Messages.GuildInfoBoostCount) - .Append(": ").AppendLine(Markdown.InlineCode(guild.PremiumSubscriptionCount.ToString())); - embedColor = ColorsList.Magenta; - } - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.InformationAbout, guild.Name), bot) - .WithDescription(description.ToString()) - .WithColour(embedColor) - .WithLargeGuildIcon(guild) - .WithGuildBanner(guild) - .WithFooter($"ID: {guild.ID.ToString()}") - .Build(); - - return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Commands/KickCommandGroup.cs b/TeamOctolings.Octobot/Commands/KickCommandGroup.cs deleted file mode 100644 index 3011375..0000000 --- a/TeamOctolings.Octobot/Commands/KickCommandGroup.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; -using Remora.Commands.Attributes; -using Remora.Commands.Groups; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Conditions; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Commands; - -/// -/// Handles the command to kick members of a guild: /kick. -/// -[UsedImplicitly] -public sealed class KickCommandGroup : CommandGroup -{ - private readonly AccessControlService _access; - private readonly IDiscordRestChannelAPI _channelApi; - private readonly ICommandContext _context; - private readonly IFeedbackService _feedback; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; - private readonly Utility _utility; - - public KickCommandGroup(AccessControlService access, IDiscordRestChannelAPI channelApi, ICommandContext context, - IFeedbackService feedback, IDiscordRestGuildAPI guildApi, GuildDataService guildData, - IDiscordRestUserAPI userApi, Utility utility) - { - _access = access; - _channelApi = channelApi; - _context = context; - _feedback = feedback; - _guildApi = guildApi; - _guildData = guildData; - _userApi = userApi; - _utility = utility; - } - - /// - /// A slash command that kicks a Discord member with the specified reason. - /// - /// The member to kick. - /// - /// The reason for this kick. Must be encoded with when passed to - /// . - /// - /// - /// A feedback sending result which may or may not have succeeded. A successful result does not mean that the member - /// was kicked and vice versa. - /// - [Command("kick", "кик")] - [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ManageMessages)] - [RequireBotDiscordPermissions(DiscordPermission.KickMembers)] - [Description("Kick member")] - [UsedImplicitly] - public async Task ExecuteKick( - [Description("Member to kick")] IUser target, - [Description("Kick reason")] [MaxLength(256)] - string reason) - { - if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - // The bot's avatar is used when sending error messages - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); - if (!guildResult.IsDefined(out var guild)) - { - return ResultExtensions.FromError(guildResult); - } - - 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) - { - var embed = new EmbedBuilder().WithSmallTitle(Messages.UserNotFoundShort, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: CancellationToken); - } - - return await KickUserAsync(executor, target, reason, guild, channelId, data, bot, CancellationToken); - } - - private async Task KickUserAsync( - IUser executor, IUser target, string reason, IGuild guild, Snowflake channelId, GuildData data, IUser bot, - CancellationToken ct = default) - { - var interactionResult - = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Kick", ct); - if (!interactionResult.IsSuccess) - { - return ResultExtensions.FromError(interactionResult); - } - - if (interactionResult.Entity is not null) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var dmChannelResult = await _userApi.CreateDMAsync(target.ID, ct); - if (dmChannelResult.IsDefined(out var dmChannel)) - { - var dmEmbed = new EmbedBuilder().WithGuildTitle(guild) - .WithTitle(Messages.YouWereKicked) - .WithDescription( - MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason))) - .WithActionFooter(executor) - .WithCurrentTimestamp() - .WithColour(ColorsList.Red) - .Build(); - - await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct); - } - - var memberData = data.GetOrCreateMemberData(target.ID); - memberData.Kicked = true; - - var kickResult = await _guildApi.RemoveGuildMemberAsync( - guild.ID, target.ID, $"({executor.GetTag()}) {reason}".EncodeHeader(), - ct); - if (!kickResult.IsSuccess) - { - memberData.Kicked = false; - return ResultExtensions.FromError(kickResult); - } - - memberData.Roles.Clear(); - - var title = string.Format(Messages.UserKicked, target.GetTag()); - var description = MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason)); - - _utility.LogAction( - data.Settings, channelId, executor, title, description, target, ColorsList.Red, ct: ct); - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.UserKicked, target.GetTag()), target) - .WithColour(ColorsList.Green).Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs deleted file mode 100644 index 5dce0b6..0000000 --- a/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs +++ /dev/null @@ -1,388 +0,0 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Text; -using JetBrains.Annotations; -using Remora.Commands.Attributes; -using Remora.Commands.Groups; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Conditions; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Parsers; -using TeamOctolings.Octobot.Services; -using TeamOctolings.Octobot.Services.Update; - -namespace TeamOctolings.Octobot.Commands; - -/// -/// Handles commands related to mute management: /mute and /unmute. -/// -[UsedImplicitly] -public sealed class MuteCommandGroup : CommandGroup -{ - private readonly AccessControlService _access; - private readonly ICommandContext _context; - private readonly IFeedbackService _feedback; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; - private readonly Utility _utility; - - public MuteCommandGroup(AccessControlService access, ICommandContext context, IFeedbackService feedback, - IDiscordRestGuildAPI guildApi, GuildDataService guildData, IDiscordRestUserAPI userApi, Utility utility) - { - _access = access; - _context = context; - _feedback = feedback; - _guildApi = guildApi; - _guildData = guildData; - _userApi = userApi; - _utility = utility; - } - - /// - /// A slash command that mutes a Discord member with the specified reason. - /// - /// The member to mute. - /// The duration for this mute. The member will be automatically unmuted after this duration. - /// - /// The reason for this mute. Must be encoded with when passed to - /// . - /// - /// - /// A feedback sending result which may or may not have succeeded. A successful result does not mean that the member - /// was muted and vice versa. - /// - /// - [Command("mute", "мут")] - [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ManageMessages)] - [RequireBotDiscordPermissions(DiscordPermission.ModerateMembers)] - [Description("Mute member")] - [UsedImplicitly] - public async Task ExecuteMute( - [Description("Member to mute")] IUser target, - [Description("Mute reason")] [MaxLength(256)] - string reason, - [Description("Mute duration (e.g. 1h30m)")] [Option("duration")] - string stringDuration) - { - if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - // The bot's avatar is used when sending error messages - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - 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) - { - var embed = new EmbedBuilder().WithSmallTitle(Messages.UserNotFoundShort, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: CancellationToken); - } - - var parseResult = TimeSpanParser.TryParse(stringDuration); - if (!parseResult.IsDefined(out var duration)) - { - var failedEmbed = new EmbedBuilder() - .WithSmallTitle(Messages.InvalidTimeSpan, bot) - .WithDescription(Messages.TimeSpanExample) - .WithColour(ColorsList.Red) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: CancellationToken); - } - - return await MuteUserAsync(executor, target, reason, duration, guildId, data, channelId, bot, - CancellationToken); - } - - private async Task MuteUserAsync( - IUser executor, IUser target, string reason, TimeSpan duration, Snowflake guildId, GuildData data, - Snowflake channelId, IUser bot, CancellationToken ct = default) - { - var interactionResult - = await _access.CheckInteractionsAsync( - guildId, executor.ID, target.ID, "Mute", ct); - if (!interactionResult.IsSuccess) - { - return ResultExtensions.FromError(interactionResult); - } - - if (interactionResult.Entity is not null) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var until = DateTimeOffset.UtcNow.Add(duration); // >:) - - var muteMethodResult = - await SelectMuteMethodAsync(executor, target, reason, duration, guildId, data, bot, until, ct); - if (!muteMethodResult.IsSuccess) - { - return ResultExtensions.FromError(muteMethodResult); - } - - var title = string.Format(Messages.UserMuted, target.GetTag()); - var description = new StringBuilder() - .AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason)) - .AppendBulletPoint(string.Format( - Messages.DescriptionActionExpiresAt, Markdown.Timestamp(until))).ToString(); - - _utility.LogAction( - data.Settings, channelId, executor, title, description, target, ColorsList.Red, ct: ct); - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.UserMuted, target.GetTag()), target) - .WithColour(ColorsList.Green).Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - private async Task SelectMuteMethodAsync( - IUser executor, IUser target, string reason, TimeSpan duration, Snowflake guildId, GuildData data, - IUser bot, DateTimeOffset until, CancellationToken ct = default) - { - var muteRole = GuildSettings.MuteRole.Get(data.Settings); - - if (muteRole.Empty()) - { - var timeoutResult = await TimeoutUserAsync(executor, target, reason, duration, guildId, bot, until, ct); - return timeoutResult; - } - - var muteRoleResult = await RoleMuteUserAsync(executor, target, reason, guildId, data, until, muteRole, ct); - return muteRoleResult; - } - - private async Task RoleMuteUserAsync( - IUser executor, IUser target, string reason, Snowflake guildId, GuildData data, - DateTimeOffset until, Snowflake muteRole, CancellationToken ct = default) - { - var assignRoles = new List { muteRole }; - var memberData = data.GetOrCreateMemberData(target.ID); - if (!GuildSettings.RemoveRolesOnMute.Get(data.Settings)) - { - assignRoles.AddRange(memberData.Roles.ConvertAll(r => r.ToSnowflake())); - } - - var muteResult = await _guildApi.ModifyGuildMemberAsync( - guildId, target.ID, roles: assignRoles, - reason: $"({executor.GetTag()}) {reason}".EncodeHeader(), ct: ct); - if (muteResult.IsSuccess) - { - memberData.MutedUntil = until; - } - - return muteResult; - } - - private async Task TimeoutUserAsync( - IUser executor, IUser target, string reason, TimeSpan duration, Snowflake guildId, - IUser bot, DateTimeOffset until, CancellationToken ct = default) - { - if (duration.TotalDays >= 28) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.BotCannotMuteTarget, bot) - .WithDescription(Messages.DurationRequiredForTimeOuts) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var muteResult = await _guildApi.ModifyGuildMemberAsync( - guildId, target.ID, reason: $"({executor.GetTag()}) {reason}".EncodeHeader(), - communicationDisabledUntil: until, ct: ct); - return muteResult; - } - - /// - /// A slash command that unmutes a Discord member with the specified reason. - /// - /// The member to unmute. - /// - /// The reason for this unmute. Must be encoded with when passed to - /// . - /// - /// - /// A feedback sending result which may or may not have succeeded. A successful result does not mean that the member - /// was unmuted and vice versa. - /// - /// - /// - [Command("unmute", "размут")] - [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ManageMessages)] - [RequireBotDiscordPermissions(DiscordPermission.ModerateMembers)] - [Description("Unmute member")] - [UsedImplicitly] - public async Task ExecuteUnmute( - [Description("Member to unmute")] IUser target, - [Description("Unmute reason")] [MaxLength(256)] - string reason) - { - if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - // The bot's avatar is used when sending error messages - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - // Needed to get the tag and avatar - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - 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) - { - var embed = new EmbedBuilder().WithSmallTitle(Messages.UserNotFoundShort, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: CancellationToken); - } - - return await RemoveMuteAsync(executor, target, reason, guildId, data, channelId, bot, CancellationToken); - } - - private async Task RemoveMuteAsync( - IUser executor, IUser target, string reason, Snowflake guildId, GuildData data, Snowflake channelId, - IUser bot, CancellationToken ct = default) - { - var interactionResult - = await _access.CheckInteractionsAsync( - guildId, executor.ID, target.ID, "Unmute", ct); - if (!interactionResult.IsSuccess) - { - return ResultExtensions.FromError(interactionResult); - } - - if (interactionResult.Entity is not null) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var guildMemberResult = await _guildApi.GetGuildMemberAsync(guildId, target.ID, ct); - DateTimeOffset? communicationDisabledUntil = null; - if (guildMemberResult.IsDefined(out var guildMember)) - { - communicationDisabledUntil = guildMember.CommunicationDisabledUntil.OrDefault(null); - } - - var memberData = data.GetOrCreateMemberData(target.ID); - var wasMuted = memberData.MutedUntil is not null || communicationDisabledUntil is not null; - - if (!wasMuted) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserNotMuted, bot) - .WithColour(ColorsList.Red).Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var removeMuteRoleAsync = - await RemoveMuteRoleAsync(executor, target, reason, guildId, memberData, CancellationToken); - if (!removeMuteRoleAsync.IsSuccess) - { - return ResultExtensions.FromError(removeMuteRoleAsync); - } - - var removeTimeoutResult = - await RemoveTimeoutAsync(executor, target, reason, guildId, communicationDisabledUntil, CancellationToken); - if (!removeTimeoutResult.IsSuccess) - { - return ResultExtensions.FromError(removeTimeoutResult); - } - - var title = string.Format(Messages.UserUnmuted, target.GetTag()); - var description = MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason)); - - _utility.LogAction( - data.Settings, channelId, executor, title, description, target, ColorsList.Green, ct: ct); - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.UserUnmuted, target.GetTag()), target) - .WithColour(ColorsList.Green).Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - private async Task RemoveMuteRoleAsync( - IUser executor, IUser target, string reason, Snowflake guildId, MemberData memberData, - CancellationToken ct = default) - { - if (memberData.MutedUntil is null) - { - return Result.Success; - } - - var unmuteResult = await _guildApi.ModifyGuildMemberAsync( - guildId, target.ID, roles: memberData.Roles.ConvertAll(r => r.ToSnowflake()), - reason: $"({executor.GetTag()}) {reason}".EncodeHeader(), ct: ct); - if (unmuteResult.IsSuccess) - { - memberData.MutedUntil = null; - } - - return unmuteResult; - } - - private async Task RemoveTimeoutAsync( - IUser executor, IUser target, string reason, Snowflake guildId, DateTimeOffset? communicationDisabledUntil, - CancellationToken ct = default) - { - if (communicationDisabledUntil is null) - { - return Result.Success; - } - - var unmuteResult = await _guildApi.ModifyGuildMemberAsync( - guildId, target.ID, reason: $"({executor.GetTag()}) {reason}".EncodeHeader(), - communicationDisabledUntil: null, ct: ct); - return unmuteResult; - } -} diff --git a/TeamOctolings.Octobot/Commands/PingCommandGroup.cs b/TeamOctolings.Octobot/Commands/PingCommandGroup.cs deleted file mode 100644 index 01a1ee2..0000000 --- a/TeamOctolings.Octobot/Commands/PingCommandGroup.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System.ComponentModel; -using JetBrains.Annotations; -using Remora.Commands.Attributes; -using Remora.Commands.Groups; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Conditions; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Gateway; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Commands; - -/// -/// Handles the command to get the time taken for the gateway to respond to the last heartbeat: /ping -/// -[UsedImplicitly] -public sealed class PingCommandGroup : CommandGroup -{ - private readonly IDiscordRestChannelAPI _channelApi; - private readonly DiscordGatewayClient _client; - private readonly ICommandContext _context; - private readonly IFeedbackService _feedback; - private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; - - public PingCommandGroup( - IDiscordRestChannelAPI channelApi, ICommandContext context, DiscordGatewayClient client, - GuildDataService guildData, IFeedbackService feedback, IDiscordRestUserAPI userApi) - { - _channelApi = channelApi; - _context = context; - _client = client; - _guildData = guildData; - _feedback = feedback; - _userApi = userApi; - } - - /// - /// A slash command that shows time taken for the gateway to respond to the last heartbeat. - /// - /// - /// A feedback sending result which may or may not have succeeded. - /// - [Command("ping", "пинг")] - [Description("Get bot latency")] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [UsedImplicitly] - 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 botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var cfg = await _guildData.GetSettings(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(cfg); - - return await SendLatencyAsync(channelId, bot, CancellationToken); - } - - private async Task SendLatencyAsync( - Snowflake channelId, IUser bot, CancellationToken ct = default) - { - var latency = _client.Latency.TotalMilliseconds; - if (latency is 0) - { - // No heartbeat has occurred, estimate latency from local time and "Octobot is thinking..." message - var lastMessageResult = await _channelApi.GetChannelMessagesAsync( - channelId, limit: 1, ct: ct); - if (!lastMessageResult.IsDefined(out var lastMessage)) - { - return ResultExtensions.FromError(lastMessageResult); - } - - latency = DateTimeOffset.UtcNow.Subtract(lastMessage.Single().Timestamp).TotalMilliseconds; - } - - var embed = new EmbedBuilder().WithSmallTitle(bot.GetTag(), bot) - .WithTitle($"Generic{Random.Shared.Next(1, 4)}".Localized()) - .WithDescription($"{latency:F0}{Messages.Milliseconds}") - .WithColour(latency < 250 ? ColorsList.Green : latency < 500 ? ColorsList.Yellow : ColorsList.Red) - .WithCurrentTimestamp() - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs b/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs deleted file mode 100644 index 3188d27..0000000 --- a/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs +++ /dev/null @@ -1,382 +0,0 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Text; -using JetBrains.Annotations; -using Remora.Commands.Attributes; -using Remora.Commands.Groups; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Conditions; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Parsers; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Commands; - -/// -/// Handles commands to manage reminders: /remind, /listremind, /delremind -/// -[UsedImplicitly] -public sealed class RemindCommandGroup : CommandGroup -{ - private readonly IInteractionCommandContext _context; - private readonly IFeedbackService _feedback; - private readonly GuildDataService _guildData; - private readonly IDiscordRestInteractionAPI _interactionApi; - private readonly IDiscordRestUserAPI _userApi; - - public RemindCommandGroup( - IInteractionCommandContext context, GuildDataService guildData, IFeedbackService feedback, - IDiscordRestUserAPI userApi, IDiscordRestInteractionAPI interactionApi) - { - _context = context; - _guildData = guildData; - _feedback = feedback; - _userApi = userApi; - _interactionApi = interactionApi; - } - - /// - /// A slash command that lists reminders of the user that called it. - /// - /// A feedback sending result which may or may not have succeeded. - [Command("listremind")] - [Description("List your reminders")] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [UsedImplicitly] - public async Task ExecuteListReminderAsync() - { - if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await ListRemindersAsync(data.GetOrCreateMemberData(executorId), guildId, executor, bot, CancellationToken); - } - - private Task ListRemindersAsync(MemberData data, Snowflake guildId, IUser executor, IUser bot, CancellationToken ct = default) - { - if (data.Reminders.Count == 0) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.NoRemindersFound, bot) - .WithColour(ColorsList.Red) - .Build(); - - return _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var builder = new StringBuilder(); - for (var i = 0; i < data.Reminders.Count; i++) - { - var reminder = data.Reminders[i]; - builder.AppendBulletPointLine(string.Format(Messages.ReminderPosition, Markdown.InlineCode((i + 1).ToString()))) - .AppendSubBulletPointLine(string.Format(Messages.ReminderText, reminder.Text)) - .AppendSubBulletPointLine(string.Format(Messages.ReminderTime, Markdown.Timestamp(reminder.At))) - .AppendSubBulletPointLine(string.Format(Messages.DescriptionActionJumpToMessage, $"https://discord.com/channels/{guildId.Value}/{reminder.ChannelId}/{reminder.MessageId}")); - } - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.ReminderList, executor.GetTag()), executor) - .WithDescription(builder.ToString()) - .WithColour(ColorsList.Cyan) - .Build(); - - return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - /// - /// A slash command that schedules a reminder with the specified text. - /// - /// The period of time which must pass before the reminder will be sent. - /// The text of the reminder. - /// A feedback sending result which may or may not have succeeded. - [Command("remind")] - [Description("Create a reminder")] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [UsedImplicitly] - public async Task ExecuteReminderAsync( - [Description("After what period of time mention the reminder (e.g. 1h30m)")] - [Option("in")] - string timeSpanString, - [Description("Reminder text")] [MaxLength(512)] - string text) - { - if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - var parseResult = TimeSpanParser.TryParse(timeSpanString); - if (!parseResult.IsDefined(out var timeSpan)) - { - var failedEmbed = new EmbedBuilder() - .WithSmallTitle(Messages.InvalidTimeSpan, bot) - .WithDescription(Messages.TimeSpanExample) - .WithColour(ColorsList.Red) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: CancellationToken); - } - - return await AddReminderAsync(timeSpan, text, data, channelId, executor, CancellationToken); - } - - private async Task AddReminderAsync(TimeSpan timeSpan, string text, GuildData data, - Snowflake channelId, IUser executor, CancellationToken ct = default) - { - var memberData = data.GetOrCreateMemberData(executor.ID); - var remindAt = DateTimeOffset.UtcNow.Add(timeSpan); - var responseResult = await _interactionApi.GetOriginalInteractionResponseAsync(_context.Interaction.ApplicationID, _context.Interaction.Token, ct); - if (!responseResult.IsDefined(out var response)) - { - return (Result)responseResult; - } - - memberData.Reminders.Add( - new Reminder - { - At = remindAt, - ChannelId = channelId.Value, - Text = text, - MessageId = response.ID.Value - }); - - var builder = new StringBuilder() - .AppendLine(MarkdownExtensions.Quote(text)) - .AppendBulletPoint(string.Format(Messages.ReminderTime, Markdown.Timestamp(remindAt))); - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.ReminderCreated, executor.GetTag()), executor) - .WithDescription(builder.ToString()) - .WithColour(ColorsList.Green) - .WithFooter(string.Format(Messages.ReminderPosition, memberData.Reminders.Count)) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - public enum Parameters - { - [UsedImplicitly] Time, - [UsedImplicitly] Text - } - - /// - /// A slash command that edits a scheduled reminder using the specified text or time. - /// - /// The list position of the reminder to edit. - /// The reminder's parameter to edit. - /// The new value for the reminder as a text or time. - /// A feedback sending result which may or may not have succeeded. - [Command("editremind")] - [Description("Edit a reminder")] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [UsedImplicitly] - public async Task ExecuteEditReminderAsync( - [Description("Position in list")] [MinValue(1)] - int position, - [Description("Parameter to edit")] Parameters parameter, - [Description("Parameter's new value")] string value) - { - if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - var memberData = data.GetOrCreateMemberData(executor.ID); - - if (parameter is Parameters.Time) - { - return await EditReminderTimeAsync(position - 1, value, memberData, bot, executor, CancellationToken); - } - - return await EditReminderTextAsync(position - 1, value, memberData, bot, executor, CancellationToken); - } - - private async Task EditReminderTimeAsync(int index, string value, MemberData data, - IUser bot, IUser executor, CancellationToken ct = default) - { - if (index >= data.Reminders.Count) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.InvalidReminderPosition, bot) - .WithColour(ColorsList.Red) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var parseResult = TimeSpanParser.TryParse(value); - if (!parseResult.IsDefined(out var timeSpan)) - { - var failedEmbed = new EmbedBuilder() - .WithSmallTitle(Messages.InvalidTimeSpan, bot) - .WithDescription(Messages.TimeSpanExample) - .WithColour(ColorsList.Red) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var oldReminder = data.Reminders[index]; - var remindAt = DateTimeOffset.UtcNow.Add(timeSpan); - - data.Reminders.Add(oldReminder with { At = remindAt }); - data.Reminders.RemoveAt(index); - - var builder = new StringBuilder() - .AppendLine(MarkdownExtensions.Quote(oldReminder.Text)) - .AppendBulletPoint(string.Format(Messages.ReminderTime, Markdown.Timestamp(remindAt))); - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.ReminderEdited, executor.GetTag()), executor) - .WithDescription(builder.ToString()) - .WithColour(ColorsList.Cyan) - .WithFooter(string.Format(Messages.ReminderPosition, data.Reminders.Count)) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - private async Task EditReminderTextAsync(int index, string value, MemberData data, - IUser bot, IUser executor, CancellationToken ct = default) - { - if (index >= data.Reminders.Count) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.InvalidReminderPosition, bot) - .WithColour(ColorsList.Red) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var oldReminder = data.Reminders[index]; - - data.Reminders.Add(oldReminder with { Text = value }); - data.Reminders.RemoveAt(index); - - var builder = new StringBuilder() - .AppendLine(MarkdownExtensions.Quote(value)) - .AppendBulletPoint(string.Format(Messages.ReminderTime, Markdown.Timestamp(oldReminder.At))); - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.ReminderEdited, executor.GetTag()), executor) - .WithDescription(builder.ToString()) - .WithColour(ColorsList.Cyan) - .WithFooter(string.Format(Messages.ReminderPosition, data.Reminders.Count)) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - /// - /// A slash command that deletes a reminder using its list position. - /// - /// The list position of the reminder to delete. - /// A feedback sending result which may or may not have succeeded. - [Command("delremind")] - [Description("Delete one of your reminders")] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [UsedImplicitly] - public async Task ExecuteDeleteReminderAsync( - [Description("Position in list")] [MinValue(1)] - int position) - { - if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await DeleteReminderAsync(data.GetOrCreateMemberData(executorId), position - 1, bot, CancellationToken); - } - - private Task DeleteReminderAsync(MemberData data, int index, IUser bot, - CancellationToken ct = default) - { - if (index >= data.Reminders.Count) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.InvalidReminderPosition, bot) - .WithColour(ColorsList.Red) - .Build(); - - return _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var reminder = data.Reminders[index]; - - var description = new StringBuilder() - .AppendLine(MarkdownExtensions.Quote(reminder.Text)) - .AppendBulletPointLine(string.Format(Messages.ReminderTime, Markdown.Timestamp(reminder.At))); - - data.Reminders.RemoveAt(index); - - var embed = new EmbedBuilder().WithSmallTitle(Messages.ReminderDeleted, bot) - .WithDescription(description.ToString()) - .WithColour(ColorsList.Green) - .Build(); - - return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs deleted file mode 100644 index 15aa42b..0000000 --- a/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs +++ /dev/null @@ -1,330 +0,0 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Text; -using System.Text.Json.Nodes; -using JetBrains.Annotations; -using Remora.Commands.Attributes; -using Remora.Commands.Groups; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Conditions; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Data.Options; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Commands; - -/// -/// Handles the commands to list and modify per-guild settings: /settings and /settings list. -/// -[UsedImplicitly] -public sealed class SettingsCommandGroup : CommandGroup -{ - /// - /// Represents all options as an array of objects implementing . - /// - /// - /// WARNING: If you update this array in any way, you must also update and make sure - /// that the orders match. - /// - private static readonly IGuildOption[] AllOptions = - [ - GuildSettings.Language, - GuildSettings.WelcomeMessage, - GuildSettings.LeaveMessage, - GuildSettings.ReceiveStartupMessages, - GuildSettings.RemoveRolesOnMute, - GuildSettings.ReturnRolesOnRejoin, - GuildSettings.AutoStartEvents, - GuildSettings.RenameHoistedUsers, - GuildSettings.PublicFeedbackChannel, - GuildSettings.PrivateFeedbackChannel, - GuildSettings.WelcomeMessagesChannel, - GuildSettings.EventNotificationChannel, - GuildSettings.DefaultRole, - GuildSettings.MuteRole, - GuildSettings.ModeratorRole, - GuildSettings.EventNotificationRole, - GuildSettings.EventEarlyNotificationOffset - ]; - - private readonly ICommandContext _context; - private readonly IFeedbackService _feedback; - private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; - private readonly Utility _utility; - - public SettingsCommandGroup( - ICommandContext context, GuildDataService guildData, - IFeedbackService feedback, IDiscordRestUserAPI userApi, Utility utility) - { - _context = context; - _guildData = guildData; - _feedback = feedback; - _userApi = userApi; - _utility = utility; - } - - /// - /// A slash command that sends a page from the list of current GuildSettings. - /// - /// The number of the page to send. - /// - /// A feedback sending result which may or may not have succeeded. - /// - [Command("listsettings")] - [DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ManageGuild)] - [Description("Shows settings list for this server")] - [UsedImplicitly] - public async Task ExecuteListSettingsAsync( - [Description("Settings list page")] [MinValue(1)] - int page) - { - if (!_context.TryGetContextIDs(out var guildId, out _, out _)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var cfg = await _guildData.GetSettings(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(cfg); - - return await SendSettingsListAsync(cfg, bot, page, CancellationToken); - } - - private Task SendSettingsListAsync(JsonNode cfg, IUser bot, int page, - CancellationToken ct = default) - { - var description = new StringBuilder(); - var footer = new StringBuilder(); - - const int optionsPerPage = 10; - - var totalPages = (AllOptions.Length + optionsPerPage - 1) / optionsPerPage; - var lastOptionOnPage = Math.Min(optionsPerPage * page, AllOptions.Length); - var firstOptionOnPage = optionsPerPage * page - optionsPerPage; - - if (firstOptionOnPage >= AllOptions.Length) - { - var errorEmbed = new EmbedBuilder().WithSmallTitle(Messages.PageNotFound, bot) - .WithDescription(string.Format(Messages.PagesAllowed, Markdown.Bold(totalPages.ToString()))) - .WithColour(ColorsList.Red) - .Build(); - - return _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: 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.AppendBulletPointLine($"Settings{optionName}".Localized()) - .AppendSubBulletPoint(Markdown.InlineCode(optionName)) - .Append(": ").AppendLine(optionValue); - } - - var embed = new EmbedBuilder().WithSmallTitle(Messages.SettingsListTitle, bot) - .WithDescription(description.ToString()) - .WithColour(ColorsList.Default) - .WithFooter(footer.ToString()) - .Build(); - - return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - /// - /// A slash command that modifies per-guild GuildSettings. - /// - /// The setting to modify. - /// The new value of the setting. - /// A feedback sending result which may or may not have succeeded. - [Command("editsettings")] - [DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ManageGuild)] - [Description("Change settings for this server")] - [UsedImplicitly] - public async Task ExecuteEditSettingsAsync( - [Description("The setting whose value you want to change")] - AllOptionsEnum setting, - [Description("Setting value")] [MaxLength(512)] - string value) - { - if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await EditSettingAsync(AllOptions[(int)setting], value, data, channelId, executor, bot, - CancellationToken); - } - - private async Task EditSettingAsync( - IGuildOption option, string value, GuildData data, Snowflake channelId, IUser executor, IUser bot, - CancellationToken ct = default) - { - var equalsResult = option.ValueEquals(data.Settings, value); - if (!equalsResult.IsSuccess) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.SettingNotChanged, bot) - .WithDescription(equalsResult.Error.Message) - .WithColour(ColorsList.Red) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - if (equalsResult.Entity) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.SettingNotChanged, bot) - .WithDescription(Messages.SettingValueEquals) - .WithColour(ColorsList.Red) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var setResult = option.Set(data.Settings, value); - if (!setResult.IsSuccess) - { - var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.SettingNotChanged, bot) - .WithDescription(setResult.Error.Message) - .WithColour(ColorsList.Red) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); - } - - var builder = new StringBuilder(); - - builder.Append(Markdown.InlineCode(option.Name)) - .Append($" {Messages.SettingIsNow} ") - .Append(option.Display(data.Settings)); - var title = Messages.SettingSuccessfullyChanged; - var description = builder.ToString(); - - _utility.LogAction( - data.Settings, channelId, executor, title, description, bot, ColorsList.Magenta, false, ct); - - var embed = new EmbedBuilder().WithSmallTitle(title, bot) - .WithDescription(description) - .WithColour(ColorsList.Green) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - /// - /// A slash command that resets per-guild GuildSettings. - /// - /// The setting to reset. - /// A feedback sending result which may have succeeded. - [Command("resetsettings")] - [DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)] - [DiscordDefaultDMPermission(false)] - [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ManageGuild)] - [Description("Reset settings for this guild")] - [UsedImplicitly] - public async Task ExecuteResetSettingsAsync( - [Description("Setting to reset")] AllOptionsEnum? setting = null) - { - if (!_context.TryGetContextIDs(out var guildId, out _, out _)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var cfg = await _guildData.GetSettings(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(cfg); - - if (setting is not null) - { - return await ResetSingleSettingAsync(cfg, bot, AllOptions[(int)setting], CancellationToken); - } - - return await ResetAllSettingsAsync(cfg, bot, CancellationToken); - } - - private async Task ResetSingleSettingAsync(JsonNode cfg, IUser bot, - IGuildOption option, CancellationToken ct = default) - { - var resetResult = option.Reset(cfg); - if (!resetResult.IsSuccess) - { - return ResultExtensions.FromError(resetResult); - } - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.SingleSettingReset, option.Name), bot) - .WithColour(ColorsList.Green) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - private async Task ResetAllSettingsAsync(JsonNode cfg, IUser bot, - CancellationToken ct = default) - { - var failedResults = new List(); - foreach (var resetResult in AllOptions.Select(option => option.Reset(cfg))) - { - failedResults.AddIfFailed(resetResult); - } - - if (failedResults.Count is not 0) - { - return failedResults.AggregateErrors(); - } - - var embed = new EmbedBuilder().WithSmallTitle(Messages.AllSettingsReset, bot) - .WithColour(ColorsList.Green) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs b/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs deleted file mode 100644 index 2936392..0000000 --- a/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs +++ /dev/null @@ -1,272 +0,0 @@ -using System.ComponentModel; -using System.Text; -using JetBrains.Annotations; -using Remora.Commands.Attributes; -using Remora.Commands.Groups; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Parsers; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Commands; - -/// -/// Handles tool commands: /random, /timestamp, /8ball. -/// -[UsedImplicitly] -public sealed class ToolsCommandGroup : CommandGroup -{ - private static readonly TimestampStyle[] AllStyles = - [ - TimestampStyle.ShortDate, - TimestampStyle.LongDate, - TimestampStyle.ShortTime, - TimestampStyle.LongTime, - TimestampStyle.ShortDateTime, - TimestampStyle.LongDateTime, - TimestampStyle.RelativeTime - ]; - - private static readonly string[] AnswerTypes = - [ - "Positive", "Questionable", "Neutral", "Negative" - ]; - - private readonly ICommandContext _context; - private readonly IFeedbackService _feedback; - private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; - - public ToolsCommandGroup( - ICommandContext context, IFeedbackService feedback, - GuildDataService guildData, IDiscordRestUserAPI userApi) - { - _context = context; - _guildData = guildData; - _feedback = feedback; - _userApi = userApi; - } - - /// - /// A slash command that generates a random number using maximum and minimum numbers. - /// - /// The first number used for randomization. - /// The second number used for randomization. Default value: 0 - /// - /// A feedback sending result which may or may not have succeeded. - /// - [Command("random")] - [DiscordDefaultDMPermission(false)] - [Description("Generates a random number")] - [UsedImplicitly] - public async Task ExecuteRandomAsync( - [Description("First number")] long first, - [Description("Second number (Default: 0)")] - long? second = null) - { - if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await SendRandomNumberAsync(first, second, executor, CancellationToken); - } - - private Task SendRandomNumberAsync(long first, long? secondNullable, - IUser executor, CancellationToken ct = default) - { - const long secondDefault = 0; - var second = secondNullable ?? secondDefault; - - var min = Math.Min(first, second); - var max = Math.Max(first, second); - - var i = Random.Shared.NextInt64(min, max + 1); - - var description = new StringBuilder().Append("# ").Append(i); - - description.AppendLine().AppendBulletPoint(string.Format( - Messages.RandomMin, Markdown.InlineCode(min.ToString()))); - if (secondNullable is null && first >= secondDefault) - { - description.Append(' ').Append(Messages.Default); - } - - description.AppendLine().AppendBulletPoint(string.Format( - Messages.RandomMax, Markdown.InlineCode(max.ToString()))); - if (secondNullable is null && first < secondDefault) - { - description.Append(' ').Append(Messages.Default); - } - - var embedColor = ColorsList.Blue; - if (secondNullable is not null && min == max) - { - description.AppendLine().Append(Markdown.Italicise(Messages.RandomMinMaxSame)); - embedColor = ColorsList.Red; - } - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.RandomTitle, executor.GetTag()), executor) - .WithDescription(description.ToString()) - .WithColour(embedColor) - .Build(); - - return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - /// - /// A slash command that shows the current timestamp with an optional offset in all styles supported by Discord. - /// - /// The offset for the current timestamp. - /// - /// A feedback sending result which may or may not have succeeded. - /// - [Command("timestamp")] - [DiscordDefaultDMPermission(false)] - [Description("Shows a timestamp in all styles")] - [UsedImplicitly] - public async Task ExecuteTimestampAsync( - [Description("Offset from current time")] [Option("offset")] - string? stringOffset = null) - { - if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - if (stringOffset is null) - { - return await SendTimestampAsync(null, executor, CancellationToken); - } - - var parseResult = TimeSpanParser.TryParse(stringOffset); - if (!parseResult.IsDefined(out var offset)) - { - var failedEmbed = new EmbedBuilder() - .WithSmallTitle(Messages.InvalidTimeSpan, bot) - .WithDescription(Messages.TimeSpanExample) - .WithColour(ColorsList.Red) - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: CancellationToken); - } - - return await SendTimestampAsync(offset, executor, CancellationToken); - } - - private Task SendTimestampAsync(TimeSpan? offset, IUser executor, CancellationToken ct = default) - { - var timestamp = DateTimeOffset.UtcNow.Add(offset ?? TimeSpan.Zero).ToUnixTimeSeconds(); - - var description = new StringBuilder().Append("# ").AppendLine(timestamp.ToString()); - - if (offset is not null) - { - description.AppendLine(string.Format( - Messages.TimestampOffset, Markdown.InlineCode(offset.ToString() ?? string.Empty))).AppendLine(); - } - - foreach (var markdownTimestamp in AllStyles.Select(style => Markdown.Timestamp(timestamp, style))) - { - description.AppendBulletPoint(Markdown.InlineCode(markdownTimestamp)) - .Append(" → ").AppendLine(markdownTimestamp); - } - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.TimestampTitle, executor.GetTag()), executor) - .WithDescription(description.ToString()) - .WithColour(ColorsList.Blue) - .Build(); - - return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - /// - /// A slash command that shows a random answer from the Magic 8-Ball. - /// - /// Unused input. - /// - /// The 8-Ball answers were taken from Wikipedia. - /// - /// - /// A feedback sending result which may or may not have succeeded. - /// - [Command("8ball")] - [DiscordDefaultDMPermission(false)] - [Description("Ask the Magic 8-Ball a question")] - [UsedImplicitly] - public async Task ExecuteEightBallAsync( - // let the user think he's actually asking the ball a question - [Description("Question to ask")] string question) - { - if (!_context.TryGetContextIDs(out var guildId, out _, out _)) - { - return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); - } - - var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await AnswerEightBallAsync(bot, CancellationToken); - } - - private Task AnswerEightBallAsync(IUser bot, CancellationToken ct = default) - { - var typeNumber = Random.Shared.Next(0, 4); - var embedColor = typeNumber switch - { - 0 => ColorsList.Blue, - 1 => ColorsList.Green, - 2 => ColorsList.Yellow, - 3 => ColorsList.Red, - _ => throw new ArgumentOutOfRangeException(null, nameof(typeNumber)) - }; - - var answer = $"EightBall{AnswerTypes[typeNumber]}{Random.Shared.Next(1, 6)}".Localized(); - - var embed = new EmbedBuilder().WithSmallTitle(answer, bot) - .WithColour(embedColor) - .Build(); - - return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Data/GuildData.cs b/TeamOctolings.Octobot/Data/GuildData.cs deleted file mode 100644 index f393323..0000000 --- a/TeamOctolings.Octobot/Data/GuildData.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Text.Json.Nodes; -using Remora.Rest.Core; - -namespace TeamOctolings.Octobot.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 sealed class GuildData -{ - public readonly Dictionary MemberData; - public readonly string MemberDataPath; - - public readonly Dictionary ScheduledEvents; - public readonly string ScheduledEventsPath; - public readonly JsonNode Settings; - public readonly string SettingsPath; - - public readonly bool DataLoadFailed; - - public GuildData( - JsonNode settings, string settingsPath, - Dictionary scheduledEvents, string scheduledEventsPath, - Dictionary memberData, string memberDataPath, bool dataLoadFailed) - { - Settings = settings; - SettingsPath = settingsPath; - ScheduledEvents = scheduledEvents; - ScheduledEventsPath = scheduledEventsPath; - MemberData = memberData; - MemberDataPath = memberDataPath; - DataLoadFailed = dataLoadFailed; - } - - public MemberData GetOrCreateMemberData(Snowflake memberId) - { - if (MemberData.TryGetValue(memberId.Value, out var existing)) - { - return existing; - } - - var newData = new MemberData(memberId.Value); - MemberData.Add(memberId.Value, newData); - return newData; - } -} diff --git a/TeamOctolings.Octobot/Data/GuildSettings.cs b/TeamOctolings.Octobot/Data/GuildSettings.cs deleted file mode 100644 index dc59d6f..0000000 --- a/TeamOctolings.Octobot/Data/GuildSettings.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Remora.Discord.API.Abstractions.Objects; -using TeamOctolings.Octobot.Data.Options; -using TeamOctolings.Octobot.Responders; - -namespace TeamOctolings.Octobot.Data; - -/// -/// Contains all per-guild settings that can be set by a member -/// with using the /settings command -/// -public static class GuildSettings -{ - public static readonly LanguageOption Language = new("Language", "en"); - - /// - /// Controls what message should be sent in when a new member joins the guild. - /// - /// - /// - /// No message will be sent if set to "off", "disable" or "disabled". - /// will be sent if set to "default" or "reset". - /// - /// - /// - public static readonly GuildOption WelcomeMessage = new("WelcomeMessage", "default"); - - /// - /// Controls what message should be sent in when a member leaves the guild. - /// - /// - /// - /// No message will be sent if set to "off", "disable" or "disabled". - /// will be sent if set to "default" or "reset". - /// - /// - /// - public static readonly GuildOption LeaveMessage = new("LeaveMessage", "default"); - - /// - /// Controls whether or not the message should be sent - /// in on startup. - /// - /// - public static readonly BoolOption ReceiveStartupMessages = new("ReceiveStartupMessages", false); - - public static readonly BoolOption RemoveRolesOnMute = new("RemoveRolesOnMute", false); - - /// - /// Controls whether or not a guild member's roles are returned if he/she leaves and then joins back. - /// - /// Roles will not be returned if the member left the guild because of /ban or /kick. - public static readonly BoolOption ReturnRolesOnRejoin = new("ReturnRolesOnRejoin", false); - - public static readonly BoolOption AutoStartEvents = new("AutoStartEvents", false); - - /// - /// Controls whether or not users who try to hoist themselves should be renamed. - /// - public static readonly BoolOption RenameHoistedUsers = new("RenameHoistedUsers", false); - - /// - /// Controls what channel should all public messages be sent to. - /// - public static readonly SnowflakeOption PublicFeedbackChannel = new("PublicFeedbackChannel"); - - /// - /// Controls what channel should all private, moderator-only messages be sent to. - /// - public static readonly SnowflakeOption PrivateFeedbackChannel = new("PrivateFeedbackChannel"); - - /// - /// Controls what channel should welcome messages be sent to. - /// - public static readonly SnowflakeOption WelcomeMessagesChannel = new("WelcomeMessagesChannel"); - - 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 ModeratorRole = new("ModeratorRole"); - public static readonly SnowflakeOption EventNotificationRole = new("EventNotificationRole"); - - /// - /// Controls the amount of time before a scheduled event to send a reminder in . - /// - public static readonly TimeSpanOption EventEarlyNotificationOffset = new( - "EventEarlyNotificationOffset", TimeSpan.Zero); -} diff --git a/TeamOctolings.Octobot/Data/MemberData.cs b/TeamOctolings.Octobot/Data/MemberData.cs deleted file mode 100644 index 984d4af..0000000 --- a/TeamOctolings.Octobot/Data/MemberData.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace TeamOctolings.Octobot.Data; - -/// -/// Stores information about a member -/// -public sealed class MemberData -{ - public MemberData(ulong id, List? reminders = null) - { - Id = id; - if (reminders is not null) - { - Reminders = reminders; - } - } - - public ulong Id { get; } - public DateTimeOffset? BannedUntil { get; set; } - public DateTimeOffset? MutedUntil { get; set; } - public bool Kicked { get; set; } - public List Roles { get; set; } = []; - public List Reminders { get; } = []; -} diff --git a/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs b/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs deleted file mode 100644 index 6a4280e..0000000 --- a/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs +++ /dev/null @@ -1,32 +0,0 @@ -using JetBrains.Annotations; -using TeamOctolings.Octobot.Commands; - -namespace TeamOctolings.Octobot.Data.Options; - -/// -/// Represents all options as enums. -/// -/// -/// WARNING: This enum is order-dependent! It's values are used as indexes for -/// . -/// -public enum AllOptionsEnum -{ - [UsedImplicitly] Language, - [UsedImplicitly] WelcomeMessage, - [UsedImplicitly] LeaveMessage, - [UsedImplicitly] ReceiveStartupMessages, - [UsedImplicitly] RemoveRolesOnMute, - [UsedImplicitly] ReturnRolesOnRejoin, - [UsedImplicitly] AutoStartEvents, - [UsedImplicitly] RenameHoistedUsers, - [UsedImplicitly] PublicFeedbackChannel, - [UsedImplicitly] PrivateFeedbackChannel, - [UsedImplicitly] WelcomeMessagesChannel, - [UsedImplicitly] EventNotificationChannel, - [UsedImplicitly] DefaultRole, - [UsedImplicitly] MuteRole, - [UsedImplicitly] ModeratorRole, - [UsedImplicitly] EventNotificationRole, - [UsedImplicitly] EventEarlyNotificationOffset -} diff --git a/TeamOctolings.Octobot/Data/Options/BoolOption.cs b/TeamOctolings.Octobot/Data/Options/BoolOption.cs deleted file mode 100644 index 3b81abb..0000000 --- a/TeamOctolings.Octobot/Data/Options/BoolOption.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Text.Json.Nodes; -using Remora.Results; - -namespace TeamOctolings.Octobot.Data.Options; - -public sealed class BoolOption : GuildOption -{ - public BoolOption(string name, bool defaultValue) : base(name, defaultValue) { } - - public override string Display(JsonNode settings) - { - return Get(settings) ? Messages.Yes : Messages.No; - } - - public override Result ValueEquals(JsonNode settings, string value) - { - if (!TryParseBool(value, out var boolean)) - { - return new ArgumentInvalidError(nameof(value), Messages.InvalidSettingValue); - } - - return Value(settings).Equals(boolean.ToString()); - } - - 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.Success; - } - - private static bool TryParseBool(string from, out bool value) - { - value = false; - switch (from.ToLowerInvariant()) - { - case "true" or "1" or "y" or "yes" or "д" or "да": - value = true; - return true; - case "false" or "0" or "n" or "no" or "н" or "не" or "нет" or "нъет": - value = false; - return true; - default: - return false; - } - } -} diff --git a/TeamOctolings.Octobot/Data/Options/GuildOption.cs b/TeamOctolings.Octobot/Data/Options/GuildOption.cs deleted file mode 100644 index ea9c30e..0000000 --- a/TeamOctolings.Octobot/Data/Options/GuildOption.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Text.Json.Nodes; -using Remora.Discord.Extensions.Formatting; -using Remora.Results; - -namespace TeamOctolings.Octobot.Data.Options; - -/// -/// Represents a per-guild option. -/// -/// The type of the option. -public class GuildOption : IGuildOption - where T : notnull -{ - protected readonly T DefaultValue; - - public GuildOption(string name, T defaultValue) - { - Name = name; - DefaultValue = defaultValue; - } - - public string Name { get; } - - protected virtual string Value(JsonNode settings) - { - return Get(settings).ToString() ?? throw new InvalidOperationException(); - } - - public virtual string Display(JsonNode settings) - { - return Markdown.InlineCode(Value(settings)); - } - - public virtual Result ValueEquals(JsonNode settings, string value) - { - return Value(settings).Equals(value); - } - - /// - /// Sets the value of the option from a to the provided JsonNode. - /// - /// 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) - { - settings[Name] = from; - return Result.Success; - } - - public Result Reset(JsonNode settings) - { - settings[Name] = null; - return Result.Success; - } - - /// - /// Gets the value of the option from the provided . - /// - /// The to get the value from. - /// The value of the option. - public virtual T Get(JsonNode settings) - { - var property = settings[Name]; - return property != null ? property.GetValue() : DefaultValue; - } -} diff --git a/TeamOctolings.Octobot/Data/Options/IGuildOption.cs b/TeamOctolings.Octobot/Data/Options/IGuildOption.cs deleted file mode 100644 index 9920281..0000000 --- a/TeamOctolings.Octobot/Data/Options/IGuildOption.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Nodes; -using Remora.Results; - -namespace TeamOctolings.Octobot.Data.Options; - -public interface IGuildOption -{ - string Name { get; } - string Display(JsonNode settings); - Result ValueEquals(JsonNode settings, string value); - Result Set(JsonNode settings, string from); - Result Reset(JsonNode settings); -} diff --git a/TeamOctolings.Octobot/Data/Options/LanguageOption.cs b/TeamOctolings.Octobot/Data/Options/LanguageOption.cs deleted file mode 100644 index f58e011..0000000 --- a/TeamOctolings.Octobot/Data/Options/LanguageOption.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Globalization; -using System.Text.Json.Nodes; -using Remora.Results; - -namespace TeamOctolings.Octobot.Data.Options; - -/// -public sealed class LanguageOption : GuildOption -{ - private static readonly Dictionary CultureInfoCache = new() - { - { "en", new CultureInfo("en-US") }, - { "ru", new CultureInfo("ru-RU") } - }; - - public LanguageOption(string name, string defaultValue) : base(name, CultureInfoCache[defaultValue]) { } - - protected override string Value(JsonNode settings) - { - return settings[Name]?.GetValue() ?? "en"; - } - - /// - 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) - { - return CultureInfoCache.ContainsKey(from.ToLowerInvariant()) - ? base.Set(settings, from.ToLowerInvariant()) - : new ArgumentInvalidError(nameof(from), Messages.LanguageNotSupported); - } -} diff --git a/TeamOctolings.Octobot/Data/Options/SnowflakeOption.cs b/TeamOctolings.Octobot/Data/Options/SnowflakeOption.cs deleted file mode 100644 index b7405f2..0000000 --- a/TeamOctolings.Octobot/Data/Options/SnowflakeOption.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Text.Json.Nodes; -using System.Text.RegularExpressions; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Extensions; - -namespace TeamOctolings.Octobot.Data.Options; - -public sealed partial class SnowflakeOption : GuildOption -{ - public SnowflakeOption(string name) : base(name, 0UL.ToSnowflake()) { } - - 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) - { - var property = settings[Name]; - return property != null ? property.GetValue().ToSnowflake() : DefaultValue; - } - - 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.Success; - } - - [GeneratedRegex("[^0-9]")] - private static partial Regex NonNumbers(); -} diff --git a/TeamOctolings.Octobot/Data/Options/TimeSpanOption.cs b/TeamOctolings.Octobot/Data/Options/TimeSpanOption.cs deleted file mode 100644 index 7e21343..0000000 --- a/TeamOctolings.Octobot/Data/Options/TimeSpanOption.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Text.Json.Nodes; -using Remora.Results; -using TeamOctolings.Octobot.Parsers; - -namespace TeamOctolings.Octobot.Data.Options; - -public sealed class TimeSpanOption : GuildOption -{ - public TimeSpanOption(string name, TimeSpan defaultValue) : base(name, defaultValue) { } - - public override Result ValueEquals(JsonNode settings, string value) - { - if (!TimeSpanParser.TryParse(value).IsDefined(out var span)) - { - return new ArgumentInvalidError(nameof(value), Messages.InvalidSettingValue); - } - - return Value(settings).Equals(span.ToString()); - } - - public override TimeSpan Get(JsonNode settings) - { - var property = settings[Name]; - return property != null ? TimeSpanParser.TryParse(property.GetValue()).Entity : DefaultValue; - } - - public override Result Set(JsonNode settings, string from) - { - if (!TimeSpanParser.TryParse(from).IsDefined(out var span)) - { - return new ArgumentInvalidError(nameof(from), Messages.InvalidSettingValue); - } - - settings[Name] = span.ToString(); - return Result.Success; - } -} diff --git a/TeamOctolings.Octobot/Data/Reminder.cs b/TeamOctolings.Octobot/Data/Reminder.cs deleted file mode 100644 index 40f29e1..0000000 --- a/TeamOctolings.Octobot/Data/Reminder.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace TeamOctolings.Octobot.Data; - -public sealed record Reminder -{ - public required DateTimeOffset At { get; init; } - public required string Text { get; init; } - public required ulong ChannelId { get; init; } - public required ulong MessageId { get; init; } -} diff --git a/TeamOctolings.Octobot/Data/ScheduledEventData.cs b/TeamOctolings.Octobot/Data/ScheduledEventData.cs deleted file mode 100644 index 7ba6e92..0000000 --- a/TeamOctolings.Octobot/Data/ScheduledEventData.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Text.Json.Serialization; -using Remora.Discord.API.Abstractions.Objects; - -namespace TeamOctolings.Octobot.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 sealed class ScheduledEventData -{ - public ScheduledEventData(ulong id, string name, DateTimeOffset scheduledStartTime, - GuildScheduledEventStatus status) - { - Id = id; - Name = name; - ScheduledStartTime = scheduledStartTime; - Status = status; - } - - [JsonConstructor] - public ScheduledEventData(ulong id, string name, bool earlyNotificationSent, DateTimeOffset scheduledStartTime, - DateTimeOffset? actualStartTime, GuildScheduledEventStatus? status, bool scheduleOnStatusUpdated) - { - Id = id; - Name = name; - EarlyNotificationSent = earlyNotificationSent; - ScheduledStartTime = scheduledStartTime; - ActualStartTime = actualStartTime; - Status = status; - ScheduleOnStatusUpdated = scheduleOnStatusUpdated; - } - - public ulong Id { get; } - public string Name { get; set; } - public bool EarlyNotificationSent { get; set; } - public DateTimeOffset ScheduledStartTime { get; set; } - public DateTimeOffset? ActualStartTime { get; set; } - public GuildScheduledEventStatus? Status { get; set; } - public bool ScheduleOnStatusUpdated { get; set; } = true; -} diff --git a/TeamOctolings.Octobot/Extensions/ChannelApiExtensions.cs b/TeamOctolings.Octobot/Extensions/ChannelApiExtensions.cs deleted file mode 100644 index 82f8889..0000000 --- a/TeamOctolings.Octobot/Extensions/ChannelApiExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using OneOf; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.API.Objects; -using Remora.Rest.Core; -using Remora.Results; - -namespace TeamOctolings.Octobot.Extensions; - -public static class ChannelApiExtensions -{ - public static async Task CreateMessageWithEmbedResultAsync(this IDiscordRestChannelAPI channelApi, - Snowflake channelId, Optional message = default, Optional nonce = default, - Optional isTextToSpeech = default, Optional> embedResult = default, - Optional allowedMentions = default, Optional messageReference = default, - Optional> components = default, - Optional> stickerIds = default, - Optional>> attachments = default, - Optional flags = default, Optional enforceNonce = default, - Optional poll = default, CancellationToken ct = default) - { - if (!embedResult.IsDefined() || !embedResult.Value.IsDefined(out var embed)) - { - return ResultExtensions.FromError(embedResult.Value); - } - - return (Result)await channelApi.CreateMessageAsync(channelId, message, nonce, isTextToSpeech, new[] { embed }, - allowedMentions, messageReference, components, stickerIds, attachments, flags, enforceNonce, poll, ct); - } -} diff --git a/TeamOctolings.Octobot/Extensions/CollectionExtensions.cs b/TeamOctolings.Octobot/Extensions/CollectionExtensions.cs deleted file mode 100644 index 3ea13a8..0000000 --- a/TeamOctolings.Octobot/Extensions/CollectionExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Remora.Results; - -namespace TeamOctolings.Octobot.Extensions; - -public static class CollectionExtensions -{ - public static TResult? MaxOrDefault( - this IEnumerable source, Func selector) - { - var list = source.ToList(); - return list.Count > 0 ? list.Max(selector) : default; - } - - public static void AddIfFailed(this List list, Result result) - { - if (!result.IsSuccess) - { - list.Add(result); - } - } - - /// - /// Return an appropriate result for a list of failed results. The list must only contain failed results. - /// - /// The list of failed results. - /// - /// A successful result if the list is empty, the only Result in the list, or - /// containing all results from the list. - /// - /// - public static Result AggregateErrors(this List list) - { - return list.Count switch - { - 0 => Result.Success, - 1 => list[0], - _ => new AggregateError(list.Cast().ToArray()) - }; - } -} diff --git a/TeamOctolings.Octobot/Extensions/CommandContextExtensions.cs b/TeamOctolings.Octobot/Extensions/CommandContextExtensions.cs deleted file mode 100644 index 16b8b56..0000000 --- a/TeamOctolings.Octobot/Extensions/CommandContextExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Extensions; -using Remora.Rest.Core; - -namespace TeamOctolings.Octobot.Extensions; - -public static class CommandContextExtensions -{ - public static bool TryGetContextIDs( - this ICommandContext context, out Snowflake guildId, - out Snowflake channelId, out Snowflake executorId) - { - channelId = default; - executorId = default; - return context.TryGetGuildID(out guildId) - && context.TryGetChannelID(out channelId) - && context.TryGetUserID(out executorId); - } -} diff --git a/TeamOctolings.Octobot/Extensions/DiffPaneModelExtensions.cs b/TeamOctolings.Octobot/Extensions/DiffPaneModelExtensions.cs deleted file mode 100644 index 3bb707b..0000000 --- a/TeamOctolings.Octobot/Extensions/DiffPaneModelExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Text; -using DiffPlex.DiffBuilder.Model; - -namespace TeamOctolings.Octobot.Extensions; - -public static class DiffPaneModelExtensions -{ - public static string AsMarkdown(this DiffPaneModel model) - { - var builder = new StringBuilder(); - 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.SanitizeForDiffBlock()); - } - } - - return builder.ToString().InBlockCode("diff"); - } -} diff --git a/TeamOctolings.Octobot/Extensions/EmbedBuilderExtensions.cs b/TeamOctolings.Octobot/Extensions/EmbedBuilderExtensions.cs deleted file mode 100644 index dab0265..0000000 --- a/TeamOctolings.Octobot/Extensions/EmbedBuilderExtensions.cs +++ /dev/null @@ -1,149 +0,0 @@ -using Remora.Discord.API; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Objects; -using Remora.Discord.Extensions.Embeds; -using Remora.Rest.Core; - -namespace TeamOctolings.Octobot.Extensions; - -public static class EmbedBuilderExtensions -{ - /// - /// Adds a footer representing that an action was performed by a . - /// - /// 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) - { - var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256); - var avatarUrl = avatarUrlResult.IsSuccess - ? avatarUrlResult.Entity.AbsoluteUri - : CDN.GetDefaultUserAvatarUrl(user, imageSize: 256).Entity.AbsoluteUri; - - return builder.WithFooter( - new EmbedFooter($"{Messages.IssuedBy}:\n{user.GetTag()}", avatarUrl)); - } - - /// - /// Adds a title using the author field, making it smaller than using the title field. - /// - /// The builder to add the small title to. - /// The text of the small title. - /// 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) - { - Uri? avatarUrl = null; - if (avatarSource is not null) - { - var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256); - - avatarUrl = avatarUrlResult.IsSuccess - ? avatarUrlResult.Entity - : CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity; - } - - builder.Author = new EmbedAuthorBuilder(text, iconUrl: avatarUrl?.AbsoluteUri); - return builder; - } - - /// - /// Adds a user avatar in the thumbnail field. - /// - /// The builder to add the thumbnail to. - /// The user whose avatar to use in the thumbnail field. - /// The builder with the added avatar in the thumbnail field. - public static EmbedBuilder WithLargeUserAvatar( - this EmbedBuilder builder, IUser avatarSource) - { - var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256); - var avatarUrl = avatarUrlResult.IsSuccess - ? avatarUrlResult.Entity - : CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity; - - return builder.WithThumbnailUrl(avatarUrl.AbsoluteUri); - } - - /// - /// Adds a guild icon in the thumbnail field. - /// - /// The builder to add the thumbnail to. - /// The guild whose icon to use in the thumbnail field. - /// The builder with the added icon in the thumbnail field. - public static EmbedBuilder WithLargeGuildIcon( - this EmbedBuilder builder, IGuild iconSource) - { - var iconUrlResult = CDN.GetGuildIconUrl(iconSource, imageSize: 256); - return iconUrlResult.IsSuccess - ? builder.WithThumbnailUrl(iconUrlResult.Entity.AbsoluteUri) - : builder; - } - - /// - /// Adds a guild banner in the image field. - /// - /// The builder to add the image to. - /// The guild whose banner to use in the image field. - /// The builder with the added banner in the image field. - public static EmbedBuilder WithGuildBanner( - this EmbedBuilder builder, IGuild bannerSource) - { - return bannerSource.Banner is not null - ? builder.WithImageUrl(CDN.GetGuildBannerUrl(bannerSource).Entity.AbsoluteUri) - : builder; - } - - /// - /// Adds a footer representing that the action was performed in the . - /// - /// 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) - { - var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256); - var iconUrl = iconUrlResult.IsSuccess - ? iconUrlResult.Entity.AbsoluteUri - : default(Optional); - - return builder.WithFooter(new EmbedFooter(guild.Name, iconUrl)); - } - - /// - /// Adds a title representing that the action happened in the . - /// - /// 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) - { - var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256); - var iconUrl = iconUrlResult.IsSuccess - ? iconUrlResult.Entity.AbsoluteUri - : null; - - builder.Author = new EmbedAuthorBuilder(guild.Name, iconUrl: iconUrl); - return builder; - } - - /// - /// Adds a scheduled event's cover image. - /// - /// The builder to add the image to. - /// The ID of the scheduled event whose image to use. - /// 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; - } - - var iconUrlResult = CDN.GetGuildScheduledEventCoverUrl(eventId, imageHash, imageSize: 1024); - return iconUrlResult.IsDefined(out var iconUrl) ? builder.WithImageUrl(iconUrl.AbsoluteUri) : builder; - } -} diff --git a/TeamOctolings.Octobot/Extensions/FeedbackServiceExtensions.cs b/TeamOctolings.Octobot/Extensions/FeedbackServiceExtensions.cs deleted file mode 100644 index c66c946..0000000 --- a/TeamOctolings.Octobot/Extensions/FeedbackServiceExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Remora.Discord.API.Objects; -using Remora.Discord.Commands.Feedback.Messages; -using Remora.Discord.Commands.Feedback.Services; -using Remora.Results; - -namespace TeamOctolings.Octobot.Extensions; - -public static class FeedbackServiceExtensions -{ - public static async Task SendContextualEmbedResultAsync( - this IFeedbackService feedback, Result embedResult, - FeedbackMessageOptions? options = null, CancellationToken ct = default) - { - if (!embedResult.IsDefined(out var embed)) - { - return ResultExtensions.FromError(embedResult); - } - - return (Result)await feedback.SendContextualEmbedAsync(embed, options, ct); - } -} diff --git a/TeamOctolings.Octobot/Extensions/GuildScheduledEventExtensions.cs b/TeamOctolings.Octobot/Extensions/GuildScheduledEventExtensions.cs deleted file mode 100644 index 7822d9b..0000000 --- a/TeamOctolings.Octobot/Extensions/GuildScheduledEventExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Remora.Discord.API.Abstractions.Objects; -using Remora.Rest.Core; -using Remora.Results; - -namespace TeamOctolings.Octobot.Extensions; - -public static class GuildScheduledEventExtensions -{ - public static Result TryGetExternalEventData(this IGuildScheduledEvent scheduledEvent, out DateTimeOffset endTime, - out string? location) - { - endTime = default; - location = null; - if (!scheduledEvent.EntityMetadata.AsOptional().IsDefined(out var metadata)) - { - return new ArgumentNullError(nameof(scheduledEvent.EntityMetadata)); - } - - if (!metadata.Location.IsDefined(out location)) - { - return new ArgumentNullError(nameof(metadata.Location)); - } - - return scheduledEvent.ScheduledEndTime.AsOptional().IsDefined(out endTime) - ? Result.Success - : new ArgumentNullError(nameof(scheduledEvent.ScheduledEndTime)); - } -} diff --git a/TeamOctolings.Octobot/Extensions/LoggerExtensions.cs b/TeamOctolings.Octobot/Extensions/LoggerExtensions.cs deleted file mode 100644 index fac4dda..0000000 --- a/TeamOctolings.Octobot/Extensions/LoggerExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.Extensions.Logging; -using Remora.Results; - -namespace TeamOctolings.Octobot.Extensions; - -public static class LoggerExtensions -{ - /// - /// Checks if the has failed due to an error that has resulted from neither invalid user - /// input nor the execution environment and logs the error using the provided . - /// - /// - /// This has special behavior for - its exception will be passed to the - /// - /// - /// The logger to use. - /// The Result whose error check. - /// The message to use if this result has failed. - public static void LogResult(this ILogger logger, IResult result, string? message = "") - { - if (result.IsSuccess) - { - return; - } - - if (result.Error is ExceptionError exe) - { - if (exe.Exception is OperationCanceledException) - { - return; - } - - logger.LogError(exe.Exception, "{ErrorMessage}", message); - return; - } - - logger.LogWarning("{UserMessage}{NewLine}{ResultErrorMessage}", message, Environment.NewLine, - result.Error.Message); - } -} diff --git a/TeamOctolings.Octobot/Extensions/MarkdownExtensions.cs b/TeamOctolings.Octobot/Extensions/MarkdownExtensions.cs deleted file mode 100644 index 30ddff5..0000000 --- a/TeamOctolings.Octobot/Extensions/MarkdownExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace TeamOctolings.Octobot.Extensions; - -public static class MarkdownExtensions -{ - /// - /// Formats a string to use Markdown Bullet formatting. - /// - /// The input text to format. - /// - /// A markdown-formatted bullet string. - /// - public static string BulletPoint(string text) - { - return $"- {text}"; - } - - /// - /// Formats a string to use Markdown Quote formatting. - /// - /// The input text to format. - /// - /// A markdown-formatted quote string. - /// - public static string Quote(string text) - { - return $"> {text}"; - } -} diff --git a/TeamOctolings.Octobot/Extensions/ResultExtensions.cs b/TeamOctolings.Octobot/Extensions/ResultExtensions.cs deleted file mode 100644 index 6872d34..0000000 --- a/TeamOctolings.Octobot/Extensions/ResultExtensions.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Diagnostics; -using Microsoft.Extensions.Logging; -using Remora.Results; - -namespace TeamOctolings.Octobot.Extensions; - -public static class ResultExtensions -{ - public static Result FromError(Result result) - { - LogResultStackTrace(result); - - return result; - } - - public static Result FromError(Result result) - { - var casted = (Result)result; - LogResultStackTrace(casted); - - return casted; - } - - private static void LogResultStackTrace(Result result) - { - if (result.IsSuccess || result.Error is ExceptionError { Exception: OperationCanceledException }) - { - return; - } - - if (Utility.StaticLogger is null) - { - throw new InvalidOperationException(); - } - - Utility.StaticLogger.LogError("{ErrorType}: {ErrorMessage}{NewLine}{StackTrace}", - result.Error.GetType().FullName, result.Error.Message, Environment.NewLine, ConstructStackTrace()); - - var inner = result.Inner; - while (inner is { IsSuccess: false }) - { - Utility.StaticLogger.LogError("Caused by: {ResultType}: {ResultMessage}", - inner.Error.GetType().FullName, inner.Error.Message); - - inner = inner.Inner; - } - } - - private static string ConstructStackTrace() - { - var stackArray = new StackTrace(3, true).ToString().Split(Environment.NewLine).ToList(); - for (var i = stackArray.Count - 1; i >= 0; i--) - { - var frame = stackArray[i]; - var trimmed = frame.TrimStart(); - if (trimmed.StartsWith("at System.Threading", StringComparison.Ordinal) - || trimmed.StartsWith("at System.Runtime.CompilerServices", StringComparison.Ordinal)) - { - stackArray.RemoveAt(i); - } - } - - return string.Join(Environment.NewLine, stackArray); - } -} diff --git a/TeamOctolings.Octobot/Extensions/SnowflakeExtensions.cs b/TeamOctolings.Octobot/Extensions/SnowflakeExtensions.cs deleted file mode 100644 index 70810ef..0000000 --- a/TeamOctolings.Octobot/Extensions/SnowflakeExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Remora.Rest.Core; - -namespace TeamOctolings.Octobot.Extensions; - -public static class SnowflakeExtensions -{ - /// - /// Checks whether this Snowflake has any value set. - /// - /// 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) - { - return snowflake.Value is 0; - } - - /// - /// Checks whether this snowflake is empty (see ) or it's equal to - /// - /// - /// The Snowflake to check for emptiness - /// The Snowflake to check for equality with . - /// - /// true if is empty or is equal to , false - /// otherwise. - /// - /// - public static bool EmptyOrEqualTo(this Snowflake snowflake, Snowflake anotherSnowflake) - { - return snowflake.Empty() || snowflake == anotherSnowflake; - } -} diff --git a/TeamOctolings.Octobot/Extensions/StringBuilderExtensions.cs b/TeamOctolings.Octobot/Extensions/StringBuilderExtensions.cs deleted file mode 100644 index 25b7b5b..0000000 --- a/TeamOctolings.Octobot/Extensions/StringBuilderExtensions.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Text; - -namespace TeamOctolings.Octobot.Extensions; - -public static class StringBuilderExtensions -{ - /// - /// Appends the input string with Markdown Bullet formatting to the specified object. - /// - /// The object. - /// The string to append with bullet point. - /// - /// The builder with the appended string with Markdown Bullet formatting. - /// - public static StringBuilder AppendBulletPoint(this StringBuilder builder, string? value) - { - return builder.Append("- ").Append(value); - } - - /// - /// Appends the input string with Markdown Sub-Bullet formatting to the specified object. - /// - /// The object. - /// The string to append with sub-bullet point. - /// - /// The builder with the appended string with Markdown Sub-Bullet formatting. - /// - public static StringBuilder AppendSubBulletPoint(this StringBuilder builder, string? value) - { - return builder.Append(" - ").Append(value); - } - - /// - /// Appends the input string with Markdown Bullet formatting followed by - /// the default line terminator to the end of specified object. - /// - /// The object. - /// The string to append with bullet point. - /// - /// The builder with the appended string with Markdown Bullet formatting - /// and default line terminator at the end. - /// - public static StringBuilder AppendBulletPointLine(this StringBuilder builder, string? value) - { - return builder.Append("- ").AppendLine(value); - } - - /// - /// Appends the input string with Markdown Sub-Bullet formatting followed by - /// the default line terminator to the end of specified object. - /// - /// The object. - /// The string to append with sub-bullet point. - /// - /// The builder with the appended string with Markdown Sub-Bullet formatting - /// and default line terminator at the end. - /// - public static StringBuilder AppendSubBulletPointLine(this StringBuilder builder, string? value) - { - return builder.Append(" - ").AppendLine(value); - } -} diff --git a/TeamOctolings.Octobot/Extensions/StringExtensions.cs b/TeamOctolings.Octobot/Extensions/StringExtensions.cs deleted file mode 100644 index bf7f6c8..0000000 --- a/TeamOctolings.Octobot/Extensions/StringExtensions.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Net; -using Remora.Discord.Extensions.Formatting; - -namespace TeamOctolings.Octobot.Extensions; - -public static class StringExtensions -{ - private const string ZeroWidthSpace = "​"; - - /// - /// Sanitizes a string for use in by inserting zero-width spaces in between - /// symbols used to format the string with block code. - /// - /// The string to sanitize. - /// The sanitized string that can be safely used in . - private static string SanitizeForBlockCode(this string s) - { - return s.Replace("```", $"{ZeroWidthSpace}`{ZeroWidthSpace}`{ZeroWidthSpace}`{ZeroWidthSpace}"); - } - - /// - /// Sanitizes a string for use in when "language" is "diff" by - /// prepending a zero-width space before the input string to prevent Discord from applying syntax highlighting. - /// - /// This does not call , you have to do so yourself if needed. - /// The string to sanitize. - /// The sanitized string that can be safely used in with "diff" as the language. - public static string SanitizeForDiffBlock(this string s) - { - return $"{ZeroWidthSpace}{s}"; - } - - /// - /// 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 = "") - { - s = s.SanitizeForBlockCode(); - return - $"```{language}\n{s.SanitizeForBlockCode()}{(s.EndsWith('`') || string.IsNullOrWhiteSpace(s) ? " " : "")}```"; - } - - public static string Localized(this string key) - { - return Messages.ResourceManager.GetString(key, Messages.Culture) ?? key; - } - - /// - /// Encodes a string to allow its transmission in request headers. - /// - /// 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) - { - return WebUtility.UrlEncode(s).Replace('+', ' '); - } -} diff --git a/TeamOctolings.Octobot/Extensions/UInt64Extensions.cs b/TeamOctolings.Octobot/Extensions/UInt64Extensions.cs deleted file mode 100644 index 2b9c0a2..0000000 --- a/TeamOctolings.Octobot/Extensions/UInt64Extensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Remora.Discord.API; -using Remora.Rest.Core; - -namespace TeamOctolings.Octobot.Extensions; - -public static class UInt64Extensions -{ - public static Snowflake ToSnowflake(this ulong id) - { - return DiscordSnowflake.New(id); - } -} diff --git a/TeamOctolings.Octobot/Extensions/UserExtensions.cs b/TeamOctolings.Octobot/Extensions/UserExtensions.cs deleted file mode 100644 index d9eff33..0000000 --- a/TeamOctolings.Octobot/Extensions/UserExtensions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Remora.Discord.API.Abstractions.Objects; - -namespace TeamOctolings.Octobot.Extensions; - -public static class UserExtensions -{ - public static string GetTag(this IUser user) - { - return user.Discriminator is 0000 ? $"@{user.Username}" : $"{user.Username}#{user.Discriminator:0000}"; - } -} diff --git a/TeamOctolings.Octobot/Messages.Designer.cs b/TeamOctolings.Octobot/Messages.Designer.cs deleted file mode 100644 index 1a81e02..0000000 --- a/TeamOctolings.Octobot/Messages.Designer.cs +++ /dev/null @@ -1,1206 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace TeamOctolings.Octobot { - using System; - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [System.Diagnostics.DebuggerNonUserCodeAttribute()] - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static System.Resources.ResourceManager resourceMan; - - private static System.Globalization.CultureInfo resourceCulture; - - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Resources.ResourceManager ResourceManager { - get { - if (object.Equals(null, resourceMan)) { - System.Resources.ResourceManager temp = new System.Resources.ResourceManager("TeamOctolings.Octobot.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - internal static string Ready { - get { - return ResourceManager.GetString("Ready", resourceCulture); - } - } - - internal static string CachedMessageDeleted { - get { - return ResourceManager.GetString("CachedMessageDeleted", resourceCulture); - } - } - - internal static string CachedMessageEdited { - get { - return ResourceManager.GetString("CachedMessageEdited", resourceCulture); - } - } - - internal static string DefaultWelcomeMessage { - get { - return ResourceManager.GetString("DefaultWelcomeMessage", resourceCulture); - } - } - - internal static string Generic1 { - get { - return ResourceManager.GetString("Generic1", resourceCulture); - } - } - - internal static string Generic2 { - get { - return ResourceManager.GetString("Generic2", resourceCulture); - } - } - - internal static string Generic3 { - get { - return ResourceManager.GetString("Generic3", resourceCulture); - } - } - - internal static string YouWereBanned { - get { - return ResourceManager.GetString("YouWereBanned", resourceCulture); - } - } - - internal static string PunishmentExpired { - get { - return ResourceManager.GetString("PunishmentExpired", resourceCulture); - } - } - - internal static string YouWereKicked { - get { - return ResourceManager.GetString("YouWereKicked", resourceCulture); - } - } - - internal static string Milliseconds { - get { - return ResourceManager.GetString("Milliseconds", resourceCulture); - } - } - - internal static string ChannelNotSpecified { - get { - return ResourceManager.GetString("ChannelNotSpecified", resourceCulture); - } - } - - internal static string RoleNotSpecified { - get { - return ResourceManager.GetString("RoleNotSpecified", resourceCulture); - } - } - - internal static string SettingsLanguage { - get { - return ResourceManager.GetString("SettingsLanguage", resourceCulture); - } - } - - internal static string SettingsPrefix { - get { - return ResourceManager.GetString("SettingsPrefix", resourceCulture); - } - } - - internal static string SettingsRemoveRolesOnMute { - get { - return ResourceManager.GetString("SettingsRemoveRolesOnMute", resourceCulture); - } - } - - internal static string SettingsSendWelcomeMessages { - get { - return ResourceManager.GetString("SettingsSendWelcomeMessages", resourceCulture); - } - } - - internal static string SettingsMuteRole { - get { - return ResourceManager.GetString("SettingsMuteRole", resourceCulture); - } - } - - internal static string LanguageNotSupported { - get { - return ResourceManager.GetString("LanguageNotSupported", resourceCulture); - } - } - - internal static string Yes { - get { - return ResourceManager.GetString("Yes", resourceCulture); - } - } - - internal static string No { - get { - return ResourceManager.GetString("No", resourceCulture); - } - } - - internal static string UserNotBanned { - get { - return ResourceManager.GetString("UserNotBanned", resourceCulture); - } - } - - internal static string MemberNotMuted { - get { - return ResourceManager.GetString("MemberNotMuted", resourceCulture); - } - } - - internal static string SettingsWelcomeMessage { - get { - return ResourceManager.GetString("SettingsWelcomeMessage", resourceCulture); - } - } - - internal static string UserBanned { - get { - return ResourceManager.GetString("UserBanned", resourceCulture); - } - } - - internal static string SettingsReceiveStartupMessages { - get { - return ResourceManager.GetString("SettingsReceiveStartupMessages", resourceCulture); - } - } - - internal static string InvalidSettingValue { - get { - return ResourceManager.GetString("InvalidSettingValue", resourceCulture); - } - } - - internal static string DurationRequiredForTimeOuts { - get { - return ResourceManager.GetString("DurationRequiredForTimeOuts", resourceCulture); - } - } - - internal static string CannotTimeOutBot { - get { - return ResourceManager.GetString("CannotTimeOutBot", resourceCulture); - } - } - - internal static string SettingsEventNotificationRole { - get { - return ResourceManager.GetString("SettingsEventNotificationRole", resourceCulture); - } - } - - internal static string SettingsEventNotificationChannel { - get { - return ResourceManager.GetString("SettingsEventNotificationChannel", resourceCulture); - } - } - - internal static string SettingsEventStartedReceivers { - get { - return ResourceManager.GetString("SettingsEventStartedReceivers", resourceCulture); - } - } - - internal static string EventStarted { - get { - return ResourceManager.GetString("EventStarted", resourceCulture); - } - } - - internal static string EventCancelled { - get { - return ResourceManager.GetString("EventCancelled", resourceCulture); - } - } - - internal static string EventCompleted { - get { - return ResourceManager.GetString("EventCompleted", resourceCulture); - } - } - - internal static string MessagesCleared { - get { - return ResourceManager.GetString("MessagesCleared", resourceCulture); - } - } - - internal static string SettingsNothingChanged { - get { - return ResourceManager.GetString("SettingsNothingChanged", resourceCulture); - } - } - - internal static string SettingNotDefined { - get { - return ResourceManager.GetString("SettingNotDefined", resourceCulture); - } - } - - internal static string MissingUser { - get { - return ResourceManager.GetString("MissingUser", resourceCulture); - } - } - - internal static string UserCannotBanMembers { - get { - return ResourceManager.GetString("UserCannotBanMembers", resourceCulture); - } - } - - internal static string UserCannotManageMessages { - get { - return ResourceManager.GetString("UserCannotManageMessages", resourceCulture); - } - } - - internal static string UserCannotKickMembers { - get { - return ResourceManager.GetString("UserCannotKickMembers", resourceCulture); - } - } - - internal static string UserCannotMuteMembers { - get { - return ResourceManager.GetString("UserCannotMuteMembers", resourceCulture); - } - } - - internal static string UserCannotUnmuteMembers { - get { - return ResourceManager.GetString("UserCannotUnmuteMembers", resourceCulture); - } - } - - internal static string UserCannotManageGuild { - get { - return ResourceManager.GetString("UserCannotManageGuild", resourceCulture); - } - } - - internal static string BotCannotBanMembers { - get { - return ResourceManager.GetString("BotCannotBanMembers", resourceCulture); - } - } - - internal static string BotCannotManageMessages { - get { - return ResourceManager.GetString("BotCannotManageMessages", resourceCulture); - } - } - - internal static string BotCannotKickMembers { - get { - return ResourceManager.GetString("BotCannotKickMembers", resourceCulture); - } - } - - internal static string BotCannotModerateMembers { - get { - return ResourceManager.GetString("BotCannotModerateMembers", resourceCulture); - } - } - - internal static string BotCannotManageGuild { - get { - return ResourceManager.GetString("BotCannotManageGuild", resourceCulture); - } - } - - internal static string UserCannotBanOwner { - get { - return ResourceManager.GetString("UserCannotBanOwner", resourceCulture); - } - } - - internal static string UserCannotBanThemselves { - get { - return ResourceManager.GetString("UserCannotBanThemselves", resourceCulture); - } - } - - internal static string UserCannotBanBot { - get { - return ResourceManager.GetString("UserCannotBanBot", resourceCulture); - } - } - - internal static string BotCannotBanTarget { - get { - return ResourceManager.GetString("BotCannotBanTarget", resourceCulture); - } - } - - internal static string UserCannotBanTarget { - get { - return ResourceManager.GetString("UserCannotBanTarget", resourceCulture); - } - } - - internal static string UserCannotKickOwner { - get { - return ResourceManager.GetString("UserCannotKickOwner", resourceCulture); - } - } - - internal static string UserCannotKickThemselves { - get { - return ResourceManager.GetString("UserCannotKickThemselves", resourceCulture); - } - } - - internal static string UserCannotKickBot { - get { - return ResourceManager.GetString("UserCannotKickBot", resourceCulture); - } - } - - internal static string BotCannotKickTarget { - get { - return ResourceManager.GetString("BotCannotKickTarget", resourceCulture); - } - } - - internal static string UserCannotKickTarget { - get { - return ResourceManager.GetString("UserCannotKickTarget", resourceCulture); - } - } - - internal static string UserCannotMuteOwner { - get { - return ResourceManager.GetString("UserCannotMuteOwner", resourceCulture); - } - } - - internal static string UserCannotMuteThemselves { - get { - return ResourceManager.GetString("UserCannotMuteThemselves", resourceCulture); - } - } - - internal static string UserCannotMuteBot { - get { - return ResourceManager.GetString("UserCannotMuteBot", resourceCulture); - } - } - - internal static string BotCannotMuteTarget { - get { - return ResourceManager.GetString("BotCannotMuteTarget", resourceCulture); - } - } - - internal static string UserCannotMuteTarget { - get { - return ResourceManager.GetString("UserCannotMuteTarget", resourceCulture); - } - } - - internal static string UserCannotUnmuteOwner { - get { - return ResourceManager.GetString("UserCannotUnmuteOwner", resourceCulture); - } - } - - internal static string UserCannotUnmuteThemselves { - get { - return ResourceManager.GetString("UserCannotUnmuteThemselves", resourceCulture); - } - } - - internal static string UserCannotUnmuteBot { - get { - return ResourceManager.GetString("UserCannotUnmuteBot", resourceCulture); - } - } - - internal static string BotCannotUnmuteTarget { - get { - return ResourceManager.GetString("BotCannotUnmuteTarget", resourceCulture); - } - } - - internal static string UserCannotUnmuteTarget { - get { - return ResourceManager.GetString("UserCannotUnmuteTarget", resourceCulture); - } - } - - internal static string EventEarlyNotification { - get { - return ResourceManager.GetString("EventEarlyNotification", resourceCulture); - } - } - - internal static string SettingsEventEarlyNotificationOffset { - get { - return ResourceManager.GetString("SettingsEventEarlyNotificationOffset", resourceCulture); - } - } - - internal static string UserNotFound { - get { - return ResourceManager.GetString("UserNotFound", resourceCulture); - } - } - - internal static string SettingsDefaultRole { - get { - return ResourceManager.GetString("SettingsDefaultRole", resourceCulture); - } - } - - internal static string SettingsPublicFeedbackChannel { - get { - return ResourceManager.GetString("SettingsPublicFeedbackChannel", resourceCulture); - } - } - - internal static string SettingsPrivateFeedbackChannel { - get { - return ResourceManager.GetString("SettingsPrivateFeedbackChannel", resourceCulture); - } - } - - internal static string SettingsReturnRolesOnRejoin { - get { - return ResourceManager.GetString("SettingsReturnRolesOnRejoin", resourceCulture); - } - } - - internal static string SettingsAutoStartEvents { - get { - return ResourceManager.GetString("SettingsAutoStartEvents", resourceCulture); - } - } - - internal static string IssuedBy { - get { - return ResourceManager.GetString("IssuedBy", resourceCulture); - } - } - - internal static string EventCreatedTitle { - get { - return ResourceManager.GetString("EventCreatedTitle", resourceCulture); - } - } - - internal static string DescriptionLocalEventCreated { - get { - return ResourceManager.GetString("DescriptionLocalEventCreated", resourceCulture); - } - } - - internal static string DescriptionExternalEventCreated { - get { - return ResourceManager.GetString("DescriptionExternalEventCreated", resourceCulture); - } - } - - internal static string ButtonOpenEventInfo { - get { - return ResourceManager.GetString("ButtonOpenEventInfo", resourceCulture); - } - } - - internal static string EventDuration { - get { - return ResourceManager.GetString("EventDuration", resourceCulture); - } - } - - internal static string DescriptionLocalEventStarted { - get { - return ResourceManager.GetString("DescriptionLocalEventStarted", resourceCulture); - } - } - - internal static string DescriptionExternalEventStarted { - get { - return ResourceManager.GetString("DescriptionExternalEventStarted", resourceCulture); - } - } - - internal static string UserAlreadyBanned { - get { - return ResourceManager.GetString("UserAlreadyBanned", resourceCulture); - } - } - - internal static string UserUnbanned { - get { - return ResourceManager.GetString("UserUnbanned", resourceCulture); - } - } - - internal static string UserMuted { - get { - return ResourceManager.GetString("UserMuted", resourceCulture); - } - } - - internal static string UserUnmuted { - get { - return ResourceManager.GetString("UserUnmuted", resourceCulture); - } - } - - internal static string UserNotMuted { - get { - return ResourceManager.GetString("UserNotMuted", resourceCulture); - } - } - - internal static string UserNotFoundShort { - get { - return ResourceManager.GetString("UserNotFoundShort", resourceCulture); - } - } - - internal static string UserKicked { - get { - return ResourceManager.GetString("UserKicked", resourceCulture); - } - } - - internal static string DescriptionActionReason { - get { - return ResourceManager.GetString("DescriptionActionReason", resourceCulture); - } - } - - internal static string DescriptionActionExpiresAt { - get { - return ResourceManager.GetString("DescriptionActionExpiresAt", resourceCulture); - } - } - - internal static string UserAlreadyMuted { - get { - return ResourceManager.GetString("UserAlreadyMuted", resourceCulture); - } - } - - internal static string MessageFrom { - get { - return ResourceManager.GetString("MessageFrom", resourceCulture); - } - } - - internal static string AboutTitleDevelopers { - get { - return ResourceManager.GetString("AboutTitleDevelopers", resourceCulture); - } - } - - internal static string ButtonOpenWebsite { - get { - return ResourceManager.GetString("ButtonOpenWebsite", resourceCulture); - } - } - - internal static string AboutBot { - get { - return ResourceManager.GetString("AboutBot", resourceCulture); - } - } - - internal static string AboutDeveloper_mctaylors { - get { - return ResourceManager.GetString("AboutDeveloper@mctaylors", resourceCulture); - } - } - - internal static string AboutDeveloper_Octol1ttle { - get { - return ResourceManager.GetString("AboutDeveloper@Octol1ttle", resourceCulture); - } - } - - internal static string AboutDeveloper_neroduckale { - get { - return ResourceManager.GetString("AboutDeveloper@neroduckale", resourceCulture); - } - } - - internal static string ReminderCreated { - get { - return ResourceManager.GetString("ReminderCreated", resourceCulture); - } - } - - internal static string Reminder { - get { - return ResourceManager.GetString("Reminder", resourceCulture); - } - } - - internal static string DescriptionReminder { - get { - return ResourceManager.GetString("DescriptionReminder", resourceCulture); - } - } - - internal static string SettingsListTitle { - get { - return ResourceManager.GetString("SettingsListTitle", resourceCulture); - } - } - - internal static string SettingSuccessfullyChanged { - get { - return ResourceManager.GetString("SettingSuccessfullyChanged", resourceCulture); - } - } - - internal static string SettingNotChanged { - get { - return ResourceManager.GetString("SettingNotChanged", resourceCulture); - } - } - - internal static string SettingIsNow { - get { - return ResourceManager.GetString("SettingIsNow", resourceCulture); - } - } - - internal static string SettingsRenameHoistedUsers { - get { - return ResourceManager.GetString("SettingsRenameHoistedUsers", resourceCulture); - } - } - - internal static string Page { - get { - return ResourceManager.GetString("Page", resourceCulture); - } - } - - internal static string PageNotFound { - get { - return ResourceManager.GetString("PageNotFound", resourceCulture); - } - } - - internal static string PagesAllowed { - get { - return ResourceManager.GetString("PagesAllowed", resourceCulture); - } - } - - internal static string Next { - get { - return ResourceManager.GetString("Next", resourceCulture); - } - } - - internal static string Previous { - get { - return ResourceManager.GetString("Previous", resourceCulture); - } - } - - internal static string ReminderList { - get { - return ResourceManager.GetString("ReminderList", resourceCulture); - } - } - - internal static string InvalidReminderPosition { - get { - return ResourceManager.GetString("InvalidReminderPosition", resourceCulture); - } - } - - internal static string ReminderDeleted { - get { - return ResourceManager.GetString("ReminderDeleted", resourceCulture); - } - } - - internal static string NoRemindersFound { - get { - return ResourceManager.GetString("NoRemindersFound", resourceCulture); - } - } - - internal static string SingleSettingReset { - get { - return ResourceManager.GetString("SingleSettingReset", resourceCulture); - } - } - - internal static string AllSettingsReset { - get { - return ResourceManager.GetString("AllSettingsReset", resourceCulture); - } - } - - internal static string DescriptionActionJumpToMessage { - get { - return ResourceManager.GetString("DescriptionActionJumpToMessage", resourceCulture); - } - } - - internal static string DescriptionActionJumpToChannel { - get { - return ResourceManager.GetString("DescriptionActionJumpToChannel", resourceCulture); - } - } - - internal static string ReminderPosition { - get { - return ResourceManager.GetString("ReminderPosition", resourceCulture); - } - } - - internal static string ReminderTime { - get { - return ResourceManager.GetString("ReminderTime", resourceCulture); - } - } - - internal static string ReminderText { - get { - return ResourceManager.GetString("ReminderText", resourceCulture); - } - } - - internal static string UserInfoDisplayName { - get { - return ResourceManager.GetString("UserInfoDisplayName", resourceCulture); - } - } - - internal static string InformationAbout { - get { - return ResourceManager.GetString("InformationAbout", resourceCulture); - } - } - - internal static string UserInfoMuted { - get { - return ResourceManager.GetString("UserInfoMuted", resourceCulture); - } - } - - internal static string UserInfoDiscordUserSince { - get { - return ResourceManager.GetString("UserInfoDiscordUserSince", resourceCulture); - } - } - - internal static string UserInfoBanned { - get { - return ResourceManager.GetString("UserInfoBanned", resourceCulture); - } - } - - internal static string UserInfoPunishments { - get { - return ResourceManager.GetString("UserInfoPunishments", resourceCulture); - } - } - - internal static string UserInfoBannedPermanently { - get { - return ResourceManager.GetString("UserInfoBannedPermanently", resourceCulture); - } - } - - internal static string UserInfoNotOnGuild { - get { - return ResourceManager.GetString("UserInfoNotOnGuild", resourceCulture); - } - } - - internal static string UserInfoMutedByTimeout { - get { - return ResourceManager.GetString("UserInfoMutedByTimeout", resourceCulture); - } - } - - internal static string UserInfoMutedByMuteRole { - get { - return ResourceManager.GetString("UserInfoMutedByMuteRole", resourceCulture); - } - } - - internal static string UserInfoGuildMemberSince { - get { - return ResourceManager.GetString("UserInfoGuildMemberSince", resourceCulture); - } - } - - internal static string UserInfoGuildNickname { - get { - return ResourceManager.GetString("UserInfoGuildNickname", resourceCulture); - } - } - - internal static string UserInfoGuildRoles { - get { - return ResourceManager.GetString("UserInfoGuildRoles", resourceCulture); - } - } - - internal static string UserInfoGuildMemberPremiumSince { - get { - return ResourceManager.GetString("UserInfoGuildMemberPremiumSince", resourceCulture); - } - } - - internal static string RandomTitle { - get { - return ResourceManager.GetString("RandomTitle", resourceCulture); - } - } - - internal static string RandomMinMaxSame { - get { - return ResourceManager.GetString("RandomMinMaxSame", resourceCulture); - } - } - - internal static string RandomMin { - get { - return ResourceManager.GetString("RandomMin", resourceCulture); - } - } - - internal static string RandomMax { - get { - return ResourceManager.GetString("RandomMax", resourceCulture); - } - } - - internal static string Default { - get { - return ResourceManager.GetString("Default", resourceCulture); - } - } - - internal static string TimestampTitle { - get { - return ResourceManager.GetString("TimestampTitle", resourceCulture); - } - } - - internal static string TimestampOffset { - get { - return ResourceManager.GetString("TimestampOffset", resourceCulture); - } - } - - internal static string GuildInfoDescription { - get { - return ResourceManager.GetString("GuildInfoDescription", resourceCulture); - } - } - - internal static string GuildInfoCreatedAt { - get { - return ResourceManager.GetString("GuildInfoCreatedAt", resourceCulture); - } - } - - internal static string GuildInfoOwner { - get { - return ResourceManager.GetString("GuildInfoOwner", resourceCulture); - } - } - - internal static string GuildInfoServerBoost { - get { - return ResourceManager.GetString("GuildInfoServerBoost", resourceCulture); - } - } - - internal static string GuildInfoBoostTier { - get { - return ResourceManager.GetString("GuildInfoBoostTier", resourceCulture); - } - } - - internal static string GuildInfoBoostCount { - get { - return ResourceManager.GetString("GuildInfoBoostCount", resourceCulture); - } - } - - internal static string NoMessagesToClear { - get { - return ResourceManager.GetString("NoMessagesToClear", resourceCulture); - } - } - - internal static string MessagesClearedFiltered { - get { - return ResourceManager.GetString("MessagesClearedFiltered", resourceCulture); - } - } - - internal static string DataLoadFailedTitle { - get { - return ResourceManager.GetString("DataLoadFailedTitle", resourceCulture); - } - } - - internal static string DataLoadFailedDescription { - get { - return ResourceManager.GetString("DataLoadFailedDescription", resourceCulture); - } - } - - internal static string CommandExecutionFailed { - get { - return ResourceManager.GetString("CommandExecutionFailed", resourceCulture); - } - } - - internal static string ContactDevelopers { - get { - return ResourceManager.GetString("ContactDevelopers", resourceCulture); - } - } - - internal static string ButtonReportIssue { - get { - return ResourceManager.GetString("ButtonReportIssue", resourceCulture); - } - } - - internal static string DefaultLeaveMessage { - get { - return ResourceManager.GetString("DefaultLeaveMessage", resourceCulture); - } - } - - internal static string SettingsLeaveMessage { - get { - return ResourceManager.GetString("SettingsLeaveMessage", resourceCulture); - } - } - - internal static string InvalidTimeSpan { - get { - return ResourceManager.GetString("InvalidTimeSpan", resourceCulture); - } - } - - internal static string UserInfoKicked { - get { - return ResourceManager.GetString("UserInfoKicked", resourceCulture); - } - } - - internal static string ReminderEdited { - get { - return ResourceManager.GetString("ReminderEdited", resourceCulture); - } - } - - internal static string EightBallPositive1 { - get { - return ResourceManager.GetString("EightBallPositive1", resourceCulture); - } - } - - internal static string EightBallPositive2 { - get { - return ResourceManager.GetString("EightBallPositive2", resourceCulture); - } - } - - internal static string EightBallPositive3 { - get { - return ResourceManager.GetString("EightBallPositive3", resourceCulture); - } - } - - internal static string EightBallPositive4 { - get { - return ResourceManager.GetString("EightBallPositive4", resourceCulture); - } - } - - internal static string EightBallPositive5 { - get { - return ResourceManager.GetString("EightBallPositive5", resourceCulture); - } - } - - internal static string EightBallQuestionable1 { - get { - return ResourceManager.GetString("EightBallQuestionable1", resourceCulture); - } - } - - internal static string EightBallQuestionable2 { - get { - return ResourceManager.GetString("EightBallQuestionable2", resourceCulture); - } - } - - internal static string EightBallQuestionable3 { - get { - return ResourceManager.GetString("EightBallQuestionable3", resourceCulture); - } - } - - internal static string EightBallQuestionable4 { - get { - return ResourceManager.GetString("EightBallQuestionable4", resourceCulture); - } - } - - internal static string EightBallQuestionable5 { - get { - return ResourceManager.GetString("EightBallQuestionable5", resourceCulture); - } - } - - internal static string EightBallNeutral1 { - get { - return ResourceManager.GetString("EightBallNeutral1", resourceCulture); - } - } - - internal static string EightBallNeutral2 { - get { - return ResourceManager.GetString("EightBallNeutral2", resourceCulture); - } - } - - internal static string EightBallNeutral3 { - get { - return ResourceManager.GetString("EightBallNeutral3", resourceCulture); - } - } - - internal static string EightBallNeutral4 { - get { - return ResourceManager.GetString("EightBallNeutral4", resourceCulture); - } - } - - internal static string EightBallNeutral5 { - get { - return ResourceManager.GetString("EightBallNeutral5", resourceCulture); - } - } - - internal static string EightBallNegative1 { - get { - return ResourceManager.GetString("EightBallNegative1", resourceCulture); - } - } - - internal static string EightBallNegative2 { - get { - return ResourceManager.GetString("EightBallNegative2", resourceCulture); - } - } - - internal static string EightBallNegative3 { - get { - return ResourceManager.GetString("EightBallNegative3", resourceCulture); - } - } - - internal static string EightBallNegative4 { - get { - return ResourceManager.GetString("EightBallNegative4", resourceCulture); - } - } - - internal static string EightBallNegative5 { - get { - return ResourceManager.GetString("EightBallNegative5", resourceCulture); - } - } - - internal static string TimeSpanExample { - get { - return ResourceManager.GetString("TimeSpanExample", resourceCulture); - } - } - - internal static string Version { - get { - return ResourceManager.GetString("Version", resourceCulture); - } - } - - internal static string SettingsWelcomeMessagesChannel { - get { - return ResourceManager.GetString("SettingsWelcomeMessagesChannel", resourceCulture); - } - } - - internal static string ButtonDirty { - get { - return ResourceManager.GetString("ButtonDirty", resourceCulture); - } - } - - internal static string ButtonOpenWiki { - get { - return ResourceManager.GetString("ButtonOpenWiki", resourceCulture); - } - } - - internal static string SettingsModeratorRole { - get { - return ResourceManager.GetString("SettingsModeratorRole", resourceCulture); - } - } - - internal static string SettingValueEquals { - get { - return ResourceManager.GetString("SettingValueEquals", resourceCulture); - } - } - } -} diff --git a/TeamOctolings.Octobot/Messages.resx b/TeamOctolings.Octobot/Messages.resx deleted file mode 100644 index e4107fb..0000000 --- a/TeamOctolings.Octobot/Messages.resx +++ /dev/null @@ -1,687 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - 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 - - - I'm ready! - - - Deleted message by {0}: - - - Edited message by {0}: - - - {0}, welcome to {1} - - - Veemo! - - - Woomy! - - - Ngyes! - - - You were banned - - - Punishment expired - - - You were kicked - - - ms - - - Not specified - - - Not specified - - - Language - - - Prefix - - - Remove roles on mute - - - Send welcome messages - - - Mute role - - - Language not supported! - - - Yes - - - No - - - This user is not banned! - - - Member not muted! - - - Welcome message - - - {0} was banned - - - Receive startup messages - - - Invalid setting value specified! - - - I cannot mute someone for more than 28 days using timeouts! Either specify a duration shorter than 28 days, or set a mute role in settings - - - I cannot use time-outs on other bots! Try to set a mute role in settings - - - Role for event creation notifications - - - Channel for event notifications - - - Event start notifications receivers - - - Event "{0}" started - - - Event "{0}" is cancelled! - - - Event "{0}" has completed! - - - Cleared {0} messages - - - Nothing changed! `{0}` is already set to {1} - - - Not specified - - - You need to specify a user! - - - You cannot ban users from this guild! - - - You cannot manage messages in this guild! - - - You cannot kick members from this guild! - - - You cannot mute members in this guild! - - - You cannot unmute members in this guild! - - - You cannot manage this guild! - - - I cannot ban users from this guild! - - - I cannot manage messages in this guild! - - - I cannot kick members from this guild! - - - I cannot moderate members in this guild! - - - I cannot manage this guild! - - - You cannot ban the owner of this guild! - - - You cannot ban yourself! - - - You cannot ban me! - - - I cannot ban this user! - - - You cannot ban this user! - - - You cannot kick the owner of this guild! - - - You cannot kick yourself! - - - You cannot kick me! - - - I cannot kick this member! - - - You cannot kick this member! - - - You cannot mute the owner of this guild! - - - You cannot mute yourself! - - - You cannot mute me! - - - I cannot mute this member! - - - You cannot mute this member! - - - You don't need to unmute the owner of this guild! - - - You are muted! - - - ... - - - I cannot unmute this member! - - - You cannot unmute this user! - - - Event "{0}" will start {1}! - - - Early event start notification offset - - - I could not find this user in any guild I'm a member of! Check if the ID is correct and that the user was on this server no longer than 30 days ago - - - Default role - - - Channel for public notifications - - - Channel for private notifications - - - Return roles on rejoin - - - Automatically start scheduled events - - - Issued by - - - {0} has created a new event: - - - The event will start at {0} in {1} - - - The event will start at {0} until {1} in {2} - - - Open Event Info - - - The event has lasted for `{0}` - - - The event is happening at {0} - - - The event is happening at {0} until {1} - - - This user is already banned! - - - {0} was unbanned - - - {0} was muted - - - {0} was unmuted - - - This member is not muted! - - - I could not find this user! - - - {0} was kicked - - - Reason: {0} - - - Expires at: {0} - - - This user is already muted! - - - From {0}: - - - Developers: - - - Open Website - - - About {0} - - - developer & designer, Octobot's Wiki creator - - - main developer - - - developer - - - Reminder for {0} created - - - Reminder for {0} - - - You asked me to remind you {0} - - - Octobot's Settings - - - Setting successfully changed - - - Setting not changed - - - is now - - - Rename members who attempt to hoist themselves - - - Page - - - Page not found! - - - There are {0} total pages - - - Next - - - Previous - - - {0}'s reminders - - - There's no reminder in this position! - - - Reminder deleted - - - You don't have any reminders created! - - - Setting {0} reset - - - All settings have been reset - - - Jump to message: {0} - - - Jump to channel: {0} - - - Position in list: {0} - - - Reminder send time: {0} - - - Reminder text: {0} - - - Display name - - - Information about {0} - - - Muted - - - Discord user since - - - Banned - - - Punishments - - - Banned permanently - - - Not in the guild - - - Muted by timeout - - - Muted by mute role - - - Guild member since - - - Nickname - - - Roles - - - Nitro booster since - - - Random number for {0} is: - - - Isn't it obvious? - - - Minimum number: {0} - - - Maximum number: {0} - - - (default) - - - Timestamp for {0}: - - - Offset: {0} - - - Guild description - - - Creation date - - - Guild owner - - - Server Boost - - - Boost level - - - Boost count - - - There are no messages matching your filter! - - - Cleared {0} messages from {1} - - - An error occurred during guild data load. - - - This will lead to unexpected behavior. Data will no longer be saved - - - An error occurred during command execution, try again later. - - - Contact the developers if the problem occurs again. - - - Report an issue - - - See you soon, {0}! - - - Leave message - - - Time specified incorrectly! - - - Kicked - - - Reminder edited - - - It is certain - - - It is decidedly so - - - Without a doubt - - - Yes — definitely - - - You may rely on it - - - As I see it, yes - - - Most likely - - - Outlook good - - - Signs point to yes - - - Yes - - - Reply hazy, try again - - - Ask again later - - - Better not tell you now - - - Cannot predict now - - - Concentrate and ask again - - - Don’t count on it - - - My reply is no - - - My sources say no - - - Outlook not so good - - - Very doubtful - - - Example of a valid input: `1h30m` - - - Version: {0} - - - Welcome messages channel - - - Can't report an issue in the development version - - - Open Octobot's Wiki - - - Moderator role - - - The setting value is the same as the input value. - - diff --git a/TeamOctolings.Octobot/Messages.ru.resx b/TeamOctolings.Octobot/Messages.ru.resx deleted file mode 100644 index d942cec..0000000 --- a/TeamOctolings.Octobot/Messages.ru.resx +++ /dev/null @@ -1,687 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - 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 - - - Я запустился! - - - Сообщение {0} удалено: - - - Сообщение {0} отредактировано: - - - {0}, добро пожаловать на сервер {1} - - - Виимо! - - - Вууми! - - - Нгьес! - - - Время наказания истекло - - - Вы были выгнаны - - - мс - - - Не указан - - - Не указана - - - Язык - - - Префикс - - - Удалять роли при муте - - - Отправлять приветствия - - - Роль мута - - - Язык не поддерживается! - - - Да - - - Нет - - - Этот пользователь не забанен! - - - Участник не заглушен! - - - Приветствие - - - {0} был(-а) забанен(-а) - - - Получать сообщения о запуске - - - Указано недействительное значение для настройки! - - - Я не могу заглушить кого-то на более чем 28 дней, используя тайм-ауты! Или укажи продолжительность менее 28 дней, или установи роль мута в настройках - - - Я не могу использовать тайм-ауты на других ботах! Попробуй указать роль мута в настройках - - - Роль для уведомлений о создании событий - - - Канал для уведомлений о событиях - - - Получатели уведомлений о начале событий - - - Событие "{0}" началось - - - Событие "{0}" отменено! - - - Событие "{0}" завершено! - - - Очищено {0} сообщений - - - Ничего не изменилось! Значение настройки `{0}` уже {1} - - - Не указано - - - Надо указать пользователя! - - - Ты не можешь банить пользователей на этом сервере! - - - Ты не можешь управлять сообщениями этого сервера! - - - Ты не можешь выгонять участников с этого сервера! - - - Ты не можешь глушить участников этого сервера! - - - Ты не можешь разглушать участников этого сервера! - - - Ты не можешь настраивать этот сервер! - - - Я не могу банить пользователей на этом сервере! - - - Я не могу управлять сообщениями этого сервера! - - - Я не могу выгонять участников с этого сервера! - - - Я не могу модерировать участников этого сервера! - - - Я не могу настраивать этот сервер! - - - Ты не можешь меня забанить! - - - Ты не можешь забанить владельца этого сервера! - - - Ты не можешь забанить этого участника! - - - Ты не можешь себя забанить! - - - Я не могу забанить этого пользователя! - - - Ты не можешь выгнать владельца этого сервера! - - - Ты не можешь себя выгнать! - - - Ты не можешь меня выгнать! - - - Я не могу выгнать этого участника - - - Ты не можешь выгнать этого участника! - - - Ты не можешь заглушить владельца этого сервера! - - - Ты не можешь себя заглушить! - - - Ты не можешь заглушить меня! - - - Я не могу заглушить этого пользователя! - - - Ты не можешь заглушить этого участника! - - - Тебе не надо возвращать из мута владельца этого сервера! - - - Ты заглушен! - - - ... - - - Ты не можешь вернуть из мута этого пользователя! - - - Я не могу вернуть из мута этого пользователя! - - - Событие "{0}" начнется {1}! - - - Офсет отправки преждевременного уведомления о начале события - - - Я не смог найти этого пользователя ни в одном из серверов, в которых я есть. Проверь правильность ID и нахождение пользователя на этом сервере максимум 30 дней назад - - - Роль по умолчанию - - - Канал для публичных уведомлений - - - Канал для приватных уведомлений - - - Возвращать роли при перезаходе - - - Автоматически начинать события - - - Ответственный - - - {0} создаёт новое событие: - - - Событие пройдёт {0} в канале {1} - - - Событие пройдёт с {0} до {1} в {2} - - - Открыть сведения о событии - - - Событие длилось `{0}` - - - Событие происходит в {0} - - - Событие происходит в {0} до {1} - - - Этот пользователь уже забанен! - - - {0} был(-а) разбанен(-а) - - - {0} был(-а) заглушен(-а) - - - Этот участник не заглушен! - - - {0} был(-а) разглушен(-а) - - - Я не смог найти этого пользователя! - - - {0} был(-а) выгнан(-а) - - - Причина: {0} - - - Закончится: {0} - - - Этот пользователь уже в муте! - - - Вы были забанены - - - От {0}: - - - Разработчики: - - - Открыть веб-сайт - - - О боте {0} - - - разработчик - - - основной разработчик - - - разработчик и дизайнер, создатель Octobot's Wiki - - - Напоминание для {0} создано - - - Напоминание для {0} - - - Вы просили напомнить вам {0} - - - Настройки Octobot - - - Настройка успешно изменена - - - Настройка не редактирована - - - теперь имеет значение - - - Переименовывать участников, которые пытаются поднять себя - - - Страница - - - Страница не найдена! - - - Всего есть {0} страниц(-ы) - - - Далее - - - Назад - - - Напоминания {0} - - - У тебя нет напоминания на этой позиции! - - - Напоминание удалено - - - У вас нет созданных напоминаний! - - - Настройка {0} сброшена - - - Все настройки были сброшены - - - Перейти к сообщению: {0} - - - Перейти к каналу: {0} - - - Позиция в списке: {0} - - - Время отправки напоминания: {0} - - - Текст напоминания: {0} - - - Отображаемое имя - - - Информация о {0} - - - Заглушен - - - Вступил в Discord - - - Забанен - - - Наказания - - - Забанен навсегда - - - Не на сервере - - - Заглушен с помощью тайм-аута - - - Заглушен с помощью роли мута - - - Вступил на сервер - - - Никнейм - - - Роли - - - Начал бустить сервер - - - Случайное число для {0}: - - - Разве это не очевидно? - - - Максимальное число: {0} - - - Минимальное число: {0} - - - (по умолчанию) - - - Временная метка для {0}: - - - Офсет: {0} - - - Описание сервера - - - Дата создания - - - Владелец сервера - - - Буст сервера - - - Уровень буста - - - Количество бустов - - - Нет сообщений, которые подходят под твой фильтр! - - - Очищено {0} сообщений от {1} - - - Произошла ошибка при загрузке данных сервера. - - - Это может привести к неожиданному поведению. Данные больше не будут сохраняться. - - - Произошла ошибка при выполнении команды, повтори попытку позже. - - - Обратись к разработчикам, если проблема возникнет снова. - - - Сообщить о проблеме - - - До скорой встречи, {0}! - - - Сообщение о выходе - - - Неправильно указано время! - - - Выгнан - - - Напоминание отредактировано - - - Бесспорно - - - Предрешено - - - Никаких сомнений - - - Определённо да - - - Можешь быть уверен в этом - - - Мне кажется — «да» - - - Вероятнее всего - - - Хорошие перспективы - - - Знаки говорят — «да» - - - Да - - - Пока не ясно, попробуй снова - - - Спроси позже - - - Лучше не рассказывать - - - Сейчас нельзя предсказать - - - Сконцентрируйся и спроси снова - - - Даже не думай - - - Мой ответ — «нет» - - - По моим данным — «нет» - - - Перспективы не очень хорошие - - - Весьма сомнительно - - - Пример правильного ввода: `1ч30м` - - - Версия: {0} - - - Канал для приветствий - - - Нельзя сообщить о проблеме в версии под разработкой - - - Открыть Octobot's Wiki - - - Роль модератора - - - Значение настройки такое же, как и вводное значение. - - diff --git a/TeamOctolings.Octobot/Parsers/TimeSpanParser.cs b/TeamOctolings.Octobot/Parsers/TimeSpanParser.cs deleted file mode 100644 index 99a8b90..0000000 --- a/TeamOctolings.Octobot/Parsers/TimeSpanParser.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Globalization; -using System.Text.RegularExpressions; -using JetBrains.Annotations; -using Remora.Commands.Parsers; -using Remora.Results; - -namespace TeamOctolings.Octobot.Parsers; - -/// -/// Parses s. -/// -[PublicAPI] -public partial class TimeSpanParser : AbstractTypeParser -{ - private static readonly Regex Pattern = ParseRegex(); - - /// - /// Parses a from the . - /// - /// - /// The parsed , or if parsing failed. - /// - public static Result TryParse(string timeSpanString) - { - if (timeSpanString.StartsWith('-')) - { - return new ArgumentInvalidError(nameof(timeSpanString), "TimeSpans cannot be negative."); - } - - if (TimeSpan.TryParse(timeSpanString, DateTimeFormatInfo.InvariantInfo, out var parsedTimeSpan)) - { - return parsedTimeSpan; - } - - var matches = ParseRegex().Matches(timeSpanString); - return matches.Count > 0 - ? ParseFromRegex(matches) - : new ArgumentInvalidError(nameof(timeSpanString), "The regex did not produce any matches."); - } - - private static Result ParseFromRegex(MatchCollection matches) - { - var timeSpan = TimeSpan.Zero; - - foreach (var groups in matches.Select(match => match.Groups - .Cast() - .Where(g => g.Success) - .Skip(1) - .Select(g => (g.Name, g.Value)))) - { - foreach ((var key, var groupValue) in groups) - { - if (!int.TryParse(groupValue, out var parsedIntegerValue)) - { - return new ArgumentInvalidError(nameof(groupValue), "The input value was not an integer."); - } - - var now = DateTimeOffset.UtcNow; - timeSpan += key switch - { - "Years" => now.AddYears(parsedIntegerValue) - now, - "Months" => now.AddMonths(parsedIntegerValue) - now, - "Weeks" => TimeSpan.FromDays(parsedIntegerValue * 7), - "Days" => TimeSpan.FromDays(parsedIntegerValue), - "Hours" => TimeSpan.FromHours(parsedIntegerValue), - "Minutes" => TimeSpan.FromMinutes(parsedIntegerValue), - "Seconds" => TimeSpan.FromSeconds(parsedIntegerValue), - _ => throw new ArgumentOutOfRangeException(key) - }; - } - } - - return timeSpan; - } - - [GeneratedRegex("(?\\d+(?=y|л|г))|(?\\d+(?=mo|мес))|(?\\d+(?=w|н|нед))|(?\\d+(?=d|д|дн))|(?\\d+(?=h|ч))|(?\\d+(?=m|min|мин|м))|(?\\d+(?=s|sec|с|сек))")] - private static partial Regex ParseRegex(); -} diff --git a/TeamOctolings.Octobot/Program.cs b/TeamOctolings.Octobot/Program.cs deleted file mode 100644 index 8cdbdcf..0000000 --- a/TeamOctolings.Octobot/Program.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Remora.Discord.API.Abstractions.Gateway.Commands; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.Caching.Extensions; -using Remora.Discord.Caching.Services; -using Remora.Discord.Commands.Extensions; -using Remora.Discord.Commands.Services; -using Remora.Discord.Extensions.Extensions; -using Remora.Discord.Gateway; -using Remora.Discord.Hosting.Extensions; -using Serilog.Extensions.Logging; -using TeamOctolings.Octobot.Commands.Events; -using TeamOctolings.Octobot.Services; -using TeamOctolings.Octobot.Services.Update; - -namespace TeamOctolings.Octobot; - -public sealed class Program -{ - public static async Task Main(string[] args) - { - var host = CreateHostBuilder(args).UseConsoleLifetime().Build(); - var services = host.Services; - Utility.StaticLogger = services.GetRequiredService>(); - - var slashService = services.GetRequiredService(); - // Providing a guild ID to this call will result in command duplicates! - // To get rid of them, provide the ID of the guild containing duplicates, - // comment out calls to WithCommandGroup in CreateHostBuilder - // then launch the bot again and remove the guild ID - await slashService.UpdateSlashCommandsAsync(); - - await host.RunAsync(); - } - - private static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args) - .AddDiscordService(services => - { - var configuration = services.GetRequiredService(); - - return configuration.GetValue("BOT_TOKEN") - ?? throw new InvalidOperationException( - "No bot token has been provided. Set the " - + "BOT_TOKEN environment variable to a valid token."); - } - ).ConfigureServices((_, services) => - { - services.Configure(options => - { - options.Intents |= GatewayIntents.MessageContents - | GatewayIntents.GuildMembers - | GatewayIntents.GuildPresences - | GatewayIntents.GuildScheduledEvents; - }); - services.Configure(cSettings => - { - cSettings.SetDefaultAbsoluteExpiration(TimeSpan.FromHours(1)); - cSettings.SetDefaultSlidingExpiration(TimeSpan.FromMinutes(30)); - cSettings.SetAbsoluteExpiration(TimeSpan.FromDays(7)); - cSettings.SetSlidingExpiration(TimeSpan.FromDays(7)); - }); - - services.AddTransient() - // Init - .AddDiscordCaching() - .AddDiscordCommands(true, false) - .AddRespondersFromAssembly(typeof(Program).Assembly) - .AddCommandGroupsFromAssembly(typeof(Program).Assembly) - // Slash command event handlers - .AddPreparationErrorEvent() - .AddPostExecutionEvent() - // Services - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddHostedService(provider => provider.GetRequiredService()) - .AddHostedService() - .AddHostedService() - .AddHostedService(); - } - ).ConfigureLogging(c => c.AddConsole() - .AddFile("Logs/Octobot-{Date}.log", - 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) - .AddFilter("System.Net.Http.HttpClient.*.ClientHandler", LogLevel.Warning) - ); - } -} diff --git a/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs b/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs deleted file mode 100644 index b24ef0b..0000000 --- a/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs +++ /dev/null @@ -1,125 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using Remora.Discord.API.Abstractions.Gateway.Events; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.API.Gateway.Events; -using Remora.Discord.API.Objects; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Gateway.Responders; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Responders; - -/// -/// Handles sending a message to a guild that has just initialized if that guild -/// has enabled -/// -[UsedImplicitly] -public sealed class GuildLoadedResponder : IResponder -{ - private readonly IDiscordRestChannelAPI _channelApi; - private readonly GuildDataService _guildData; - private readonly ILogger _logger; - private readonly IDiscordRestUserAPI _userApi; - private readonly Utility _utility; - - public GuildLoadedResponder( - IDiscordRestChannelAPI channelApi, GuildDataService guildData, ILogger logger, - IDiscordRestUserAPI userApi, Utility utility) - { - _channelApi = channelApi; - _guildData = guildData; - _logger = logger; - _userApi = userApi; - _utility = utility; - } - - public async Task RespondAsync(IGuildCreate gatewayEvent, CancellationToken ct = default) - { - if (!gatewayEvent.Guild.IsT0) // Guild is not IAvailableGuild - { - return Result.Success; - } - - var guild = gatewayEvent.Guild.AsT0; - - var data = await _guildData.GetData(guild.ID, ct); - var cfg = data.Settings; - foreach (var member in guild.Members.Where(m => m.User.HasValue)) - { - data.GetOrCreateMemberData(member.User.Value.ID); - } - - var botResult = await _userApi.GetCurrentUserAsync(ct); - if (!botResult.IsDefined(out var bot)) - { - return ResultExtensions.FromError(botResult); - } - - if (data.DataLoadFailed) - { - return await SendDataLoadFailed(guild, data, bot, ct); - } - - var ownerResult = await _userApi.GetUserAsync(guild.OwnerID, ct); - if (!ownerResult.IsDefined(out var owner)) - { - return ResultExtensions.FromError(ownerResult); - } - - _logger.LogInformation("Loaded guild \"{Name}\" ({ID}) owned by {Owner} ({OwnerID}) with {MemberCount} members", - guild.Name, guild.ID, owner.GetTag(), owner.ID, guild.MemberCount); - - if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty() - || !GuildSettings.ReceiveStartupMessages.Get(cfg)) - { - return Result.Success; - } - - Messages.Culture = GuildSettings.Language.Get(cfg); - var i = Random.Shared.Next(1, 4); - - var embed = new EmbedBuilder().WithSmallTitle(bot.GetTag(), bot) - .WithTitle($"Generic{i}".Localized()) - .WithDescription(Messages.Ready) - .WithCurrentTimestamp() - .WithColour(ColorsList.Blue) - .Build(); - - return await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.PrivateFeedbackChannel.Get(cfg), embedResult: embed, ct: ct); - } - - private async Task SendDataLoadFailed(IGuild guild, GuildData data, IUser bot, CancellationToken ct = default) - { - var channelResult = await _utility.GetEmergencyFeedbackChannel(guild, data, ct); - if (!channelResult.IsDefined(out var channel)) - { - return ResultExtensions.FromError(channelResult); - } - - var errorEmbed = new EmbedBuilder() - .WithSmallTitle(Messages.DataLoadFailedTitle, bot) - .WithDescription(Messages.DataLoadFailedDescription) - .WithFooter(Messages.ContactDevelopers) - .WithColour(ColorsList.Red) - .Build(); - - var issuesButton = new ButtonComponent( - ButtonComponentStyle.Link, - BuildInfo.IsDirty - ? Messages.ButtonDirty - : Messages.ButtonReportIssue, - new PartialEmoji(Name: "\u26a0\ufe0f"), // 'WARNING SIGN' (U+26A0) - URL: BuildInfo.IssuesUrl, - IsDisabled: BuildInfo.IsDirty - ); - - return await _channelApi.CreateMessageWithEmbedResultAsync(channel, embedResult: errorEmbed, - components: new[] { new ActionRowComponent([issuesButton]) }, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs b/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs deleted file mode 100644 index ae9f174..0000000 --- a/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Text.Json.Nodes; -using JetBrains.Annotations; -using Remora.Discord.API.Abstractions.Gateway.Events; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Gateway.Responders; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Responders; - -/// -/// Handles sending a guild's if one is set. -/// If is enabled, roles will be returned. -/// -/// -[UsedImplicitly] -public sealed class GuildMemberJoinedResponder : IResponder -{ - private readonly IDiscordRestChannelAPI _channelApi; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly GuildDataService _guildData; - - public GuildMemberJoinedResponder( - IDiscordRestChannelAPI channelApi, GuildDataService guildData, IDiscordRestGuildAPI guildApi) - { - _channelApi = channelApi; - _guildData = guildData; - _guildApi = guildApi; - } - - 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 _guildData.GetData(gatewayEvent.GuildID, ct); - var cfg = data.Settings; - var memberData = data.GetOrCreateMemberData(user.ID); - - memberData.Kicked = false; - - var returnRolesResult = await TryReturnRolesAsync(cfg, memberData, gatewayEvent.GuildID, user.ID, ct); - if (!returnRolesResult.IsSuccess) - { - return ResultExtensions.FromError(returnRolesResult); - } - - if (GuildSettings.WelcomeMessagesChannel.Get(cfg).Empty() - || GuildSettings.WelcomeMessage.Get(cfg) is "off" or "disable" or "disabled") - { - return Result.Success; - } - - Messages.Culture = GuildSettings.Language.Get(cfg); - var welcomeMessage = GuildSettings.WelcomeMessage.Get(cfg) is "default" or "reset" - ? Messages.DefaultWelcomeMessage - : GuildSettings.WelcomeMessage.Get(cfg); - - var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct); - if (!guildResult.IsDefined(out var guild)) - { - return ResultExtensions.FromError(guildResult); - } - - var embed = new EmbedBuilder() - .WithSmallTitle(string.Format(welcomeMessage, user.GetTag(), guild.Name), user) - .WithGuildFooter(guild) - .WithTimestamp(gatewayEvent.JoinedAt) - .WithColour(ColorsList.Green) - .Build(); - - return await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed, - allowedMentions: Utility.NoMentions, ct: ct); - } - - private async Task TryReturnRolesAsync( - JsonNode cfg, MemberData memberData, Snowflake guildId, Snowflake userId, CancellationToken ct = default) - { - if (!GuildSettings.ReturnRolesOnRejoin.Get(cfg)) - { - return Result.Success; - } - - var assignRoles = new List(); - - if (memberData.MutedUntil is null || !GuildSettings.RemoveRolesOnMute.Get(cfg)) - { - assignRoles.AddRange(memberData.Roles.ConvertAll(r => r.ToSnowflake())); - } - - if (memberData.MutedUntil is not null) - { - assignRoles.Add(GuildSettings.MuteRole.Get(cfg)); - } - - return await _guildApi.ModifyGuildMemberAsync( - guildId, userId, roles: assignRoles, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs b/TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs deleted file mode 100644 index 957a107..0000000 --- a/TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs +++ /dev/null @@ -1,68 +0,0 @@ -using JetBrains.Annotations; -using Remora.Discord.API.Abstractions.Gateway.Events; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Gateway.Responders; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Responders; - -/// -/// Handles sending a guild's if one is set. -/// -/// -[UsedImplicitly] -public sealed class GuildMemberLeftResponder : IResponder -{ - private readonly IDiscordRestChannelAPI _channelApi; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly GuildDataService _guildData; - - public GuildMemberLeftResponder( - IDiscordRestChannelAPI channelApi, GuildDataService guildData, IDiscordRestGuildAPI guildApi) - { - _channelApi = channelApi; - _guildData = guildData; - _guildApi = guildApi; - } - - public async Task RespondAsync(IGuildMemberRemove gatewayEvent, CancellationToken ct = default) - { - var user = gatewayEvent.User; - var data = await _guildData.GetData(gatewayEvent.GuildID, ct); - var cfg = data.Settings; - - var memberData = data.GetOrCreateMemberData(user.ID); - if (memberData.BannedUntil is not null || memberData.Kicked - || GuildSettings.WelcomeMessagesChannel.Get(cfg).Empty() - || GuildSettings.LeaveMessage.Get(cfg) is "off" or "disable" or "disabled") - { - return Result.Success; - } - - Messages.Culture = GuildSettings.Language.Get(cfg); - var leaveMessage = GuildSettings.LeaveMessage.Get(cfg) is "default" or "reset" - ? Messages.DefaultLeaveMessage - : GuildSettings.LeaveMessage.Get(cfg); - - var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct); - if (!guildResult.IsDefined(out var guild)) - { - return ResultExtensions.FromError(guildResult); - } - - var embed = new EmbedBuilder() - .WithSmallTitle(string.Format(leaveMessage, user.GetTag(), guild.Name), user) - .WithGuildFooter(guild) - .WithTimestamp(DateTimeOffset.UtcNow) - .WithColour(ColorsList.Black) - .Build(); - - return await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed, - allowedMentions: Utility.NoMentions, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs b/TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs deleted file mode 100644 index c73c134..0000000 --- a/TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs +++ /dev/null @@ -1,38 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using Remora.Discord.API.Abstractions.Gateway.Events; -using Remora.Discord.Gateway.Responders; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Responders; - -/// -/// Handles removing guild ID from if the guild becomes unavailable. -/// -[UsedImplicitly] -public sealed class GuildUnloadedResponder : IResponder -{ - private readonly GuildDataService _guildData; - private readonly ILogger _logger; - - public GuildUnloadedResponder( - GuildDataService guildData, ILogger logger) - { - _guildData = guildData; - _logger = logger; - } - - public Task RespondAsync(IGuildDelete gatewayEvent, CancellationToken ct = default) - { - var guildId = gatewayEvent.ID; - var isDataRemoved = _guildData.UnloadGuildData(guildId); - if (isDataRemoved) - { - _logger.LogInformation("Unloaded guild {GuildId}", guildId); - } - - return Task.FromResult(Result.Success); - } -} diff --git a/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs b/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs deleted file mode 100644 index f0e3d22..0000000 --- a/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Text; -using JetBrains.Annotations; -using Remora.Discord.API.Abstractions.Gateway.Events; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Discord.Gateway.Responders; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Responders; - -/// -/// Handles logging the contents of a deleted message and the user who deleted the message -/// to a guild's if one is set. -/// -[UsedImplicitly] -public sealed class MessageDeletedResponder : IResponder -{ - private readonly IDiscordRestAuditLogAPI _auditLogApi; - private readonly IDiscordRestChannelAPI _channelApi; - private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; - - public MessageDeletedResponder( - IDiscordRestAuditLogAPI auditLogApi, IDiscordRestChannelAPI channelApi, - GuildDataService guildData, IDiscordRestUserAPI userApi) - { - _auditLogApi = auditLogApi; - _channelApi = channelApi; - _guildData = guildData; - _userApi = userApi; - } - - public async Task RespondAsync(IMessageDelete gatewayEvent, CancellationToken ct = default) - { - if (!gatewayEvent.GuildID.IsDefined(out var guildId)) - { - return Result.Success; - } - - var cfg = await _guildData.GetSettings(guildId, ct); - if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) - { - return Result.Success; - } - - var messageResult = await _channelApi.GetChannelMessageAsync(gatewayEvent.ChannelID, gatewayEvent.ID, ct); - if (!messageResult.IsDefined(out var message)) - { - return ResultExtensions.FromError(messageResult); - } - - if (string.IsNullOrWhiteSpace(message.Content)) - { - return Result.Success; - } - - var auditLogResult = await _auditLogApi.GetGuildAuditLogAsync( - guildId, actionType: AuditLogEvent.MessageDelete, limit: 1, ct: ct); - if (!auditLogResult.IsDefined(out var auditLogPage)) - { - return ResultExtensions.FromError(auditLogResult); - } - - var deleterResult = Result.FromSuccess(message.Author); - - var auditLog = auditLogPage.AuditLogEntries.SingleOrDefault(); - if (auditLog is { UserID: not null } - && auditLog.Options.Value.ChannelID == gatewayEvent.ChannelID - && DateTimeOffset.UtcNow.Subtract(auditLog.ID.Timestamp).TotalSeconds <= 2) - { - deleterResult = await _userApi.GetUserAsync(auditLog.UserID.Value, ct); - } - - if (!deleterResult.IsDefined(out var deleter)) - { - return ResultExtensions.FromError(deleterResult); - } - - Messages.Culture = GuildSettings.Language.Get(cfg); - - var builder = new StringBuilder() - .AppendLine(message.Content.InBlockCode()) - .AppendLine( - string.Format(Messages.DescriptionActionJumpToChannel, Mention.Channel(gatewayEvent.ChannelID)) - ); - - var embed = new EmbedBuilder() - .WithSmallTitle( - string.Format( - Messages.CachedMessageDeleted, - message.Author.GetTag()), message.Author) - .WithDescription(builder.ToString()) - .WithActionFooter(deleter) - .WithTimestamp(message.Timestamp) - .WithColour(ColorsList.Red) - .Build(); - - return await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.PrivateFeedbackChannel.Get(cfg), embedResult: embed, - allowedMentions: Utility.NoMentions, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Responders/MessageEditedResponder.cs b/TeamOctolings.Octobot/Responders/MessageEditedResponder.cs deleted file mode 100644 index e3d1c58..0000000 --- a/TeamOctolings.Octobot/Responders/MessageEditedResponder.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Text; -using DiffPlex.DiffBuilder; -using JetBrains.Annotations; -using Remora.Discord.API.Abstractions.Gateway.Events; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Caching; -using Remora.Discord.Caching.Services; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Gateway.Responders; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; -using TeamOctolings.Octobot.Services; - -namespace TeamOctolings.Octobot.Responders; - -/// -/// Handles logging the difference between an edited message's old and new content -/// to a guild's if one is set. -/// -[UsedImplicitly] -public sealed class MessageEditedResponder : IResponder -{ - private readonly CacheService _cacheService; - private readonly IDiscordRestChannelAPI _channelApi; - private readonly GuildDataService _guildData; - - public MessageEditedResponder( - CacheService cacheService, IDiscordRestChannelAPI channelApi, GuildDataService guildData) - { - _cacheService = cacheService; - _channelApi = channelApi; - _guildData = guildData; - } - - public async Task RespondAsync(IMessageUpdate gatewayEvent, CancellationToken ct = default) - { - if (!gatewayEvent.GuildID.IsDefined(out var guildId) - || !gatewayEvent.EditedTimestamp.HasValue - || gatewayEvent.Author.IsBot.OrDefault(false)) - { - return Result.Success; - } - - var cfg = await _guildData.GetSettings(guildId, ct); - if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) - { - return Result.Success; - } - - var cacheKey = new KeyHelpers.MessageCacheKey(gatewayEvent.ChannelID, gatewayEvent.ID); - var messageResult = await _cacheService.TryGetValueAsync( - cacheKey, ct); - if (!messageResult.IsDefined(out var message)) - { - _ = _channelApi.GetChannelMessageAsync(gatewayEvent.ChannelID, gatewayEvent.ID, ct); - return Result.Success; - } - - if (message.Content == gatewayEvent.Content) - { - return Result.Success; - } - - // Custom event responders are called earlier than responders responsible for message caching - // This means that subsequent edit logs may contain the wrong content - // We can work around this by evicting the message from the cache - await _cacheService.EvictAsync(cacheKey, ct); - // However, since we evicted the message, subsequent edits won't have a cached instance to work with - // Getting the message will put it back in the cache, resolving all issues - // We don't need to await this since the result is not needed - // NOTE: Because this is not awaited, there may be a race condition depending on how fast clients are able to edit their messages - // NOTE: Awaiting this might not even solve this if the same responder is called asynchronously - _ = _channelApi.GetChannelMessageAsync(gatewayEvent.ChannelID, gatewayEvent.ID, ct); - - var diff = InlineDiffBuilder.Diff(message.Content, gatewayEvent.Content); - - Messages.Culture = GuildSettings.Language.Get(cfg); - - var builder = new StringBuilder() - .AppendLine(diff.AsMarkdown()) - .AppendLine(string.Format(Messages.DescriptionActionJumpToMessage, - $"https://discord.com/channels/{guildId}/{gatewayEvent.ChannelID}/{gatewayEvent.ID}") - ); - - var embed = new EmbedBuilder() - .WithSmallTitle(string.Format(Messages.CachedMessageEdited, message.Author.GetTag()), message.Author) - .WithDescription(builder.ToString()) - .WithTimestamp(gatewayEvent.EditedTimestamp.Value) - .WithColour(ColorsList.Yellow) - .Build(); - - return await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.PrivateFeedbackChannel.Get(cfg), embedResult: embed, - allowedMentions: Utility.NoMentions, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs b/TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs deleted file mode 100644 index 24d53a5..0000000 --- a/TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs +++ /dev/null @@ -1,39 +0,0 @@ -using JetBrains.Annotations; -using Remora.Discord.API.Abstractions.Gateway.Events; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Gateway.Responders; -using Remora.Rest.Core; -using Remora.Results; - -namespace TeamOctolings.Octobot.Responders; - -/// -/// Handles sending replies to easter egg messages. -/// -[UsedImplicitly] -public sealed class MessageCreateResponder : IResponder -{ - private readonly IDiscordRestChannelAPI _channelApi; - - public MessageCreateResponder(IDiscordRestChannelAPI channelApi) - { - _channelApi = channelApi; - } - - public Task RespondAsync(IMessageCreate gatewayEvent, CancellationToken ct = default) - { - _ = _channelApi.CreateMessageAsync( - gatewayEvent.ChannelID, ct: ct, content: gatewayEvent.Content.ToLowerInvariant() switch - { - "whoami" => "`nobody`", - "сука !!" => "`root`", - "воооо" => "`removing /...`", - "пон" => "https://i.ibb.co/Kw6QVcw/parry.jpg", - "++++" => "#", - "осу" => "https://github.com/ppy/osu", - "лан" => "https://i.ibb.co/VYH2QLc/lan.jpg", - _ => default(Optional) - }); - return Task.FromResult(Result.Success); - } -} diff --git a/TeamOctolings.Octobot/Services/AccessControlService.cs b/TeamOctolings.Octobot/Services/AccessControlService.cs deleted file mode 100644 index d39c9e5..0000000 --- a/TeamOctolings.Octobot/Services/AccessControlService.cs +++ /dev/null @@ -1,142 +0,0 @@ -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; - -namespace TeamOctolings.Octobot.Services; - -public sealed class AccessControlService -{ - private readonly GuildDataService _data; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly IDiscordRestUserAPI _userApi; - - public AccessControlService(GuildDataService data, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi) - { - _data = data; - _guildApi = guildApi; - _userApi = userApi; - } - - private static bool CheckPermission(IEnumerable roles, GuildData data, MemberData memberData, - DiscordPermission permission) - { - var moderatorRole = GuildSettings.ModeratorRole.Get(data.Settings); - if (!moderatorRole.Empty() && memberData.Roles.Contains(moderatorRole.Value)) - { - return true; - } - - return roles - .Where(r => memberData.Roles.Contains(r.ID.Value)) - .Any(r => - r.Permissions.HasPermission(permission) - ); - } - - /// - /// Checks whether or not a member can interact with another member - /// - /// The ID of the guild in which an operation is being performed. - /// The executor of the operation. - /// The target of the operation. - /// The operation. - /// The cancellation token for this operation. - /// - /// - /// A result which has succeeded with a null string if the member can interact with the target. - /// - /// A result which has succeeded with a non-null string containing the error message if the member cannot - /// interact with the target. - /// - /// A result which has failed if an error occurred during the execution of this method. - /// - /// - public async Task> CheckInteractionsAsync( - Snowflake guildId, Snowflake? interacterId, Snowflake targetId, string action, CancellationToken ct = default) - { - if (interacterId == targetId) - { - return Result.FromSuccess($"UserCannot{action}Themselves".Localized()); - } - - var guildResult = await _guildApi.GetGuildAsync(guildId, ct: ct); - if (!guildResult.IsDefined(out var guild)) - { - return Result.FromError(guildResult); - } - - if (interacterId == guild.OwnerID) - { - return Result.FromSuccess(null); - } - - var botResult = await _userApi.GetCurrentUserAsync(ct); - if (!botResult.IsDefined(out var bot)) - { - return Result.FromError(botResult); - } - - var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct); - if (!rolesResult.IsDefined(out var roles)) - { - return Result.FromError(rolesResult); - } - - var data = await _data.GetData(guildId, ct); - var targetData = data.GetOrCreateMemberData(targetId); - var botData = data.GetOrCreateMemberData(bot.ID); - - if (interacterId is null) - { - return CheckInteractions(action, guild, roles, targetData, botData, botData); - } - - var interacterData = data.GetOrCreateMemberData(interacterId.Value); - var hasPermission = CheckPermission(roles, data, interacterData, - action switch - { - "Ban" => DiscordPermission.BanMembers, - "Kick" => DiscordPermission.KickMembers, - "Mute" or "Unmute" => DiscordPermission.ModerateMembers, - _ => throw new Exception() - }); - - return hasPermission - ? CheckInteractions(action, guild, roles, targetData, botData, interacterData) - : Result.FromSuccess($"UserCannot{action}Members".Localized()); - } - - private static Result CheckInteractions( - string action, IGuild guild, IReadOnlyList roles, MemberData targetData, MemberData botData, - MemberData interacterData) - { - if (botData.Id == targetData.Id) - { - return Result.FromSuccess($"UserCannot{action}Bot".Localized()); - } - - if (targetData.Id == guild.OwnerID) - { - return Result.FromSuccess($"UserCannot{action}Owner".Localized()); - } - - var targetRoles = roles.Where(r => targetData.Roles.Contains(r.ID.Value)).ToList(); - var botRoles = roles.Where(r => botData.Roles.Contains(r.ID.Value)); - - var targetBotRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - botRoles.MaxOrDefault(r => r.Position); - if (targetBotRoleDiff >= 0) - { - return Result.FromSuccess($"BotCannot{action}Target".Localized()); - } - - var interacterRoles = roles.Where(r => interacterData.Roles.Contains(r.ID.Value)); - var targetInteracterRoleDiff - = targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position); - return targetInteracterRoleDiff < 0 - ? Result.FromSuccess(null) - : Result.FromSuccess($"UserCannot{action}Target".Localized()); - } -} diff --git a/TeamOctolings.Octobot/Services/GuildDataService.cs b/TeamOctolings.Octobot/Services/GuildDataService.cs deleted file mode 100644 index 88edb5f..0000000 --- a/TeamOctolings.Octobot/Services/GuildDataService.cs +++ /dev/null @@ -1,297 +0,0 @@ -using System.Collections.Concurrent; -using System.Text.Json; -using System.Text.Json.Nodes; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Remora.Rest.Core; -using TeamOctolings.Octobot.Data; - -namespace TeamOctolings.Octobot.Services; - -/// -/// Handles saving, loading, initializing and providing . -/// -public sealed class GuildDataService : BackgroundService -{ - private readonly ConcurrentDictionary _datas = new(); - private readonly ILogger _logger; - - public GuildDataService(ILogger logger) - { - _logger = logger; - } - - public override Task StopAsync(CancellationToken ct) - { - base.StopAsync(ct); - return SaveAsync(ct); - } - - private Task SaveAsync(CancellationToken ct = default) - { - var tasks = new List(); - var datas = _datas.Values.ToArray(); - foreach (var data in datas.Where(data => !data.DataLoadFailed)) - { - tasks.Add(SerializeObjectSafelyAsync(data.Settings, data.SettingsPath, ct)); - tasks.Add(SerializeObjectSafelyAsync(data.ScheduledEvents, data.ScheduledEventsPath, ct)); - - var memberDatas = data.MemberData.Values.ToArray(); - tasks.AddRange(memberDatas.Select(memberData => - SerializeObjectSafelyAsync(memberData, $"{data.MemberDataPath}/{memberData.Id}.json", ct))); - } - - return Task.WhenAll(tasks); - } - - private static async Task SerializeObjectSafelyAsync(T obj, string path, CancellationToken ct = default) - { - var tempFilePath = path + ".tmp"; - await using (var tempFileStream = File.Create(tempFilePath)) - { - await JsonSerializer.SerializeAsync(tempFileStream, obj, cancellationToken: ct); - } - - File.Copy(tempFilePath, path, true); - File.Delete(tempFilePath); - } - - protected override async Task ExecuteAsync(CancellationToken ct) - { - using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5)); - - while (await timer.WaitForNextTickAsync(ct)) - { - await SaveAsync(ct); - } - } - - 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) - { - var path = $"GuildData/{guildId}"; - var memberDataPath = $"{path}/MemberData"; - - var settingsPath = $"{path}/Settings.json"; - - var scheduledEventsPath = $"{path}/ScheduledEvents.json"; - - MigrateDataDirectory(guildId, path); - - Directory.CreateDirectory(path); - - var dataLoadFailed = false; - - var jsonSettings = await LoadGuildSettings(settingsPath, ct); - if (jsonSettings is not null) - { - FixJsonSettings(jsonSettings); - } - else - { - dataLoadFailed = true; - } - - var events = await LoadScheduledEvents(scheduledEventsPath, ct); - if (events is null) - { - dataLoadFailed = true; - } - - var memberData = new Dictionary(); - foreach (var dataFileInfo in Directory.CreateDirectory(memberDataPath).GetFiles() - .Where(dataFileInfo => - !memberData.ContainsKey( - ulong.Parse(dataFileInfo.Name.Replace(".json", "").Replace(".tmp", ""))))) - { - var data = await LoadMemberData(dataFileInfo, memberDataPath, true, ct); - - if (data == null) - { - dataLoadFailed = true; - continue; - } - - memberData.TryAdd(data.Id, data); - } - - var finalData = new GuildData( - jsonSettings ?? new JsonObject(), settingsPath, - events ?? new Dictionary(), scheduledEventsPath, - memberData, memberDataPath, - dataLoadFailed); - - _datas.TryAdd(guildId, finalData); - - return finalData; - } - - private async Task LoadMemberData(FileInfo dataFileInfo, string memberDataPath, bool loadTmp, - CancellationToken ct = default) - { - MemberData? data; - var temporaryPath = $"{dataFileInfo.FullName}.tmp"; - var usedInfo = loadTmp && File.Exists(temporaryPath) ? new FileInfo(temporaryPath) : dataFileInfo; - - var isTmp = usedInfo.Extension is ".tmp"; - try - { - await using var dataStream = usedInfo.OpenRead(); - data = await JsonSerializer.DeserializeAsync(dataStream, cancellationToken: ct); - if (isTmp) - { - usedInfo.CopyTo(usedInfo.FullName.Replace(".tmp", ""), true); - usedInfo.Delete(); - } - } - catch (Exception e) - { - if (isTmp) - { - _logger.LogWarning(e, - "Unable to load temporary member data file, deleting: {MemberDataPath}/{FileName}", memberDataPath, - usedInfo.Name); - usedInfo.Delete(); - return await LoadMemberData(dataFileInfo, memberDataPath, false, ct); - } - - _logger.LogError(e, "Member data load failed: {MemberDataPath}/{FileName}", memberDataPath, - usedInfo.Name); - return null; - } - - return data; - } - - private async Task?> LoadScheduledEvents(string scheduledEventsPath, - CancellationToken ct = default) - { - var tempScheduledEventsPath = $"{scheduledEventsPath}.tmp"; - - if (!File.Exists(scheduledEventsPath) && !File.Exists(tempScheduledEventsPath)) - { - return new Dictionary(); - } - - if (File.Exists(tempScheduledEventsPath)) - { - _logger.LogWarning("Found temporary scheduled events file, will try to parse and copy to main: ${Path}", - tempScheduledEventsPath); - try - { - await using var tempEventsStream = File.OpenRead(tempScheduledEventsPath); - var events = await JsonSerializer.DeserializeAsync>( - tempEventsStream, cancellationToken: ct); - File.Copy(tempScheduledEventsPath, scheduledEventsPath, true); - File.Delete(tempScheduledEventsPath); - - _logger.LogInformation("Successfully loaded temporary scheduled events file: ${Path}", - tempScheduledEventsPath); - return events; - } - catch (Exception e) - { - _logger.LogError(e, "Unable to load temporary scheduled events file: {Path}, deleting", - tempScheduledEventsPath); - File.Delete(tempScheduledEventsPath); - } - } - - try - { - await using var eventsStream = File.OpenRead(scheduledEventsPath); - return await JsonSerializer.DeserializeAsync>( - eventsStream, cancellationToken: ct); - } - catch (Exception e) - { - _logger.LogError(e, "Guild scheduled events load failed: {Path}", scheduledEventsPath); - return null; - } - } - - private async Task LoadGuildSettings(string settingsPath, CancellationToken ct = default) - { - var tempSettingsPath = $"{settingsPath}.tmp"; - - if (!File.Exists(settingsPath) && !File.Exists(tempSettingsPath)) - { - return new JsonObject(); - } - - if (File.Exists(tempSettingsPath)) - { - _logger.LogWarning("Found temporary settings file, will try to parse and copy to main: ${Path}", - tempSettingsPath); - try - { - await using var tempSettingsStream = File.OpenRead(tempSettingsPath); - var jsonSettings = await JsonNode.ParseAsync(tempSettingsStream, cancellationToken: ct); - - File.Copy(tempSettingsPath, settingsPath, true); - File.Delete(tempSettingsPath); - - _logger.LogInformation("Successfully loaded temporary settings file: ${Path}", tempSettingsPath); - return jsonSettings; - } - catch (Exception e) - { - _logger.LogError(e, "Unable to load temporary settings file: {Path}, deleting", tempSettingsPath); - File.Delete(tempSettingsPath); - } - } - - try - { - await using var settingsStream = File.OpenRead(settingsPath); - return await JsonNode.ParseAsync(settingsStream, cancellationToken: ct); - } - catch (Exception e) - { - _logger.LogError(e, "Guild settings load failed: {Path}", settingsPath); - return null; - } - } - - private void MigrateDataDirectory(Snowflake guildId, string newPath) - { - var oldPath = $"{guildId}"; - - if (Directory.Exists(oldPath)) - { - Directory.CreateDirectory($"{newPath}/.."); - Directory.Move(oldPath, newPath); - - _logger.LogInformation("Moved guild data to separate folder: \"{OldPath}\" -> \"{NewPath}\"", oldPath, - newPath); - } - } - - private static void FixJsonSettings(JsonNode settings) - { - var language = settings[GuildSettings.Language.Name]?.GetValue(); - if (language is "mctaylors-ru") - { - settings[GuildSettings.Language.Name] = "ru"; - } - } - - public async Task GetSettings(Snowflake guildId, CancellationToken ct = default) - { - return (await GetData(guildId, ct)).Settings; - } - - public ICollection GetGuildIds() - { - return _datas.Keys; - } - - public bool UnloadGuildData(Snowflake id) - { - return _datas.TryRemove(id, out _); - } -} diff --git a/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs b/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs deleted file mode 100644 index 3170060..0000000 --- a/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs +++ /dev/null @@ -1,257 +0,0 @@ -using System.Text; -using System.Text.RegularExpressions; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; - -namespace TeamOctolings.Octobot.Services.Update; - -public sealed partial class MemberUpdateService : BackgroundService -{ - 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", - "Iceberg", "Iguana", "Kiwi", "Kite", "Lamb", "Lily", "Macaw", "Manatee", "Maple", "Mask", - "Nautilus", "Ostrich", "Octopus", "Pelican", "Puffin", "Pyramid", "Rattle", "Robin", "Rose", - "Salmon", "Seal", "Shark", "Sheep", "Snake", "Sonar", "Stump", "Sparrow", "Toaster", "Toucan", - "Torus", "Violet", "Vortex", "Vulture", "Wagon", "Whale", "Woodpecker", "Zebra", "Zigzag" - ]; - - private readonly AccessControlService _access; - private readonly IDiscordRestChannelAPI _channelApi; - private readonly IDiscordRestGuildAPI _guildApi; - private readonly GuildDataService _guildData; - private readonly ILogger _logger; - - public MemberUpdateService(AccessControlService access, IDiscordRestChannelAPI channelApi, - IDiscordRestGuildAPI guildApi, GuildDataService guildData, ILogger logger) - { - _access = access; - _channelApi = channelApi; - _guildApi = guildApi; - _guildData = guildData; - _logger = logger; - } - - 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 = _guildData.GetGuildIds(); - - tasks.AddRange(guildIds.Select(async id => - { - var tickResult = await TickMemberDatasAsync(id, ct); - _logger.LogResult(tickResult, $"Error in member data update for guild {id}."); - })); - - await Task.WhenAll(tasks); - tasks.Clear(); - } - } - - private async Task TickMemberDatasAsync(Snowflake guildId, CancellationToken ct = default) - { - var guildData = await _guildData.GetData(guildId, ct); - var defaultRole = GuildSettings.DefaultRole.Get(guildData.Settings); - var failedResults = new List(); - var memberDatas = guildData.MemberData.Values.ToArray(); - foreach (var data in memberDatas) - { - var tickResult = await TickMemberDataAsync(guildId, guildData, defaultRole, data, ct); - failedResults.AddIfFailed(tickResult); - } - - return failedResults.AggregateErrors(); - } - - private async Task TickMemberDataAsync(Snowflake guildId, GuildData guildData, Snowflake defaultRole, - MemberData data, - CancellationToken ct = default) - { - var failedResults = new List(); - var id = data.Id.ToSnowflake(); - - var autoUnbanResult = await TryAutoUnbanAsync(guildId, id, data, ct); - failedResults.AddIfFailed(autoUnbanResult); - - var guildMemberResult = await _guildApi.GetGuildMemberAsync(guildId, id, ct); - if (!guildMemberResult.IsDefined(out var guildMember)) - { - return failedResults.AggregateErrors(); - } - - var interactionResult - = await _access.CheckInteractionsAsync(guildId, null, id, "Update", ct); - if (!interactionResult.IsSuccess) - { - return ResultExtensions.FromError(interactionResult); - } - - var canInteract = interactionResult.Entity is null; - - if (data.MutedUntil is null) - { - data.Roles = guildMember.Roles.ToList().ConvertAll(r => r.Value); - } - - if (!guildMember.User.IsDefined(out var user)) - { - failedResults.AddIfFailed(new ArgumentNullError(nameof(guildMember.User))); - return failedResults.AggregateErrors(); - } - - for (var i = data.Reminders.Count - 1; i >= 0; i--) - { - var reminderTickResult = await TickReminderAsync(data.Reminders[i], user, data, guildId, ct); - failedResults.AddIfFailed(reminderTickResult); - } - - if (!canInteract) - { - return Result.Success; - } - - var autoUnmuteResult = await TryAutoUnmuteAsync(guildId, id, data, ct); - failedResults.AddIfFailed(autoUnmuteResult); - - if (!defaultRole.Empty() && !data.Roles.Contains(defaultRole.Value)) - { - var addResult = await _guildApi.AddGuildMemberRoleAsync( - guildId, id, defaultRole, ct: ct); - failedResults.AddIfFailed(addResult); - } - - if (GuildSettings.RenameHoistedUsers.Get(guildData.Settings)) - { - var filterResult = await FilterNicknameAsync(guildId, user, guildMember, ct); - failedResults.AddIfFailed(filterResult); - } - - return failedResults.AggregateErrors(); - } - - private async Task TryAutoUnbanAsync( - Snowflake guildId, Snowflake id, MemberData data, CancellationToken ct = default) - { - if (data.BannedUntil is null || DateTimeOffset.UtcNow <= data.BannedUntil) - { - return Result.Success; - } - - var existingBanResult = await _guildApi.GetGuildBanAsync(guildId, id, ct); - if (!existingBanResult.IsDefined()) - { - data.BannedUntil = null; - return Result.Success; - } - - var unbanResult = await _guildApi.RemoveGuildBanAsync( - guildId, id, Messages.PunishmentExpired.EncodeHeader(), ct); - if (unbanResult.IsSuccess) - { - data.BannedUntil = null; - } - - return unbanResult; - } - - private async Task TryAutoUnmuteAsync( - Snowflake guildId, Snowflake id, MemberData data, CancellationToken ct = default) - { - if (data.MutedUntil is null || DateTimeOffset.UtcNow <= data.MutedUntil) - { - return Result.Success; - } - - var unmuteResult = await _guildApi.ModifyGuildMemberAsync( - guildId, id, roles: data.Roles.ConvertAll(r => r.ToSnowflake()), - reason: Messages.PunishmentExpired.EncodeHeader(), ct: ct); - if (unmuteResult.IsSuccess) - { - data.MutedUntil = null; - } - - return unmuteResult; - } - - private async Task FilterNicknameAsync(Snowflake guildId, IUser user, IGuildMember member, - CancellationToken ct = default) - { - var currentNickname = member.Nickname.IsDefined(out var nickname) - ? nickname - : user.GlobalName.OrDefault(user.Username); - var characterList = currentNickname.ToList(); - var usernameChanged = false; - foreach (var character in currentNickname) - { - if (IllegalChars().IsMatch(character.ToString())) - { - characterList.Remove(character); - usernameChanged = true; - continue; - } - - break; - } - - if (!usernameChanged) - { - return Result.Success; - } - - var newNickname = string.Concat(characterList.ToArray()); - - return await _guildApi.ModifyGuildMemberAsync( - guildId, user.ID, - !string.IsNullOrWhiteSpace(newNickname) - ? newNickname - : GenericNicknames[Random.Shared.Next(GenericNicknames.Length)], - ct: ct); - } - - [GeneratedRegex("[^0-9A-Za-zА-Яа-яЁё]")] - private static partial Regex IllegalChars(); - - private async Task TickReminderAsync(Reminder reminder, IUser user, MemberData data, Snowflake guildId, - CancellationToken ct = default) - { - if (DateTimeOffset.UtcNow < reminder.At) - { - return Result.Success; - } - - var builder = new StringBuilder() - .AppendLine(MarkdownExtensions.Quote(reminder.Text)) - .AppendBulletPointLine(string.Format(Messages.DescriptionActionJumpToMessage, - $"https://discord.com/channels/{guildId.Value}/{reminder.ChannelId}/{reminder.MessageId}")); - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.Reminder, user.GetTag()), user) - .WithDescription(builder.ToString()) - .WithColour(ColorsList.Magenta) - .Build(); - - var messageResult = await _channelApi.CreateMessageWithEmbedResultAsync( - reminder.ChannelId.ToSnowflake(), Mention.User(user), embedResult: embed, ct: ct); - if (!messageResult.IsSuccess) - { - return ResultExtensions.FromError(messageResult); - } - - data.Reminders.Remove(reminder); - return Result.Success; - } -} diff --git a/TeamOctolings.Octobot/Services/Update/ScheduledEventUpdateService.cs b/TeamOctolings.Octobot/Services/Update/ScheduledEventUpdateService.cs deleted file mode 100644 index 389a6a8..0000000 --- a/TeamOctolings.Octobot/Services/Update/ScheduledEventUpdateService.cs +++ /dev/null @@ -1,434 +0,0 @@ -using System.Text.Json.Nodes; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.API.Objects; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; - -namespace TeamOctolings.Octobot.Services.Update; - -public sealed class ScheduledEventUpdateService : BackgroundService -{ - private readonly IDiscordRestChannelAPI _channelApi; - private readonly IDiscordRestGuildScheduledEventAPI _eventApi; - private readonly GuildDataService _guildData; - private readonly ILogger _logger; - private readonly Utility _utility; - - public ScheduledEventUpdateService(IDiscordRestChannelAPI channelApi, IDiscordRestGuildScheduledEventAPI eventApi, - GuildDataService guildData, ILogger logger, Utility utility) - { - _channelApi = channelApi; - _eventApi = eventApi; - _guildData = guildData; - _logger = logger; - _utility = utility; - } - - protected override async Task ExecuteAsync(CancellationToken ct) - { - using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); - - while (await timer.WaitForNextTickAsync(ct)) - { - var guildIds = _guildData.GetGuildIds(); - foreach (var id in guildIds) - { - var tickResult = await TickScheduledEventsAsync(id, ct); - _logger.LogResult(tickResult, $"Error in scheduled events update for guild {id}."); - } - } - } - - private async Task TickScheduledEventsAsync(Snowflake guildId, CancellationToken ct = default) - { - var failedResults = new List(); - var data = await _guildData.GetData(guildId, ct); - var eventsResult = await _eventApi.ListScheduledEventsForGuildAsync(guildId, ct: ct); - if (!eventsResult.IsDefined(out var events)) - { - return ResultExtensions.FromError(eventsResult); - } - - SyncScheduledEvents(data, events); - - foreach (var storedEvent in data.ScheduledEvents.Values) - { - var scheduledEvent = TryGetScheduledEvent(events, storedEvent.Id); - if (!scheduledEvent.IsSuccess) - { - storedEvent.ScheduleOnStatusUpdated = true; - storedEvent.Status = storedEvent.ActualStartTime != null - ? GuildScheduledEventStatus.Completed - : GuildScheduledEventStatus.Canceled; - } - - if (!storedEvent.ScheduleOnStatusUpdated) - { - var tickResult = - await TickScheduledEventAsync(guildId, data, scheduledEvent.Entity, storedEvent, ct); - failedResults.AddIfFailed(tickResult); - continue; - } - - var statusUpdatedResponseResult = storedEvent.Status switch - { - GuildScheduledEventStatus.Scheduled => - await SendScheduledEventCreatedMessage(scheduledEvent.Entity, data.Settings, ct), - GuildScheduledEventStatus.Canceled => - await SendScheduledEventCancelledMessage(storedEvent, data, ct), - GuildScheduledEventStatus.Active => - await SendScheduledEventStartedMessage(scheduledEvent.Entity, data, ct), - GuildScheduledEventStatus.Completed => - await SendScheduledEventCompletedMessage(storedEvent, data, ct), - _ => new ArgumentOutOfRangeError(nameof(storedEvent.Status)) - }; - if (statusUpdatedResponseResult.IsSuccess) - { - storedEvent.ScheduleOnStatusUpdated = false; - } - - failedResults.AddIfFailed(statusUpdatedResponseResult); - } - - return failedResults.AggregateErrors(); - } - - private static void SyncScheduledEvents(GuildData data, IEnumerable events) - { - foreach (var @event in events) - { - if (!data.ScheduledEvents.TryGetValue(@event.ID.Value, out var eventData)) - { - data.ScheduledEvents.Add(@event.ID.Value, - new ScheduledEventData(@event.ID.Value, @event.Name, @event.ScheduledStartTime, @event.Status)); - continue; - } - - eventData.Name = @event.Name; - eventData.ScheduledStartTime = @event.ScheduledStartTime; - if (!eventData.ScheduleOnStatusUpdated) - { - eventData.ScheduleOnStatusUpdated = eventData.Status != @event.Status; - } - - eventData.Status = @event.Status; - } - } - - private static Result TryGetScheduledEvent(IEnumerable from, ulong id) - { - var filtered = from.Where(schEvent => schEvent.ID == id); - var filteredArray = filtered.ToArray(); - return filteredArray.Length > 0 - ? Result.FromSuccess(filteredArray.Single()) - : new NotFoundError(); - } - - private async Task TickScheduledEventAsync( - Snowflake guildId, GuildData data, IGuildScheduledEvent scheduledEvent, ScheduledEventData eventData, - CancellationToken ct = default) - { - if (GuildSettings.AutoStartEvents.Get(data.Settings) - && DateTimeOffset.UtcNow >= scheduledEvent.ScheduledStartTime - && scheduledEvent.Status is not GuildScheduledEventStatus.Active) - { - return await AutoStartEventAsync(guildId, scheduledEvent, ct); - } - - var offset = GuildSettings.EventEarlyNotificationOffset.Get(data.Settings); - if (offset == TimeSpan.Zero - || eventData.EarlyNotificationSent - || DateTimeOffset.UtcNow < scheduledEvent.ScheduledStartTime - offset) - { - return Result.Success; - } - - var sendResult = await SendEarlyEventNotificationAsync(scheduledEvent, data, ct); - if (sendResult.IsSuccess) - { - eventData.EarlyNotificationSent = true; - } - - return sendResult; - } - - private async Task AutoStartEventAsync( - Snowflake guildId, IGuildScheduledEvent scheduledEvent, CancellationToken ct = default) - { - return (Result)await _eventApi.ModifyGuildScheduledEventAsync( - guildId, scheduledEvent.ID, - status: GuildScheduledEventStatus.Active, ct: ct); - } - - /// - /// Handles sending a notification, mentioning the if one is - /// set, - /// when a scheduled event is created - /// in a guild's if one is set. - /// - /// The scheduled event that has just been created. - /// The settings of the guild containing the scheduled event. - /// 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) - { - if (GuildSettings.EventNotificationChannel.Get(settings).Empty()) - { - return Result.Success; - } - - if (!scheduledEvent.Creator.IsDefined(out var creator)) - { - return new ArgumentNullError(nameof(scheduledEvent.Creator)); - } - - var eventDescription = scheduledEvent.Description.IsDefined(out var description) - ? description - : string.Empty; - var embedDescriptionResult = scheduledEvent.EntityType switch - { - GuildScheduledEventEntityType.StageInstance or GuildScheduledEventEntityType.Voice => - GetLocalEventCreatedEmbedDescription(scheduledEvent, eventDescription), - GuildScheduledEventEntityType.External => GetExternalScheduledEventCreatedEmbedDescription( - scheduledEvent, eventDescription), - _ => new ArgumentOutOfRangeError(nameof(scheduledEvent.EntityType)) - }; - - if (!embedDescriptionResult.IsDefined(out var embedDescription)) - { - return ResultExtensions.FromError(embedDescriptionResult); - } - - var embed = new EmbedBuilder() - .WithSmallTitle(string.Format(Messages.EventCreatedTitle, creator.GetTag()), creator) - .WithTitle(Markdown.Sanitize(scheduledEvent.Name)) - .WithDescription(embedDescription) - .WithEventCover(scheduledEvent.ID, scheduledEvent.Image) - .WithCurrentTimestamp() - .WithColour(ColorsList.White) - .Build(); - - var roleMention = !GuildSettings.EventNotificationRole.Get(settings).Empty() - ? Mention.Role(GuildSettings.EventNotificationRole.Get(settings)) - : string.Empty; - - var button = new ButtonComponent( - ButtonComponentStyle.Link, - Messages.ButtonOpenEventInfo, - new PartialEmoji(Name: "\ud83d\udccb"), // 'CLIPBOARD' (U+1F4CB) - URL: $"https://discord.com/events/{scheduledEvent.GuildID}/{scheduledEvent.ID}" - ); - - return await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.EventNotificationChannel.Get(settings), roleMention, embedResult: embed, - components: new[] { new ActionRowComponent([button]) }, ct: ct); - } - - private static Result GetExternalScheduledEventCreatedEmbedDescription( - IGuildScheduledEvent scheduledEvent, string eventDescription) - { - var dataResult = scheduledEvent.TryGetExternalEventData(out var endTime, out var location); - if (!dataResult.IsSuccess) - { - return Result.FromError(dataResult); - } - - return $"{eventDescription}\n\n{Markdown.BlockQuote( - string.Format( - Messages.DescriptionExternalEventCreated, - Markdown.Timestamp(scheduledEvent.ScheduledStartTime), - Markdown.Timestamp(endTime), - Markdown.InlineCode(location ?? string.Empty) - ))}"; - } - - private static Result GetLocalEventCreatedEmbedDescription( - IGuildScheduledEvent scheduledEvent, string eventDescription) - { - if (scheduledEvent.ChannelID is null) - { - return new ArgumentNullError(nameof(scheduledEvent.ChannelID)); - } - - return $"{eventDescription}\n\n{Markdown.BlockQuote( - string.Format( - Messages.DescriptionLocalEventCreated, - Markdown.Timestamp(scheduledEvent.ScheduledStartTime), - Mention.Channel(scheduledEvent.ChannelID.Value) - ))}"; - } - - /// - /// 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. - /// - /// The scheduled event that is about to start, has started or completed. - /// The data for the guild containing the scheduled event. - /// The cancellation token for this operation - /// A reminder/notification sending result which may or may not have succeeded. - private async Task SendScheduledEventStartedMessage( - IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct = default) - { - data.ScheduledEvents[scheduledEvent.ID.Value].ActualStartTime = DateTimeOffset.UtcNow; - - if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) - { - return Result.Success; - } - - var embedDescriptionResult = scheduledEvent.EntityType switch - { - GuildScheduledEventEntityType.StageInstance or GuildScheduledEventEntityType.Voice => - GetLocalEventStartedEmbedDescription(scheduledEvent), - GuildScheduledEventEntityType.External => GetExternalEventStartedEmbedDescription(scheduledEvent), - _ => new ArgumentOutOfRangeError(nameof(scheduledEvent.EntityType)) - }; - - var contentResult = await _utility.GetEventNotificationMentions( - scheduledEvent, data, ct); - if (!contentResult.IsDefined(out var content)) - { - return ResultExtensions.FromError(contentResult); - } - - if (!embedDescriptionResult.IsDefined(out var embedDescription)) - { - return ResultExtensions.FromError(embedDescriptionResult); - } - - var startedEmbed = new EmbedBuilder() - .WithTitle(string.Format(Messages.EventStarted, Markdown.Sanitize(scheduledEvent.Name))) - .WithDescription(embedDescription) - .WithColour(ColorsList.Green) - .WithCurrentTimestamp() - .Build(); - - return await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.EventNotificationChannel.Get(data.Settings), - content, embedResult: startedEmbed, ct: ct); - } - - private async Task SendScheduledEventCompletedMessage(ScheduledEventData eventData, GuildData data, - CancellationToken ct = default) - { - if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) - { - data.ScheduledEvents.Remove(eventData.Id); - return Result.Success; - } - - var completedEmbed = new EmbedBuilder() - .WithTitle(string.Format(Messages.EventCompleted, Markdown.Sanitize(eventData.Name))) - .WithDescription( - string.Format( - Messages.EventDuration, - DateTimeOffset.UtcNow.Subtract( - eventData.ActualStartTime - ?? eventData.ScheduledStartTime).ToString())) - .WithColour(ColorsList.Black) - .WithCurrentTimestamp() - .Build(); - - var createResult = await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.EventNotificationChannel.Get(data.Settings), - embedResult: completedEmbed, ct: ct); - if (createResult.IsSuccess) - { - data.ScheduledEvents.Remove(eventData.Id); - } - - return createResult; - } - - private async Task SendScheduledEventCancelledMessage(ScheduledEventData eventData, GuildData data, - CancellationToken ct = default) - { - if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) - { - data.ScheduledEvents.Remove(eventData.Id); - return Result.Success; - } - - var embed = new EmbedBuilder() - .WithSmallTitle(string.Format(Messages.EventCancelled, Markdown.Sanitize(eventData.Name))) - .WithDescription(":(") - .WithColour(ColorsList.Red) - .WithCurrentTimestamp() - .Build(); - - var createResult = await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.EventNotificationChannel.Get(data.Settings), embedResult: embed, ct: ct); - if (createResult.IsSuccess) - { - data.ScheduledEvents.Remove(eventData.Id); - } - - return createResult; - } - - private static Result GetLocalEventStartedEmbedDescription(IGuildScheduledEvent scheduledEvent) - { - if (scheduledEvent.ChannelID is null) - { - return new ArgumentNullError(nameof(scheduledEvent.ChannelID)); - } - - return string.Format( - Messages.DescriptionLocalEventStarted, - Mention.Channel(scheduledEvent.ChannelID.Value) - ); - } - - private static Result GetExternalEventStartedEmbedDescription(IGuildScheduledEvent scheduledEvent) - { - var dataResult = scheduledEvent.TryGetExternalEventData(out var endTime, out var location); - if (!dataResult.IsSuccess) - { - return Result.FromError(dataResult); - } - - return string.Format( - Messages.DescriptionExternalEventStarted, - Markdown.InlineCode(location ?? string.Empty), - Markdown.Timestamp(endTime) - ); - } - - private async Task SendEarlyEventNotificationAsync( - IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct = default) - { - if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) - { - return Result.Success; - } - - var contentResult = await _utility.GetEventNotificationMentions( - scheduledEvent, data, ct); - if (!contentResult.IsDefined(out var content)) - { - return ResultExtensions.FromError(contentResult); - } - - var earlyResult = new EmbedBuilder() - .WithDescription( - string.Format(Messages.EventEarlyNotification, Markdown.Sanitize(scheduledEvent.Name), - Markdown.Timestamp(scheduledEvent.ScheduledStartTime, TimestampStyle.RelativeTime))) - .WithColour(ColorsList.Default) - .Build(); - - return await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.EventNotificationChannel.Get(data.Settings), - content, - embedResult: earlyResult, ct: ct); - } -} diff --git a/TeamOctolings.Octobot/Services/Update/SongUpdateService.cs b/TeamOctolings.Octobot/Services/Update/SongUpdateService.cs deleted file mode 100644 index 8eaa4c2..0000000 --- a/TeamOctolings.Octobot/Services/Update/SongUpdateService.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Microsoft.Extensions.Hosting; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Gateway.Commands; -using Remora.Discord.API.Objects; -using Remora.Discord.Gateway; - -namespace TeamOctolings.Octobot.Services.Update; - -public sealed class SongUpdateService : BackgroundService -{ - private static readonly (string Author, string Name, TimeSpan Duration)[] SongList = - [ - ("Yoko & the Gold Bazookas", "Rockagilly Blues", new TimeSpan(0, 2, 52)), - ("Deep Cut", "Big Betrayal", new TimeSpan(0, 5, 55)), - ("Squid Sisters", "Tomorrow's Nostalgia Today", new TimeSpan(0, 3, 7)), - ("Deep Cut", "Anarchy Rainbow", new TimeSpan(0, 3, 20)), - ("Squid Sisters feat. Ian BGM", "Liquid Sunshine", new TimeSpan(0, 2, 37)), - ("Damp Socks feat. Off the Hook", "Candy-Coated Rocks", new TimeSpan(0, 2, 58)), - ("H2Whoa", "Aquasonic", new TimeSpan(0, 2, 51)), - ("Yoko & the Gold Bazookas", "Ska-BLAM", new TimeSpan(0, 2, 57)), - ("Off the Hook", "Muck Warfare", new TimeSpan(0, 3, 20)), - ("Off the Hook", "Acid Hues", new TimeSpan(0, 3, 15)), - ("Off the Hook", "Shark Bytes", new TimeSpan(0, 3, 34)), - ("Squid Sisters", "Calamari Inkantation", new TimeSpan(0, 2, 14)), - ("Squid Sisters", "Ink Me Up", new TimeSpan(0, 2, 13)), - ("Chirpy Chips", "No Quarters", new TimeSpan(0, 2, 36)), - ("Chirpy Chips", "Shellfie", new TimeSpan(0, 2, 1)), - ("Dedf1sh", "#11 above", new TimeSpan(0, 2, 10)), - ("Callie", "Bomb Rush Blush", new TimeSpan(0, 2, 18)), - ("Turquoise October", "Octoling Rendezvous", new TimeSpan(0, 1, 57)), - ("Damp Socks feat. Off the Hook", "Tentacle to the Metal", new TimeSpan(0, 2, 51)), - ("Off the Hook feat. Dedf1sh", "Spectrum Obligato ~ Ebb & Flow (Out of Order)", new TimeSpan(0, 4, 30)), - ("Dedf1sh feat. Off the Hook", "#47 onward", new TimeSpan(0, 4, 40)), - ("Free Association", "EchΘ Θnslaught", new TimeSpan(0, 2, 52)), - ("Off the Hook", "Short Order", new TimeSpan(0, 3, 36)), - ("Deep Cut", "Fins in the Air", new TimeSpan(0, 3, 1)) - ]; - - private static readonly (string Author, string Name, TimeSpan Duration)[] SpecialSongList = - [ - ("Squid Sisters", "Maritime Memory", new TimeSpan(0, 2, 47)) - ]; - - private readonly List _activityList = [new("with Remora.Discord", ActivityType.Game)]; - - private readonly DiscordGatewayClient _client; - private readonly GuildDataService _guildData; - - private uint _nextSongIndex; - - public SongUpdateService(DiscordGatewayClient client, GuildDataService guildData) - { - _client = client; - _guildData = guildData; - } - - protected override async Task ExecuteAsync(CancellationToken ct) - { - while (_guildData.GetGuildIds().Count is 0) - { - await Task.Delay(TimeSpan.FromSeconds(5), ct); - } - - while (!ct.IsCancellationRequested) - { - var nextSong = NextSong(); - _activityList[0] = new Activity($"{nextSong.Name} / {nextSong.Author}", - ActivityType.Listening); - _client.SubmitCommand( - new UpdatePresence( - UserStatus.Online, false, DateTimeOffset.UtcNow, _activityList)); - - await Task.Delay(nextSong.Duration, ct); - } - } - - private (string Author, string Name, TimeSpan Duration) NextSong() - { - var today = DateTime.Today; - // Discontinuation of Online Services for Nintendo Wii U - if (today.Day is 8 or 9 && today.Month is 4) - { - return SpecialSongList[0]; // Maritime Memory / Squid Sisters - } - - var nextSong = SongList[_nextSongIndex]; - _nextSongIndex++; - if (_nextSongIndex >= SongList.Length) - { - _nextSongIndex = 0; - } - - return nextSong; - } -} diff --git a/TeamOctolings.Octobot/TeamOctolings.Octobot.csproj b/TeamOctolings.Octobot/TeamOctolings.Octobot.csproj deleted file mode 100644 index b67eaf8..0000000 --- a/TeamOctolings.Octobot/TeamOctolings.Octobot.csproj +++ /dev/null @@ -1,46 +0,0 @@ - - - - Exe - net9.0 - enable - enable - 2.0.0 - Octobot - Octol1ttle, mctaylors, neroduckale - AGPLv3 - https://github.com/TeamOctolings/Octobot - https://github.com/TeamOctolings/Octobot/blob/master/LICENSE - https://github.com/TeamOctolings/Octobot - github - TeamOctolings - en - A general-purpose Discord bot for moderation written in C# - ../docs/octobot.ico - false - - - - - - - - - - - - - - - - - - - ResXFileCodeGenerator - Messages.Designer.cs - - - - - - diff --git a/TeamOctolings.Octobot/Utility.cs b/TeamOctolings.Octobot/Utility.cs deleted file mode 100644 index a2f7aca..0000000 --- a/TeamOctolings.Octobot/Utility.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System.Drawing; -using System.Text; -using System.Text.Json.Nodes; -using Microsoft.Extensions.Logging; -using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.API.Objects; -using Remora.Discord.Extensions.Embeds; -using Remora.Discord.Extensions.Formatting; -using Remora.Rest.Core; -using Remora.Results; -using TeamOctolings.Octobot.Attributes; -using TeamOctolings.Octobot.Data; -using TeamOctolings.Octobot.Extensions; - -namespace TeamOctolings.Octobot; - -/// -/// Provides utility methods that cannot be transformed to extension methods because they require usage -/// of some Discord APIs. -/// -public sealed class Utility -{ - public static readonly AllowedMentions NoMentions = new( - Array.Empty(), Array.Empty(), Array.Empty()); - - private readonly IDiscordRestChannelAPI _channelApi; - private readonly IDiscordRestGuildScheduledEventAPI _eventApi; - private readonly IDiscordRestGuildAPI _guildApi; - - public Utility( - IDiscordRestChannelAPI channelApi, IDiscordRestGuildScheduledEventAPI eventApi, IDiscordRestGuildAPI guildApi) - { - _channelApi = channelApi; - _eventApi = eventApi; - _guildApi = guildApi; - } - - [StaticCallersOnly] - public static ILogger? StaticLogger { get; set; } - - /// - /// Gets the string mentioning the and event subscribers related to - /// a scheduled - /// event. - /// - /// - /// The scheduled event whose subscribers will be mentioned. - /// - /// The data of the guild containing the scheduled event. - /// The cancellation token for this operation. - /// A result containing the string which may or may not have succeeded. - public async Task> GetEventNotificationMentions( - IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct = default) - { - var builder = new StringBuilder(); - var role = GuildSettings.EventNotificationRole.Get(data.Settings); - var subscribersResult = await _eventApi.GetGuildScheduledEventUsersAsync( - scheduledEvent.GuildID, scheduledEvent.ID, ct: ct); - if (!subscribersResult.IsDefined(out var subscribers)) - { - return Result.FromError(subscribersResult); - } - - if (!role.Empty()) - { - builder.Append($"{Mention.Role(role)} "); - } - - builder = subscribers.Where(subscriber => - !data.GetOrCreateMemberData(subscriber.User.ID).Roles.Contains(role.Value)) - .Aggregate(builder, (current, subscriber) => current.Append($"{Mention.User(subscriber.User)} ")); - return builder.ToString(); - } - - /// - /// Logs an action in the and - /// . - /// - /// The guild configuration. - /// The ID of the channel where the action was executed. - /// The user who performed the action. - /// The title for the embed. - /// 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 - /// - /// The cancellation token for this operation. - /// A result which has succeeded. - public void LogAction( - 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; - } - - var logEmbed = new EmbedBuilder().WithSmallTitle(title, avatar) - .WithDescription(description) - .WithActionFooter(user) - .WithCurrentTimestamp() - .WithColour(color) - .Build(); - - // Not awaiting to reduce response time - if (isPublic && publicChannel != channelId) - { - _ = _channelApi.CreateMessageWithEmbedResultAsync( - publicChannel, embedResult: logEmbed, - ct: ct); - } - - if (privateChannel != publicChannel - && privateChannel != channelId) - { - _ = _channelApi.CreateMessageWithEmbedResultAsync( - privateChannel, embedResult: logEmbed, - ct: ct); - } - } - - public async Task> GetEmergencyFeedbackChannel(IGuild guild, GuildData data, CancellationToken ct = default) - { - var privateFeedback = GuildSettings.PrivateFeedbackChannel.Get(data.Settings); - if (!privateFeedback.Empty()) - { - return privateFeedback; - } - - var publicFeedback = GuildSettings.PublicFeedbackChannel.Get(data.Settings); - if (!publicFeedback.Empty()) - { - return publicFeedback; - } - - if (guild.SystemChannelID.AsOptional().IsDefined(out var systemChannel)) - { - return systemChannel; - } - - var channelsResult = await _guildApi.GetGuildChannelsAsync(guild.ID, ct); - - return channelsResult.IsDefined(out var channels) - ? channels[0].ID - : Result.FromError(channelsResult); - } -} diff --git a/assets/css/fonts.css b/assets/css/fonts.css new file mode 100644 index 0000000..01a61b0 --- /dev/null +++ b/assets/css/fonts.css @@ -0,0 +1,11 @@ +@font-face { + font-family: 'BlitzBold'; + font-weight: normal; + src: url(../woff2/BlitzBold.woff2); +} + +@font-face { + font-family: 'BlitzMain'; + font-weight: normal; + src: url(../woff2/BlitzMain.woff2); +} \ No newline at end of file diff --git a/assets/css/styles.css b/assets/css/styles.css new file mode 100644 index 0000000..42412b1 --- /dev/null +++ b/assets/css/styles.css @@ -0,0 +1,196 @@ +/* + Octobot for Discord. Made by mctaylors. + Inspired by splatoon3.ink. +*/ + +@import url(fonts.css); + +:root { + color: #eee; + background-color: #000; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOIAAADiBAMAAAChPgbkAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAqUExURRcXF0dwTBUVFRYWFgAAABoaGhkZGRUVFRgYGAAAAAAAAB4eHiQkJBISEkRDNy8AAAAOdFJOUxYAGBcDEwoMFQUBEQcOBwHpdgAAAjdJREFUeNrt271NBDEUReGnjUlGIoGICCKkbYIKqIASqIEm6IGMDiiBjtj5XXvGz9k9Cde5dfTFvo6Xc6TnacjP/Xd+7/TbuRi3r52b752bXw/5xedecaCRMdDIGGjkpQgjL0UYORZZ5FhkkVMRRU5FFDkXSeRcJJFLEUQuRRC5FjnkWuSQWxFDbkUMeS1SyGuRQhZFCFkUIWRZZJBlkUFWRQRZFRFkXSSQdZFA7ooAclcEkPuiHrkv6pGHohx5KMqRx6IaeSyqkY2iGNkoipGtohbZKmqRzaIU2SxKke2iEtkuKpFJUYhMikJkVtQhs6IOmRZlyLQoQ+ZFFTIvqpCdogjZKYqQvaIGGW80Mh5pZJxoZBhppJFGGmmkkUYaaaSRRhpppJFGGmmkkUYaaeR/Rp5pZHzQyPikkXFHI2OgkTHQyBho5KUII8f3RxY5Flnk9MaKIqciipzfkUnkXCSRy1s5iFyKIHLdA3DItcght80DhtyKGPK666CQ1yKFLLYrELIoQshyn8MgyyKDrDZICLIqIsh6Z0Ug6yKB3G3JAOSuCCD3ezk9cl/UIw+bQDnyUJQjj7tHNfJYVCMb204xslEUI1v7VS2yVdQimxtdKbJZlCLbO2Qlsl1UIpOttRCZFIXIbE+uQ2ZFHTLdzMuQaVGGzP8FqJB5UYXs/H0QITtFEbL3v0OD7BU1yPihkXFDIwNHhpFGGmmkkUYaaaSRRhpppJFGGmmkkUYaaaSRRhoJIgNH/gHUGcb6oO4YfwAAAABJRU5ErkJggg=="); + font-family: BlitzMain, sans-serif; +} + +a, a:visited { + color: chartreuse; +} + +a:hover { + color: aquamarine; +} + +a:active { + color: darkcyan; +} + +a.alternative { + text-decoration: none; +} + +.highlight { + font-family: BlitzBold, sans-serif; + font-size: 32px; +} + +.header { + font-size: 24px; + padding: 16px; + position: fixed; + width: calc(100% - 48px); + z-index: 10; +} + +.header > .left { + float: left; +} + +.header > .right { + float: right; +} + +.header > .left img { + margin: 0 8px; + height: 64px; +} + +.header > .right .social img { + height: 32px; + width: 32px; + border: #999 1px solid; + border-radius: 16px; + background-color: #0009; + padding: 8px; + transition: 200ms; +} + +.header > .right .social img:hover { + border-color: #eee; +} + +.content { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 32px 64px; + padding: 100px 16px 80px; +} + +.content > .card { + mask-image: url("../svg/card-header.svg"); + mask-size: 2000px auto; + mask-position: top; + background-image: url("../png/tapes-transparent.png"); + background-size: contain; + width: 480px; + min-height: 520px; + padding: 48px 16px 8px; + border-radius: 16px; +} + +.content > .card.first { + background-color: #1bbeab; /* Splatoon 3 TurquoisePink Alpha */ + rotate: -2deg; +} + +.content > .card.second { + background-color: #c43a6e; /* Splatoon 3 TurquoisePink Bravo */ + rotate: 2deg; +} + +.content > .card * { + text-align: left; +} + +.content > .card span { + line-height: 1em; + filter: drop-shadow(1px 1px #000); +} + +.content > .card > .title { + margin: 8px 0; +} + +.content > .card > .title > * { + vertical-align: middle; +} + +.content > .card > .title > span { + font-size: 24px; +} + +.content > .card > .title > img { + height: 32px; + width: 32px; +} + +.content > .card > .frame { + padding: 4px 8px 8px; + border-radius: 8px; + background-color: #0009; + backdrop-filter: blur(4px); +} + +.content > .card > .frame > ul { + padding: 0 0 0 24px; +} + +.invite { + margin-top: 8px; + padding: 12px 0; + width: 100%; + font-family: BlitzBold, sans-serif; + font-size: 20px; + color: white; + background-color: #4d5058; + border-radius: 4px; + border: 0; + display: flex; + justify-content: center; + gap: 8px; + transition: 200ms; +} + +.invite:hover { + background-color: #6d6f78; + cursor: pointer; +} + +.invite:active { + background-color: #80848e; +} + +.invite > img { + height: 24px; + width: 24px; +} + +.invite > span { + filter: none !important; +} + +.invite > * { + vertical-align: middle; +} + +.footer { + position: fixed; + left: 0; + bottom: 0; + width: 100%; + padding: 8px; + color: #999; + font-size: 14px; + text-align: center; + background-color: #0009; + backdrop-filter: blur(4px); +} + +.footer img { + vertical-align: sub; +} + +.splatoon { + height: 24px; + filter: brightness(75%); +} \ No newline at end of file diff --git a/docs/octobot.ico b/assets/ico/octobot.ico similarity index 100% rename from docs/octobot.ico rename to assets/ico/octobot.ico diff --git a/assets/png/mem-cake-mole.png b/assets/png/mem-cake-mole.png new file mode 100644 index 0000000..6c7c244 Binary files /dev/null and b/assets/png/mem-cake-mole.png differ diff --git a/assets/png/mem-cake-octoling.png b/assets/png/mem-cake-octoling.png new file mode 100644 index 0000000..ca882d6 Binary files /dev/null and b/assets/png/mem-cake-octoling.png differ diff --git a/assets/png/mem-cake-sardinium.png b/assets/png/mem-cake-sardinium.png new file mode 100644 index 0000000..ecf358b Binary files /dev/null and b/assets/png/mem-cake-sardinium.png differ diff --git a/assets/png/octo.png b/assets/png/octo.png new file mode 100644 index 0000000..a5a7b5e Binary files /dev/null and b/assets/png/octo.png differ diff --git a/assets/png/octobot-web-logo.png b/assets/png/octobot-web-logo.png new file mode 100644 index 0000000..4f8aa19 Binary files /dev/null and b/assets/png/octobot-web-logo.png differ diff --git a/assets/png/tapes-transparent.png b/assets/png/tapes-transparent.png new file mode 100644 index 0000000..6e10758 Binary files /dev/null and b/assets/png/tapes-transparent.png differ diff --git a/assets/svg/add-circle-white.svg b/assets/svg/add-circle-white.svg new file mode 100644 index 0000000..0ee4d6e --- /dev/null +++ b/assets/svg/add-circle-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/svg/card-header.svg b/assets/svg/card-header.svg new file mode 100644 index 0000000..5dad4bf --- /dev/null +++ b/assets/svg/card-header.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/github-mark-white.svg b/assets/svg/github-mark-white.svg new file mode 100644 index 0000000..d5e6491 --- /dev/null +++ b/assets/svg/github-mark-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/svg/splatoon.svg b/assets/svg/splatoon.svg new file mode 100644 index 0000000..75094c7 --- /dev/null +++ b/assets/svg/splatoon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/woff2/BlitzBold.woff2 b/assets/woff2/BlitzBold.woff2 new file mode 100644 index 0000000..9756e0c Binary files /dev/null and b/assets/woff2/BlitzBold.woff2 differ diff --git a/assets/woff2/BlitzMain.woff2 b/assets/woff2/BlitzMain.woff2 new file mode 100644 index 0000000..59feb45 Binary files /dev/null and b/assets/woff2/BlitzMain.woff2 differ diff --git a/compose.example.yaml b/compose.example.yaml deleted file mode 100644 index 522281f..0000000 --- a/compose.example.yaml +++ /dev/null @@ -1,17 +0,0 @@ -services: - octobot: - container_name: octobot - build: - context: . - args: - - PUBLISH_OPTIONS - environment: - - BOT_TOKEN - volumes: - - guild-data:/Octobot/GuildData - - logs:/Octobot/Logs - restart: unless-stopped - -volumes: - guild-data: - logs: diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md deleted file mode 100644 index 0b5e0cd..0000000 --- a/docs/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,128 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement via the "Report Content" feature or via email at -l1ttleofficial@outlook.com. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md deleted file mode 100644 index dc5a793..0000000 --- a/docs/CONTRIBUTING.md +++ /dev/null @@ -1,68 +0,0 @@ -# Contributing Guidelines - -Thank you for showing interest in the development of Octobot. We aim to provide a good collaborating environment for -everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. -Before starting, please read our [Code of Conduct](CODE_OF_CONDUCT.md) - -## Reporting bugs - -A **bug** is a situation in which there is something clearly wrong with the bot. Examples of applicable bug reports are: - -- The bot doesn't reply to a command -- The bot sends the same message twice -- The bot takes a long time to a respond if I use this specific command -- An embed the bot sent has incorrect information in it - -To track bug reports, we primarily use GitHub **issues**. When opening an issue, please keep in mind the following: - -- Before opening the issue, please search for any similar existing issues using the text search bar and the issue - labels. This includes both open and closed issues (we may have already fixed something, but the fix hasn't yet been - released). -- When opening the issue, please fill out as much of the issue template as you can. In particular, please make sure to - include console output and screenshots as much as possible. -- We may ask you for follow-up information to reproduce or debug the problem. Please look out for this and provide - follow-up info if we request it. - -## Submitting pull requests - -While pull requests from unaffiliated contributors are welcome, please note that the core team *may* be focused on -internal issues that haven't been published to the issue tracker yet. Reviewing PRs is done on a best-effort basis, so -please be aware that it may take a while before a core maintainer gets around to review your change. - -The [issue tracker](https://github.com/TeamOctolings/Octobot/issues) should provide plenty of issues to start with. -Make sure to check that an issue you're planning to resolve does not already have people working on it and that there -are no PRs associated with it - -In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't -seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the -correct direction on how to address it. - -If you'd like to propose a subjective change to one of the UI/UX aspects of the bot, or there is a bigger task you'd -like to work on, but there is no corresponding issue yet for it, **please open an issue first** to avoid wasted effort. - -Aside from the above, below is a brief checklist of things to watch out when you're preparing your code changes: - -- Make sure you're comfortable with the principles of object-oriented programming, the syntax of C\# and your - development environment. -- Make sure you are familiar with [git](https://git-scm.com/) - and [the pull request workflow](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests). -- Please do not make code changes via the GitHub web interface. -- Please make sure your development environment respects the .editorconfig file present in the repository. Our code - style differs from most C\# projects and is closer to something you see in Java projects. -- Please test your changes. We expect most new features and bugfixes to be tested in an environment similar to - production. - -After you're done with your changes and you wish to open the PR, please observe the following recommendations: - -- Please submit the pull request from - a [topic branch](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows#_topic_branch) (not `master`), and - keep the *Allow edits from maintainers* check box selected, so that we can push fixes to your PR if necessary. -- Please avoid pushing untested or incomplete code. -- Please do not force-push or rebase unless we ask you to. -- Please do not merge `master` continually if there are no conflicts to resolve. We will do this for you when the change - is ready for merge. - -We are highly committed to quality when it comes to Octobot. This means that contributions from less experienced -community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never -conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please -consider our comments and requests a learning experience. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index ccc3b83..0000000 --- a/docs/README.md +++ /dev/null @@ -1,47 +0,0 @@ -

- Octobot banner -

- - - - - -Veemo! I'm a general-purpose bot for moderation (formerly known as Boyfriend) written by [Team Octolings](https://github.com/TeamOctolings) in C# and Remora.Discord - -## Features - -* Banning, muting, kicking, etc. -* Reminding you about something if you wish -* Reminding everyone about that new event you made -* Renaming those annoying self-hoisting members -* Log everything from joining the server to deleting messages -* Listen to Inkantation! - -*...a-a-and more!* - -## Building Octobot - -Check out the Octobot's Wiki for details. - -| [Windows](https://github.com/TeamOctolings/Octobot/wiki/Installing-Windows) | [Linux/macOS](https://github.com/TeamOctolings/Octobot/wiki/Installing-Unix) | -| --- | --- | - -## Contributing - -When it comes to contributing to the project, the two main things you can do to help out are reporting issues and -submitting pull requests. Please refer to the [contributing guidelines](CONTRIBUTING.md) to understand how to help in -the most effective way possible. - -## Special Thanks - -![JetBrains Logo (Main) logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg) - -[JetBrains](https://www.jetbrains.com/), creators of [ReSharper](https://www.jetbrains.com/resharper) -and [Rider](https://www.jetbrains.com/rider), supports Octobot with one of -their [Open Source Licenses](https://jb.gg/OpenSourceSupport). -Rider is the recommended IDE when working with Octobot, and everyone on the Octobot team uses it. -Additionally, ReSharper command-line tools made by JetBrains are used for status checks on pull requests to ensure code -quality even when not using ReSharper or Rider. - -# -Not an official Splatoon™ product. We are in no way affiliated with or endorsed by Nintendo Company, or other rightsholders. diff --git a/docs/octobot-banner.png b/docs/octobot-banner.png deleted file mode 100644 index 2ab5f5b..0000000 Binary files a/docs/octobot-banner.png and /dev/null differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..acd199b --- /dev/null +++ b/index.html @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + Octobot for Discord + + +
+
+ Octobot Web logo +
+
+ +
+
+
+
+
+ Octobot icon + Veemo! +
+
+ I'm a general-purpose Discord bot for moderation written by Team Octolings in C# and Remora.Discord! +
+
+ Mem Cake (Sardinium) + Features +
+
+
    +
  • Banning, muting, kicking, etc.
  • +
  • Reminding you about something if you wish
  • +
  • Reminding everyone about that new event you made
  • +
  • Renaming those annoying self-hoisting members
  • +
  • Log everything from joining the server to deleting messages
  • +
  • Listen to Inkantation!
  • + ...a-a-and more! +
+
+ + + +
+
+
+ Mem Cake (Rival Octoling) + Bug Report / Feature Request +
+
+ If you find some bug or want some new feature in Octobot, you can always use the Issues menu in our GitHub repository. + +
+
+ Mem Cake (Mole) + Building Octobot +
+
+ Want to make your own Octobot with, for example, even more features? Then, Octobot's Wiki is at your service! + +
+
+
+ + +