diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..ee8e124
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,532 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = tab
+insert_final_newline = true
+max_line_length = 120
+tab_width = 4
+ij_continuation_indent_size = 8
+ij_formatter_off_tag = @formatter:off
+ij_formatter_on_tag = @formatter:on
+ij_formatter_tags_enabled = false
+ij_smart_tabs = false
+ij_visual_guides = none
+ij_wrap_on_typing = false
+
+[*.java]
+ij_java_align_consecutive_assignments = false
+ij_java_align_consecutive_variable_declarations = false
+ij_java_align_group_field_declarations = false
+ij_java_align_multiline_annotation_parameters = false
+ij_java_align_multiline_array_initializer_expression = false
+ij_java_align_multiline_assignment = true
+ij_java_align_multiline_binary_operation = false
+ij_java_align_multiline_chained_methods = false
+ij_java_align_multiline_extends_list = false
+ij_java_align_multiline_for = false
+ij_java_align_multiline_method_parentheses = false
+ij_java_align_multiline_parameters = false
+ij_java_align_multiline_parameters_in_calls = false
+ij_java_align_multiline_parenthesized_expression = true
+ij_java_align_multiline_records = true
+ij_java_align_multiline_resources = false
+ij_java_align_multiline_ternary_operation = true
+ij_java_align_multiline_text_blocks = false
+ij_java_align_multiline_throws_list = false
+ij_java_align_subsequent_simple_methods = false
+ij_java_align_throws_keyword = true
+ij_java_annotation_parameter_wrap = off
+ij_java_array_initializer_new_line_after_left_brace = true
+ij_java_array_initializer_right_brace_on_new_line = true
+ij_java_array_initializer_wrap = on_every_item
+ij_java_assert_statement_colon_on_next_line = true
+ij_java_assert_statement_wrap = normal
+ij_java_assignment_wrap = normal
+ij_java_binary_operation_sign_on_next_line = false
+ij_java_binary_operation_wrap = off
+ij_java_blank_lines_after_anonymous_class_header = 0
+ij_java_blank_lines_after_class_header = 1
+ij_java_blank_lines_after_imports = 1
+ij_java_blank_lines_after_package = 1
+ij_java_blank_lines_around_class = 1
+ij_java_blank_lines_around_field = 0
+ij_java_blank_lines_around_field_in_interface = 0
+ij_java_blank_lines_around_initializer = 0
+ij_java_blank_lines_around_method = 1
+ij_java_blank_lines_around_method_in_interface = 1
+ij_java_blank_lines_before_class_end = 1
+ij_java_blank_lines_before_imports = 1
+ij_java_blank_lines_before_method_body = 0
+ij_java_blank_lines_before_package = 0
+ij_java_block_brace_style = end_of_line
+ij_java_block_comment_add_space = false
+ij_java_block_comment_at_first_column = true
+ij_java_builder_methods = none
+ij_java_call_parameters_new_line_after_left_paren = true
+ij_java_call_parameters_right_paren_on_new_line = true
+ij_java_call_parameters_wrap = normal
+ij_java_case_statement_on_separate_line = true
+ij_java_catch_on_new_line = false
+ij_java_class_annotation_wrap = split_into_lines
+ij_java_class_brace_style = end_of_line
+ij_java_class_count_to_use_import_on_demand = 5
+ij_java_class_names_in_javadoc = 1
+ij_java_do_not_indent_top_level_class_members = false
+ij_java_do_not_wrap_after_single_annotation = false
+ij_java_do_while_brace_force = never
+ij_java_doc_add_blank_line_after_description = true
+ij_java_doc_add_blank_line_after_param_comments = false
+ij_java_doc_add_blank_line_after_return = false
+ij_java_doc_add_p_tag_on_empty_lines = true
+ij_java_doc_align_exception_comments = false
+ij_java_doc_align_param_comments = false
+ij_java_doc_do_not_wrap_if_one_line = true
+ij_java_doc_enable_formatting = true
+ij_java_doc_enable_leading_asterisks = true
+ij_java_doc_indent_on_continuation = false
+ij_java_doc_keep_empty_lines = true
+ij_java_doc_keep_empty_parameter_tag = true
+ij_java_doc_keep_empty_return_tag = true
+ij_java_doc_keep_empty_throws_tag = true
+ij_java_doc_keep_invalid_tags = true
+ij_java_doc_param_description_on_new_line = false
+ij_java_doc_preserve_line_breaks = true
+ij_java_doc_use_throws_not_exception_tag = true
+ij_java_else_on_new_line = false
+ij_java_entity_dd_suffix = EJB
+ij_java_entity_eb_suffix = Bean
+ij_java_entity_hi_suffix = Home
+ij_java_entity_lhi_prefix = Local
+ij_java_entity_lhi_suffix = Home
+ij_java_entity_li_prefix = Local
+ij_java_entity_pk_class = java.lang.String
+ij_java_entity_vo_suffix = VO
+ij_java_enum_constants_wrap = on_every_item
+ij_java_extends_keyword_wrap = normal
+ij_java_extends_list_wrap = off
+ij_java_field_annotation_wrap = normal
+ij_java_finally_on_new_line = false
+ij_java_for_brace_force = if_multiline
+ij_java_for_statement_new_line_after_left_paren = true
+ij_java_for_statement_right_paren_on_new_line = true
+ij_java_for_statement_wrap = on_every_item
+ij_java_generate_final_locals = false
+ij_java_generate_final_parameters = false
+ij_java_if_brace_force = never
+ij_java_imports_layout = *,|,javax.**,java.**,|,$*
+ij_java_indent_case_from_switch = true
+ij_java_insert_inner_class_imports = false
+ij_java_insert_override_annotation = true
+ij_java_keep_blank_lines_before_right_brace = 2
+ij_java_keep_blank_lines_between_package_declaration_and_header = 2
+ij_java_keep_blank_lines_in_code = 2
+ij_java_keep_blank_lines_in_declarations = 2
+ij_java_keep_builder_methods_indents = false
+ij_java_keep_control_statement_in_one_line = false
+ij_java_keep_first_column_comment = false
+ij_java_keep_indents_on_empty_lines = true
+ij_java_keep_line_breaks = false
+ij_java_keep_multiple_expressions_in_one_line = false
+ij_java_keep_simple_blocks_in_one_line = false
+ij_java_keep_simple_classes_in_one_line = false
+ij_java_keep_simple_lambdas_in_one_line = false
+ij_java_keep_simple_methods_in_one_line = false
+ij_java_label_indent_absolute = false
+ij_java_label_indent_size = 0
+ij_java_lambda_brace_style = end_of_line
+ij_java_layout_static_imports_separately = true
+ij_java_line_comment_add_space = false
+ij_java_line_comment_at_first_column = true
+ij_java_message_dd_suffix = EJB
+ij_java_message_eb_suffix = Bean
+ij_java_method_annotation_wrap = split_into_lines
+ij_java_method_brace_style = end_of_line
+ij_java_method_call_chain_wrap = normal
+ij_java_method_parameters_new_line_after_left_paren = true
+ij_java_method_parameters_right_paren_on_new_line = true
+ij_java_method_parameters_wrap = normal
+ij_java_modifier_list_wrap = false
+ij_java_names_count_to_use_import_on_demand = 3
+ij_java_new_line_after_lparen_in_record_header = false
+ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.*
+ij_java_parameter_annotation_wrap = normal
+ij_java_parentheses_expression_new_line_after_left_paren = false
+ij_java_parentheses_expression_right_paren_on_new_line = false
+ij_java_place_assignment_sign_on_next_line = false
+ij_java_prefer_longer_names = true
+ij_java_prefer_parameters_wrap = false
+ij_java_record_components_wrap = normal
+ij_java_repeat_synchronized = true
+ij_java_replace_instanceof_and_cast = false
+ij_java_replace_null_check = true
+ij_java_replace_sum_lambda_with_method_ref = true
+ij_java_resource_list_new_line_after_left_paren = true
+ij_java_resource_list_right_paren_on_new_line = true
+ij_java_resource_list_wrap = normal
+ij_java_rparen_on_new_line_in_record_header = false
+ij_java_session_dd_suffix = EJB
+ij_java_session_eb_suffix = Bean
+ij_java_session_hi_suffix = Home
+ij_java_session_lhi_prefix = Local
+ij_java_session_lhi_suffix = Home
+ij_java_session_li_prefix = Local
+ij_java_session_si_suffix = Service
+ij_java_space_after_closing_angle_bracket_in_type_argument = false
+ij_java_space_after_colon = true
+ij_java_space_after_comma = true
+ij_java_space_after_comma_in_type_arguments = true
+ij_java_space_after_for_semicolon = true
+ij_java_space_after_quest = true
+ij_java_space_after_type_cast = false
+ij_java_space_before_annotation_array_initializer_left_brace = false
+ij_java_space_before_annotation_parameter_list = false
+ij_java_space_before_array_initializer_left_brace = false
+ij_java_space_before_catch_keyword = true
+ij_java_space_before_catch_left_brace = true
+ij_java_space_before_catch_parentheses = true
+ij_java_space_before_class_left_brace = true
+ij_java_space_before_colon = true
+ij_java_space_before_colon_in_foreach = true
+ij_java_space_before_comma = false
+ij_java_space_before_do_left_brace = true
+ij_java_space_before_else_keyword = true
+ij_java_space_before_else_left_brace = true
+ij_java_space_before_finally_keyword = true
+ij_java_space_before_finally_left_brace = true
+ij_java_space_before_for_left_brace = true
+ij_java_space_before_for_parentheses = true
+ij_java_space_before_for_semicolon = false
+ij_java_space_before_if_left_brace = true
+ij_java_space_before_if_parentheses = true
+ij_java_space_before_method_call_parentheses = false
+ij_java_space_before_method_left_brace = true
+ij_java_space_before_method_parentheses = true
+ij_java_space_before_opening_angle_bracket_in_type_parameter = true
+ij_java_space_before_quest = true
+ij_java_space_before_switch_left_brace = true
+ij_java_space_before_switch_parentheses = true
+ij_java_space_before_synchronized_left_brace = true
+ij_java_space_before_synchronized_parentheses = true
+ij_java_space_before_try_left_brace = true
+ij_java_space_before_try_parentheses = true
+ij_java_space_before_type_parameter_list = false
+ij_java_space_before_while_keyword = true
+ij_java_space_before_while_left_brace = true
+ij_java_space_before_while_parentheses = true
+ij_java_space_inside_one_line_enum_braces = false
+ij_java_space_within_empty_array_initializer_braces = false
+ij_java_space_within_empty_method_call_parentheses = false
+ij_java_space_within_empty_method_parentheses = false
+ij_java_spaces_around_additive_operators = true
+ij_java_spaces_around_assignment_operators = true
+ij_java_spaces_around_bitwise_operators = true
+ij_java_spaces_around_equality_operators = true
+ij_java_spaces_around_lambda_arrow = true
+ij_java_spaces_around_logical_operators = true
+ij_java_spaces_around_method_ref_dbl_colon = false
+ij_java_spaces_around_multiplicative_operators = true
+ij_java_spaces_around_relational_operators = true
+ij_java_spaces_around_shift_operators = true
+ij_java_spaces_around_type_bounds_in_type_parameters = true
+ij_java_spaces_around_unary_operator = false
+ij_java_spaces_within_angle_brackets = false
+ij_java_spaces_within_annotation_parentheses = false
+ij_java_spaces_within_array_initializer_braces = false
+ij_java_spaces_within_braces = false
+ij_java_spaces_within_brackets = false
+ij_java_spaces_within_cast_parentheses = false
+ij_java_spaces_within_catch_parentheses = false
+ij_java_spaces_within_for_parentheses = false
+ij_java_spaces_within_if_parentheses = false
+ij_java_spaces_within_method_call_parentheses = false
+ij_java_spaces_within_method_parentheses = false
+ij_java_spaces_within_parentheses = false
+ij_java_spaces_within_record_header = false
+ij_java_spaces_within_switch_parentheses = false
+ij_java_spaces_within_synchronized_parentheses = false
+ij_java_spaces_within_try_parentheses = false
+ij_java_spaces_within_while_parentheses = false
+ij_java_special_else_if_treatment = true
+ij_java_subclass_name_suffix = Impl
+ij_java_ternary_operation_signs_on_next_line = false
+ij_java_ternary_operation_wrap = on_every_item
+ij_java_test_name_suffix = Test
+ij_java_throws_keyword_wrap = normal
+ij_java_throws_list_wrap = off
+ij_java_use_external_annotations = false
+ij_java_use_fq_class_names = false
+ij_java_use_relative_indents = true
+ij_java_use_single_class_imports = true
+ij_java_variable_annotation_wrap = on_every_item
+ij_java_visibility = public
+ij_java_while_brace_force = never
+ij_java_while_on_new_line = true
+ij_java_wrap_comments = false
+ij_java_wrap_first_method_in_call_chain = true
+ij_java_wrap_long_lines = false
+
+[.editorconfig]
+ij_editorconfig_align_group_field_declarations = false
+ij_editorconfig_space_after_colon = false
+ij_editorconfig_space_after_comma = true
+ij_editorconfig_space_before_colon = false
+ij_editorconfig_space_before_comma = false
+ij_editorconfig_spaces_around_assignment_operators = true
+
+[{*.ant,*.fxml,*.isc,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.tld,*.wadl,*.wsdd,*.wsdl,*.xjb,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
+ij_smart_tabs = true
+ij_xml_align_attributes = false
+ij_xml_align_text = false
+ij_xml_attribute_wrap = off
+ij_xml_block_comment_add_space = false
+ij_xml_block_comment_at_first_column = true
+ij_xml_keep_blank_lines = 2
+ij_xml_keep_indents_on_empty_lines = true
+ij_xml_keep_line_breaks = true
+ij_xml_keep_line_breaks_in_text = true
+ij_xml_keep_whitespaces = false
+ij_xml_keep_whitespaces_around_cdata = preserve
+ij_xml_keep_whitespaces_inside_cdata = false
+ij_xml_line_comment_at_first_column = true
+ij_xml_space_after_tag_name = false
+ij_xml_space_around_equals_in_attribute = false
+ij_xml_space_inside_empty_tag = true
+ij_xml_text_wrap = normal
+ij_xml_use_custom_settings = false
+
+[{*.gant,*.groovy,*.gson,*.gy}]
+ij_smart_tabs = true
+ij_groovy_align_group_field_declarations = false
+ij_groovy_align_multiline_array_initializer_expression = false
+ij_groovy_align_multiline_assignment = false
+ij_groovy_align_multiline_binary_operation = false
+ij_groovy_align_multiline_chained_methods = false
+ij_groovy_align_multiline_extends_list = false
+ij_groovy_align_multiline_for = true
+ij_groovy_align_multiline_list_or_map = true
+ij_groovy_align_multiline_method_parentheses = false
+ij_groovy_align_multiline_parameters = true
+ij_groovy_align_multiline_parameters_in_calls = false
+ij_groovy_align_multiline_resources = true
+ij_groovy_align_multiline_ternary_operation = false
+ij_groovy_align_multiline_throws_list = false
+ij_groovy_align_named_args_in_map = true
+ij_groovy_align_throws_keyword = true
+ij_groovy_array_initializer_new_line_after_left_brace = true
+ij_groovy_array_initializer_right_brace_on_new_line = true
+ij_groovy_array_initializer_wrap = on_every_item
+ij_groovy_assert_statement_wrap = off
+ij_groovy_assignment_wrap = off
+ij_groovy_binary_operation_wrap = off
+ij_groovy_blank_lines_after_class_header = 1
+ij_groovy_blank_lines_after_imports = 1
+ij_groovy_blank_lines_after_package = 1
+ij_groovy_blank_lines_around_class = 1
+ij_groovy_blank_lines_around_field = 0
+ij_groovy_blank_lines_around_field_in_interface = 0
+ij_groovy_blank_lines_around_method = 1
+ij_groovy_blank_lines_around_method_in_interface = 1
+ij_groovy_blank_lines_before_imports = 1
+ij_groovy_blank_lines_before_method_body = 0
+ij_groovy_blank_lines_before_package = 0
+ij_groovy_block_brace_style = end_of_line
+ij_groovy_block_comment_add_space = false
+ij_groovy_block_comment_at_first_column = true
+ij_groovy_call_parameters_new_line_after_left_paren = true
+ij_groovy_call_parameters_right_paren_on_new_line = true
+ij_groovy_call_parameters_wrap = on_every_item
+ij_groovy_catch_on_new_line = false
+ij_groovy_class_annotation_wrap = split_into_lines
+ij_groovy_class_brace_style = end_of_line
+ij_groovy_class_count_to_use_import_on_demand = 5
+ij_groovy_do_while_brace_force = never
+ij_groovy_else_on_new_line = true
+ij_groovy_enum_constants_wrap = on_every_item
+ij_groovy_extends_keyword_wrap = normal
+ij_groovy_extends_list_wrap = off
+ij_groovy_field_annotation_wrap = split_into_lines
+ij_groovy_finally_on_new_line = false
+ij_groovy_for_brace_force = never
+ij_groovy_for_statement_new_line_after_left_paren = true
+ij_groovy_for_statement_right_paren_on_new_line = true
+ij_groovy_for_statement_wrap = on_every_item
+ij_groovy_if_brace_force = never
+ij_groovy_import_annotation_wrap = 2
+ij_groovy_imports_layout = *,|,javax.**,java.**,|,$*
+ij_groovy_indent_case_from_switch = true
+ij_groovy_indent_label_blocks = true
+ij_groovy_insert_inner_class_imports = false
+ij_groovy_keep_blank_lines_before_right_brace = 2
+ij_groovy_keep_blank_lines_in_code = 2
+ij_groovy_keep_blank_lines_in_declarations = 2
+ij_groovy_keep_control_statement_in_one_line = true
+ij_groovy_keep_first_column_comment = true
+ij_groovy_keep_indents_on_empty_lines = true
+ij_groovy_keep_line_breaks = true
+ij_groovy_keep_multiple_expressions_in_one_line = false
+ij_groovy_keep_simple_blocks_in_one_line = false
+ij_groovy_keep_simple_classes_in_one_line = true
+ij_groovy_keep_simple_lambdas_in_one_line = true
+ij_groovy_keep_simple_methods_in_one_line = true
+ij_groovy_label_indent_absolute = false
+ij_groovy_label_indent_size = 0
+ij_groovy_lambda_brace_style = end_of_line
+ij_groovy_layout_static_imports_separately = true
+ij_groovy_line_comment_add_space = false
+ij_groovy_line_comment_at_first_column = true
+ij_groovy_method_annotation_wrap = split_into_lines
+ij_groovy_method_brace_style = end_of_line
+ij_groovy_method_call_chain_wrap = off
+ij_groovy_method_parameters_new_line_after_left_paren = true
+ij_groovy_method_parameters_right_paren_on_new_line = true
+ij_groovy_method_parameters_wrap = on_every_item
+ij_groovy_modifier_list_wrap = false
+ij_groovy_names_count_to_use_import_on_demand = 3
+ij_groovy_packages_to_use_import_on_demand = java.awt.*,javax.swing.*
+ij_groovy_parameter_annotation_wrap = normal
+ij_groovy_parentheses_expression_new_line_after_left_paren = false
+ij_groovy_parentheses_expression_right_paren_on_new_line = false
+ij_groovy_prefer_parameters_wrap = false
+ij_groovy_resource_list_new_line_after_left_paren = true
+ij_groovy_resource_list_right_paren_on_new_line = true
+ij_groovy_resource_list_wrap = on_every_item
+ij_groovy_space_after_assert_separator = true
+ij_groovy_space_after_colon = true
+ij_groovy_space_after_comma = true
+ij_groovy_space_after_comma_in_type_arguments = true
+ij_groovy_space_after_for_semicolon = true
+ij_groovy_space_after_quest = true
+ij_groovy_space_after_type_cast = false
+ij_groovy_space_before_annotation_parameter_list = false
+ij_groovy_space_before_array_initializer_left_brace = false
+ij_groovy_space_before_assert_separator = false
+ij_groovy_space_before_catch_keyword = true
+ij_groovy_space_before_catch_left_brace = true
+ij_groovy_space_before_catch_parentheses = true
+ij_groovy_space_before_class_left_brace = true
+ij_groovy_space_before_closure_left_brace = true
+ij_groovy_space_before_colon = true
+ij_groovy_space_before_comma = false
+ij_groovy_space_before_do_left_brace = true
+ij_groovy_space_before_else_keyword = true
+ij_groovy_space_before_else_left_brace = true
+ij_groovy_space_before_finally_keyword = true
+ij_groovy_space_before_finally_left_brace = true
+ij_groovy_space_before_for_left_brace = true
+ij_groovy_space_before_for_parentheses = true
+ij_groovy_space_before_for_semicolon = false
+ij_groovy_space_before_if_left_brace = true
+ij_groovy_space_before_if_parentheses = true
+ij_groovy_space_before_method_call_parentheses = false
+ij_groovy_space_before_method_left_brace = true
+ij_groovy_space_before_method_parentheses = true
+ij_groovy_space_before_quest = true
+ij_groovy_space_before_record_parentheses = false
+ij_groovy_space_before_switch_left_brace = true
+ij_groovy_space_before_switch_parentheses = true
+ij_groovy_space_before_synchronized_left_brace = true
+ij_groovy_space_before_synchronized_parentheses = true
+ij_groovy_space_before_try_left_brace = true
+ij_groovy_space_before_try_parentheses = true
+ij_groovy_space_before_while_keyword = true
+ij_groovy_space_before_while_left_brace = true
+ij_groovy_space_before_while_parentheses = true
+ij_groovy_space_in_named_argument = true
+ij_groovy_space_in_named_argument_before_colon = false
+ij_groovy_space_within_empty_array_initializer_braces = false
+ij_groovy_space_within_empty_method_call_parentheses = false
+ij_groovy_spaces_around_additive_operators = true
+ij_groovy_spaces_around_assignment_operators = true
+ij_groovy_spaces_around_bitwise_operators = true
+ij_groovy_spaces_around_equality_operators = true
+ij_groovy_spaces_around_lambda_arrow = true
+ij_groovy_spaces_around_logical_operators = true
+ij_groovy_spaces_around_multiplicative_operators = true
+ij_groovy_spaces_around_regex_operators = true
+ij_groovy_spaces_around_relational_operators = true
+ij_groovy_spaces_around_shift_operators = true
+ij_groovy_spaces_within_annotation_parentheses = false
+ij_groovy_spaces_within_array_initializer_braces = false
+ij_groovy_spaces_within_braces = true
+ij_groovy_spaces_within_brackets = false
+ij_groovy_spaces_within_cast_parentheses = false
+ij_groovy_spaces_within_catch_parentheses = false
+ij_groovy_spaces_within_for_parentheses = false
+ij_groovy_spaces_within_gstring_injection_braces = false
+ij_groovy_spaces_within_if_parentheses = false
+ij_groovy_spaces_within_list_or_map = false
+ij_groovy_spaces_within_method_call_parentheses = false
+ij_groovy_spaces_within_method_parentheses = false
+ij_groovy_spaces_within_parentheses = false
+ij_groovy_spaces_within_switch_parentheses = false
+ij_groovy_spaces_within_synchronized_parentheses = false
+ij_groovy_spaces_within_try_parentheses = false
+ij_groovy_spaces_within_tuple_expression = false
+ij_groovy_spaces_within_while_parentheses = false
+ij_groovy_special_else_if_treatment = true
+ij_groovy_ternary_operation_wrap = normal
+ij_groovy_throws_keyword_wrap = normal
+ij_groovy_throws_list_wrap = off
+ij_groovy_use_flying_geese_braces = false
+ij_groovy_use_fq_class_names = false
+ij_groovy_use_fq_class_names_in_javadoc = true
+ij_groovy_use_relative_indents = false
+ij_groovy_use_single_class_imports = true
+ij_groovy_variable_annotation_wrap = normal
+ij_groovy_while_brace_force = never
+ij_groovy_while_on_new_line = false
+ij_groovy_wrap_chain_calls_after_dot = false
+ij_groovy_wrap_long_lines = false
+
+[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}]
+ij_smart_tabs = true
+ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
+ij_html_align_attributes = false
+ij_html_align_text = false
+ij_html_attribute_wrap = off
+ij_html_block_comment_add_space = false
+ij_html_block_comment_at_first_column = true
+ij_html_do_not_align_children_of_min_lines = 0
+ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p
+ij_html_do_not_indent_children_of_tags = none
+ij_html_enforce_quotes = false
+ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var
+ij_html_keep_blank_lines = 0
+ij_html_keep_indents_on_empty_lines = true
+ij_html_keep_line_breaks = false
+ij_html_keep_line_breaks_in_text = true
+ij_html_keep_whitespaces = false
+ij_html_keep_whitespaces_inside = span,pre,textarea
+ij_html_line_comment_at_first_column = true
+ij_html_new_line_after_last_attribute = never
+ij_html_new_line_before_first_attribute = never
+ij_html_quote_style = double
+ij_html_remove_new_line_before_tags = br
+ij_html_space_after_tag_name = false
+ij_html_space_around_equality_in_attribute = false
+ij_html_space_inside_empty_tag = true
+ij_html_text_wrap = off
+
+[{*.markdown,*.md}]
+ij_smart_tabs = true
+ij_markdown_force_one_space_after_blockquote_symbol = true
+ij_markdown_force_one_space_after_header_symbol = true
+ij_markdown_force_one_space_after_list_bullet = true
+ij_markdown_force_one_space_between_words = true
+ij_markdown_keep_indents_on_empty_lines = true
+ij_markdown_max_lines_around_block_elements = 1
+ij_markdown_max_lines_around_header = 1
+ij_markdown_max_lines_between_paragraphs = 1
+ij_markdown_min_lines_around_block_elements = 1
+ij_markdown_min_lines_around_header = 1
+ij_markdown_min_lines_between_paragraphs = 1
+
+[{*.properties,spring.handlers,spring.schemas}]
+ij_properties_align_group_field_declarations = false
+ij_properties_keep_blank_lines = true
+ij_properties_key_value_delimiter = equals
+ij_properties_spaces_around_key_value_delimiter = true
+
diff --git a/gradle.properties b/gradle.properties
index ad6e88f..f697b89 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,10 +1,10 @@
## Core
-VERSION = 0.4.1.3
+VERSION = 0.4.2.11
# dependencies
-libSpotbugsVersion = 4.5.0
+libSpotbugsVersion = 4.5.2
libMessivaVersion = 0.1.0.1
diff --git a/src/main/java/cc/sukazyo/cono/morny/GradleProjectConfigures.java b/src/main/java/cc/sukazyo/cono/morny/GradleProjectConfigures.java
index 9000285..4760921 100644
--- a/src/main/java/cc/sukazyo/cono/morny/GradleProjectConfigures.java
+++ b/src/main/java/cc/sukazyo/cono/morny/GradleProjectConfigures.java
@@ -4,6 +4,6 @@ package cc.sukazyo.cono.morny;
* the final field that will be updated by gradle automatically.
*/
public class GradleProjectConfigures {
- public static final String VERSION = "0.4.1.3";
- public static final long COMPILE_TIMESTAMP = 1639476313268L;
+ public static final String VERSION = "0.4.2.11";
+ public static final long COMPILE_TIMESTAMP = 1640595623685L;
}
diff --git a/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java b/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java
index c6fdf0d..06d7944 100644
--- a/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java
+++ b/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java
@@ -17,8 +17,14 @@ import static cc.sukazyo.cono.morny.Log.logger;
*/
public class MornyCoeur {
+ /** 当前程序的 Morny Coeur 实例 */
+ private static MornyCoeur INSTANCE;
+
+ /** 当前 Morny 的{@link MornyTrusted 信任验证机}实例 */
+ private final MornyTrusted trusted;
+
/** morny 的 bot 账户 */
- private static TelegramBot account;
+ private final TelegramBot account;
/**
* morny 的 bot 账户的用户名
*
@@ -28,36 +34,40 @@ public class MornyCoeur {
* 如果在登陆之前就定义了此字段,则登陆代码会验证登陆的 bot 的 username
* 是否与定义的 username 符合。如果不符合则会报错。
*/
- private static String username;
+ private final String username;
/**
* morny 的事件忽略前缀时间
*
* {@link cc.sukazyo.cono.morny.bot.event.OnUpdateTimestampOffsetLock}
* 会根据这里定义的时间戳取消掉比此时间更早的事件链
*/
- public static long latestEventTimestamp;
+ public long latestEventTimestamp;
/**
* morny 主程序启动时间
* 用于统计数据
*/
public static final long coeurStartTimestamp = System.currentTimeMillis();
+ private record LogInResult(TelegramBot account, String username) { }
+
/**
- * bot 启动入口,执行 bot 初始化
+ * 执行 bot 初始化
*
* @param botKey bot 的 telegram bot api token
* @param botUsername bot 的 username 限定。如果为 null 则表示不限定,
* 如果指定,则登录时会检查所登陆的 bot 的用户名是否与此相等
+ * @param master morny 实例所信任的主人的 id。用于初始化 {@link #trusted}
+ * @param trustedChat morny 实例所信任的群组的 id。用于初始化 {@link #trusted}
* @param latestEventTimestamp 事件处理器会处理事件的最早时间戳 ——
* 只有限定的 message 事件会受此影响。
* 单位为毫秒
*/
- public static void main (@Nonnull String botKey, @Nullable String botUsername, long latestEventTimestamp) {
-
- MornyCoeur.latestEventTimestamp = latestEventTimestamp;
-
- logger.info("System Starting");
+ private MornyCoeur (
+ @Nonnull String botKey, @Nullable String botUsername,
+ long master, long trustedChat, long latestEventTimestamp
+ ) {
+ this.latestEventTimestamp = latestEventTimestamp;
configureSafeExit();
logger.info("args key:\n " + botKey);
@@ -65,23 +75,57 @@ public class MornyCoeur {
logger.info("login as:\n " + botUsername);
}
- try { account = login(botKey); }
- catch (Exception e) { logger.error("Cannot login to bot/api. :\n " + e.getMessage()); System.exit(-1); }
+ try {
+ final LogInResult loginResult = login(botKey);
+ this.account = loginResult.account;
+ this.username = loginResult.username;
+ this.trusted = new MornyTrusted(master, trustedChat);
+ logger.info(String.format("""
+ trusted param set:
+ - master (id)
+ %d
+ - trusted chat (id)
+ %d""",
+ master, trustedChat
+ ));
+ }
+ catch (Exception e) {
+ RuntimeException ex = new RuntimeException("Cannot login to bot/api. :\n " + e.getMessage());
+ logger.error(ex.getMessage());
+ throw ex;
+ }
logger.info("Bot login succeed.");
- TrackerDataManager.init();
- EventListeners.registerAllListeners();
- account.setUpdatesListener(OnUpdate::onNormalUpdate);
-
- logger.info("System start complete");
-
+ }
+
+ /**
+ * 向外界暴露的 morny 初始化入口.
+ *
+ * 如果 morny 已经初始化,则不会进行初始化,抛出错误消息并直接退出方法。
+ *
+ * @see #MornyCoeur 程序初始化方法
+ */
+ public static void main (
+ @Nonnull String botKey, @Nullable String botUsername,
+ long master, long trustedChat, long latestEventTimestamp
+ ) {
+ if (INSTANCE == null) {
+ logger.info("System Starting");
+ INSTANCE = new MornyCoeur(botKey, botUsername, master, trustedChat, latestEventTimestamp);
+ TrackerDataManager.init();
+ EventListeners.registerAllListeners();
+ INSTANCE.account.setUpdatesListener(OnUpdate::onNormalUpdate);
+ logger.info("System start complete");
+ return;
+ }
+ logger.error("System already started coeur!!!");
}
/**
* 用于退出时进行缓存的任务处理等进行安全退出
*/
- private static void exitCleanup () {
+ private void exitCleanup () {
TrackerDataManager.DAEMON.interrupt();
TrackerDataManager.trackingLock.lock();
}
@@ -89,8 +133,8 @@ public class MornyCoeur {
/**
* 为程序在虚拟机上添加退出钩子
*/
- private static void configureSafeExit () {
- Runtime.getRuntime().addShutdownHook(new Thread(MornyCoeur::exitCleanup));
+ private void configureSafeExit () {
+ Runtime.getRuntime().addShutdownHook(new Thread(this::exitCleanup));
}
/**
@@ -104,18 +148,17 @@ public class MornyCoeur {
* @return 成功登录后的 {@link TelegramBot} 对象
*/
@Nonnull
- public static TelegramBot login (@Nonnull String key) {
+ private LogInResult login (@Nonnull String key) {
final TelegramBot account = new TelegramBot(key);
logger.info("Trying to login...");
for (int i = 1; i < 4; i++) {
if (i != 1) logger.info("retrying...");
try {
final String username = account.execute(new GetMe()).user().username();
- if (MornyCoeur.username != null && !MornyCoeur.username.equals(username))
- throw new RuntimeException("Required the bot @" + MornyCoeur.username + " but @" + username + " logged in!");
- else MornyCoeur.username = username;
+ if (this.username != null && !this.username.equals(username))
+ throw new RuntimeException("Required the bot @" + this.username + " but @" + username + " logged in!");
logger.info("Succeed login to @" + username);
- return account;
+ return new LogInResult(account, username);
} catch (Exception e) {
e.printStackTrace(System.out);
logger.error("login failed.");
@@ -129,8 +172,9 @@ public class MornyCoeur {
*
* @return {@link #account MornyCoeur.account}
*/
+ @Nonnull
public static TelegramBot getAccount () {
- return account;
+ return INSTANCE.account;
}
/**
@@ -138,8 +182,29 @@ public class MornyCoeur {
*
* @return {@link #username MornyCoeur.username}
*/
+ @Nonnull
public static String getUsername () {
- return username;
+ return INSTANCE.username;
+ }
+
+ /**
+ *
+ * 获取忽略时间点
+ *
+ * @return {@link #latestEventTimestamp MornyCoeur.latestEventTimestamp}
+ */
+ public static long getLatestEventTimestamp () {
+ return INSTANCE.latestEventTimestamp;
+ }
+
+ /**
+ * 获取 Morny 的{@link MornyTrusted 信任验证机}
+ *
+ * @return {@link #trusted MornyCoeur.trusted}
+ */
+ @Nonnull
+ public static MornyTrusted trustedInstance () {
+ return INSTANCE.trusted;
}
}
diff --git a/src/main/java/cc/sukazyo/cono/morny/MornyTrusted.java b/src/main/java/cc/sukazyo/cono/morny/MornyTrusted.java
index 6fa7883..f892b9d 100644
--- a/src/main/java/cc/sukazyo/cono/morny/MornyTrusted.java
+++ b/src/main/java/cc/sukazyo/cono/morny/MornyTrusted.java
@@ -12,7 +12,18 @@ public class MornyTrusted {
* 群聊id,其指向的群聊指示了哪个群的成员是受信任的
* @see #isTrusted(long) 受信检查
*/
- public static final long TRUSTED_CHAT_ID = -1001541451710L;
+ public final Long TRUSTED_CHAT_ID;
+
+ /**
+ * morny 的主人
+ * 这项值的对象总是会被认为是可信任的
+ */
+ public final long MASTER;
+
+ public MornyTrusted (long master, long trustedChatId) {
+ this.TRUSTED_CHAT_ID = trustedChatId;
+ this.MASTER = master;
+ }
/**
* 用于检查一个 telegram-user 是否受信任
@@ -24,7 +35,8 @@ public class MornyTrusted {
* @param userId 需要检查的用户的id
* @return 所传递的用户id对应的用户是否受信任
*/
- public static boolean isTrusted (long userId) {
+ public boolean isTrusted (long userId) {
+ if (userId == MASTER) return true;
final ChatMember chatMember = MornyCoeur.getAccount().execute(new GetChatMember(TRUSTED_CHAT_ID, userId)).chatMember();
return (
chatMember != null && (
diff --git a/src/main/java/cc/sukazyo/cono/morny/ServerMain.java b/src/main/java/cc/sukazyo/cono/morny/ServerMain.java
index 89147c3..fcbe489 100644
--- a/src/main/java/cc/sukazyo/cono/morny/ServerMain.java
+++ b/src/main/java/cc/sukazyo/cono/morny/ServerMain.java
@@ -35,6 +35,13 @@ public class ServerMain {
* 不要同时使用 {@code --no-hello},原因见下。
*
*
%s
core md5_hash:
@@ -118,14 +128,17 @@ public class OnCommandExecute extends EventListener {
compile timestamp:
- %d
- %s [UTC]
""",
- MornySystem.VERSION,
- MornySystem.getJarMd5(),
+ escapeHtmlTelegram(MornySystem.VERSION),
+ escapeHtmlTelegram(MornySystem.getJarMd5()),
GradleProjectConfigures.COMPILE_TIMESTAMP,
- CommonFormatUtils.formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)
+ escapeHtmlTelegram(formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0))
)
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
}
+ /**
+ * @since 0.4.1.2
+ */
private void onCommandRuntimeExec (@Nonnull Update event) {
MornyCoeur.getAccount().execute(new SendMessage(
event.message().chat().id(),
@@ -137,34 +150,52 @@ public class OnCommandExecute extends EventListener {
java runtime:
- %s
- %s
- memory:
+ vm memory:
- %d
/ %d
MB
morny version:
- %s
- %s
- %s [UTC]
- [%d
]
- continuous
+ continuous:
- %s
+ - [%d
]
+ - %s [UTC]
- [%d
]""",
// system
- System.getProperty("os.name"),
- System.getProperty("os.version"),
+ escapeHtmlTelegram(System.getProperty("os.name")),
+ escapeHtmlTelegram(System.getProperty("os.version")),
Runtime.getRuntime().availableProcessors(),
// java
- System.getProperty("java.vm.name"),
- System.getProperty("java.version"),
+ escapeHtmlTelegram(System.getProperty("java.vm.name")),
+ escapeHtmlTelegram(System.getProperty("java.version")),
// memory
Runtime.getRuntime().totalMemory() / 1024 / 1024,
Runtime.getRuntime().maxMemory() / 1024 / 1024,
// version
- MornySystem.VERSION,
- MornySystem.getJarMd5(),
- CommonFormatUtils.formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0),
+ escapeHtmlTelegram(MornySystem.VERSION),
+ escapeHtmlTelegram(MornySystem.getJarMd5()),
+ escapeHtmlTelegram(formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)),
GradleProjectConfigures.COMPILE_TIMESTAMP,
// continuous
- CommonFormatUtils.formatDuration(System.currentTimeMillis() - MornyCoeur.coeurStartTimestamp),
- System.currentTimeMillis() - MornyCoeur.coeurStartTimestamp
+ escapeHtmlTelegram(formatDuration(System.currentTimeMillis() - MornyCoeur.coeurStartTimestamp)),
+ System.currentTimeMillis() - MornyCoeur.coeurStartTimestamp,
+ escapeHtmlTelegram(formatDate(MornyCoeur.coeurStartTimestamp, 0)),
+ MornyCoeur.coeurStartTimestamp
+ )
+ ).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
+ }
+
+ private void onCommandJrrpExec (Update event) {
+ final double jrrp = MornyJrrp.getJrrpFromTelegramUser(event.message().from(), System.currentTimeMillis());
+ final String endChar = jrrp>70 ? "!" : jrrp>30 ? ";" : "...";
+ MornyCoeur.getAccount().execute(new SendMessage(
+ event.message().chat().id(),
+ String.format(
+ "%s 在(utc的)今天的运气指数是———— %.2f%%
%s",
+ event.message().from().id(),
+ escapeHtmlTelegram(event.message().from().firstName()),
+ jrrp, escapeHtmlTelegram(endChar)
)
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
}
diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.java b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.java
new file mode 100644
index 0000000..a4275a4
--- /dev/null
+++ b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnEventHackHandle.java
@@ -0,0 +1,144 @@
+package cc.sukazyo.cono.morny.bot.event;
+
+import cc.sukazyo.cono.morny.MornyCoeur;
+import cc.sukazyo.cono.morny.bot.api.EventListener;
+import cc.sukazyo.cono.morny.util.StringUtils;
+import com.google.gson.GsonBuilder;
+import com.pengrad.telegrambot.model.Update;
+import com.pengrad.telegrambot.model.request.ParseMode;
+import com.pengrad.telegrambot.request.SendMessage;
+
+import javax.annotation.Nonnull;
+import java.util.HashMap;
+import java.util.Map;
+
+import static cc.sukazyo.cono.morny.Log.logger;
+
+/**
+ * 事件劫持与序列化工具.
+ * @since 0.4.2.0
+ */
+public class OnEventHackHandle extends EventListener {
+
+ /** 事件劫持请求列表 */
+ private static final Map%s
",
+ StringUtils.escapeHtmlTelegram(new GsonBuilder().setPrettyPrinting().create().toJson(update))
+ )).parseMode(ParseMode.HTML).replyToMessageId((int)x.fromMessageId));
+ return true;
+ }
+
+ @Override
+ public boolean onMessage (@Nonnull Update update) {
+ return onEventHacked(update, update.message().chat().id(), update.message().from().id());
+ }
+
+ @Override
+ public boolean onEditedMessage (@Nonnull Update update) {
+ return onEventHacked(update, update.editedMessage().chat().id(), update.editedMessage().from().id());
+ }
+
+ @Override
+ public boolean onChannelPost (@Nonnull Update update) {
+ return onEventHacked(update, update.channelPost().chat().id(), update.channelPost().from().id());
+ }
+
+ @Override
+ public boolean onEditedChannelPost (@Nonnull Update update) {
+ return onEventHacked(update, update.editedChannelPost().chat().id(), update.editedChannelPost().from().id());
+ }
+
+ @Override
+ public boolean onInlineQuery (@Nonnull Update update) {
+ return onEventHacked(update, 0, update.inlineQuery().from().id());
+ }
+
+ @Override
+ public boolean onChosenInlineResult (@Nonnull Update update) {
+ return onEventHacked(update, 0, update.chosenInlineResult().from().id());
+ }
+
+ @Override
+ public boolean onCallbackQuery (@Nonnull Update update) {
+ return onEventHacked(update, 0, update.callbackQuery().from().id());
+ }
+
+ @Override
+ public boolean onShippingQuery (@Nonnull Update update) {
+ return onEventHacked(update, 0, update.shippingQuery().from().id());
+ }
+
+ @Override
+ public boolean onPreCheckoutQuery (@Nonnull Update update) {
+ return onEventHacked(update, 0, update.preCheckoutQuery().from().id());
+ }
+
+ @Override
+ public boolean onPoll (@Nonnull Update update) {
+ return onEventHacked(update, 0, 0);
+ }
+
+ @Override
+ public boolean onPollAnswer (@Nonnull Update update) {
+ return onEventHacked(update, 0, update.pollAnswer().user().id());
+ }
+
+ @Override
+ public boolean onMyChatMemberUpdated (@Nonnull Update update) {
+ return onEventHacked(update, update.myChatMember().chat().id(), update.myChatMember().from().id());
+ }
+
+ @Override
+ public boolean onChatMemberUpdated (@Nonnull Update update) {
+ return onEventHacked(update, update.chatMember().chat().id(), update.chatMember().from().id());
+ }
+
+ @Override
+ public boolean onChatJoinRequest (@Nonnull Update update) {
+ return onEventHacked(update, update.chatJoinRequest().chat().id(), update.chatJoinRequest().from().id());
+ }
+
+}
diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnInlineQuery.java b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnInlineQuery.java
index 6bf189e..1867887 100644
--- a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnInlineQuery.java
+++ b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnInlineQuery.java
@@ -8,9 +8,13 @@ import com.pengrad.telegrambot.model.request.InlineQueryResultArticle;
import com.pengrad.telegrambot.model.request.InputTextMessageContent;
import com.pengrad.telegrambot.model.request.ParseMode;
import com.pengrad.telegrambot.request.AnswerInlineQuery;
-import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nonnull;
/**
+ * telegram inlineQuery 功能的处理类,
+ * 也是一个 InlineQueryManager(还没做)
+ *
* @since 0.4.1.3
*/
public class OnInlineQuery extends EventListener {
@@ -19,7 +23,7 @@ public class OnInlineQuery extends EventListener {
* @since 0.4.1.3
*/
@Override
- public boolean onInlineQuery (@NotNull Update update) {
+ public boolean onInlineQuery (@Nonnull Update update) {
MornyCoeur.getAccount().execute(new AnswerInlineQuery(update.inlineQuery().id(), new InlineQueryResultArticle[]{
new InlineQueryResultArticle(
EncryptUtils.encryptByMD5(update.inlineQuery().query()),
diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnUpdateTimestampOffsetLock.java b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnUpdateTimestampOffsetLock.java
index 7130277..80eff7f 100644
--- a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnUpdateTimestampOffsetLock.java
+++ b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnUpdateTimestampOffsetLock.java
@@ -3,13 +3,54 @@ package cc.sukazyo.cono.morny.bot.event;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.bot.api.EventListener;
import com.pengrad.telegrambot.model.Update;
-import org.jetbrains.annotations.NotNull;
+import javax.annotation.Nonnull;
+
+/**
+ * 阻止 {@link MornyCoeur#latestEventTimestamp 指定时间} 之前的事件处理.
+ * + * 只支持以下事件 + *
%d
- username :
- - %s
""",
- userId, user.username()
+ - %d
""",
+ userId
));
+ if (user.username() == null) {
+ userInformation.append("\nusername : null");
+ } else {
+ userInformation.append(String.format(
+ """
+
+ username :
+ - %s
""",
+ escapeHtmlTelegram(user.username())
+ ));
+ }
if (user.firstName() == null) {
userInformation.append("\nfirstname : null");
} else {
@@ -75,7 +78,7 @@ public class GetUsernameAndId {
firstname :
- %s
""",
- user.firstName()
+ escapeHtmlTelegram(user.firstName())
));
}
if (user.lastName() == null) {
@@ -86,7 +89,7 @@ public class GetUsernameAndId {
lastname :
- %s
""",
- user.lastName()
+ escapeHtmlTelegram(user.lastName())
));
}
if (user.languageCode() != null) {
@@ -95,7 +98,7 @@ public class GetUsernameAndId {
language-code :
- %s
""",
- user.languageCode()
+ escapeHtmlTelegram(user.languageCode())
));
}
diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/event/on_commands/Ip186Query.java b/src/main/java/cc/sukazyo/cono/morny/bot/event/on_commands/Ip186Query.java
new file mode 100644
index 0000000..3c73c8a
--- /dev/null
+++ b/src/main/java/cc/sukazyo/cono/morny/bot/event/on_commands/Ip186Query.java
@@ -0,0 +1,52 @@
+package cc.sukazyo.cono.morny.bot.event.on_commands;
+
+import cc.sukazyo.cono.morny.MornyCoeur;
+import cc.sukazyo.cono.morny.bot.api.InputCommand;
+import cc.sukazyo.cono.morny.data.ip186.IP186QueryResponse;
+import cc.sukazyo.cono.morny.data.ip186.IP186QueryHandler;
+import com.pengrad.telegrambot.model.Update;
+import com.pengrad.telegrambot.model.request.ParseMode;
+import com.pengrad.telegrambot.request.SendMessage;
+
+import javax.annotation.Nonnull;
+
+import static cc.sukazyo.cono.morny.util.StringUtils.escapeHtmlTelegram;
+
+/**
+ * {@value IP186QueryHandler#SITE_URL} 查询的 telegram 命令前端
+ * @since 0.4.2.10
+ */
+public class Ip186Query {
+
+ public static void exec (@Nonnull Update event, @Nonnull InputCommand command) {
+
+ if (!command.hasArgs()) { MornyCoeur.getAccount().execute(new SendMessage(
+ event.message().chat().id(),
+ "[Unavailable] No ip defined."
+ ).replyToMessageId(event.message().messageId())); return; }
+
+ if (command.getArgs().length > 1) { MornyCoeur.getAccount().execute(new SendMessage(
+ event.message().chat().id(),
+ "[Unavailable] Too much arguments."
+ ).replyToMessageId(event.message().messageId())); return; }
+
+ try {
+ IP186QueryResponse response = switch (command.getCommand()) {
+ case "/ip" -> IP186QueryHandler.queryIp(command.getArgs()[0]);
+ case "/whois" -> IP186QueryHandler.queryWhois(command.getArgs()[0]);
+ default -> throw new IllegalArgumentException("Unknown 186-IP query method " + command.getCommand());
+ };
+ MornyCoeur.getAccount().execute(new SendMessage(
+ event.message().chat().id(),
+ escapeHtmlTelegram(response.url()) + "\n" + escapeHtmlTelegram(response.body()) + "
"
+ ).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId()));
+ } catch (Exception e) {
+ MornyCoeur.getAccount().execute(new SendMessage(
+ event.message().chat().id(),
+ "[Exception] in query:\n" + escapeHtmlTelegram(e.getMessage()) + "
"
+ ).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId()));
+ }
+
+ }
+
+}
diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/event/on_commands/Roll.java b/src/main/java/cc/sukazyo/cono/morny/bot/event/on_commands/Roll.java
new file mode 100644
index 0000000..4cb209a
--- /dev/null
+++ b/src/main/java/cc/sukazyo/cono/morny/bot/event/on_commands/Roll.java
@@ -0,0 +1,4 @@
+package cc.sukazyo.cono.morny.bot.event.on_commands;
+
+public class Roll {
+}
diff --git a/src/main/java/cc/sukazyo/cono/morny/data/MornyJrrp.java b/src/main/java/cc/sukazyo/cono/morny/data/MornyJrrp.java
new file mode 100644
index 0000000..2fd858e
--- /dev/null
+++ b/src/main/java/cc/sukazyo/cono/morny/data/MornyJrrp.java
@@ -0,0 +1,45 @@
+package cc.sukazyo.cono.morny.data;
+
+import cc.sukazyo.cono.morny.util.EncryptUtils;
+import com.pengrad.telegrambot.model.User;
+
+/**
+ * Morny 的 jrrp 运算类.
+ *
+ * @see #getJrrpFromTelegramUser(User,long)
+ * @see #calcJrrpXmomi(long,long)
+ * @since 0.4.2.9
+ */
+public class MornyJrrp {
+
+ /**
+ * 通过 telegram 用户和时间戳作为参数获取 jrrp.
+ *
+ * @see #calcJrrpXmomi 当前版本的实现算法 {@code Xmomi}
+ * @since 0.4.2.9
+ * @param user telegram 用户
+ * @param timestamp 时间戳
+ * @return 通过当前版本的算法计算出的用户 jrrp 值,取值为 {@code [0.00, 100.00]}
+ */
+ public static double getJrrpFromTelegramUser (User user, long timestamp) {
+ return calcJrrpXmomi(user.id(), timestamp / (1000 * 60 * 60 * 24)) * 100.0;
+ }
+
+ /**
+ * {@code Xmomi} 版本的 jrrp 算法.
+ *
+ * 算法规则为,将用户id与日期戳链接为 uid@daystamp
这样的字符串,
+ * 然后通过 MD5 计算出字符串的哈希值,取哈希值前4个字节,将其作为16进制数值表示法转换为取值为 {@code [0x0000, 0xffff]} 的数值,
+ * 得到的数值除以区间最大值 {@code 0xffff} 即可得到一个分布在 {@code [0.0, 1.0]} 之间的分布值,
+ * 这个分布值乘以 {@code 100.0},即为计算得到的 jrrp 数值。
+ *
+ * @since 0.4.2.9
+ * @param userId telegram 用户 uid
+ * @param dayStamp unix 时间戳转换为日期单位后的数值. 数值应该在转换前转换时区
+ * @return 算法得到的 jrrp 值,取值为 {@code [0.00. 100.00]}
+ */
+ public static double calcJrrpXmomi (long userId, long dayStamp) {
+ return (double)Long.parseLong(EncryptUtils.encryptByMD5(userId + "@" + dayStamp).substring(0, 4), 16) / (double)0xffff;
+ }
+
+}
diff --git a/src/main/java/cc/sukazyo/cono/morny/data/TelegramStickers.java b/src/main/java/cc/sukazyo/cono/morny/data/TelegramStickers.java
new file mode 100644
index 0000000..d478e05
--- /dev/null
+++ b/src/main/java/cc/sukazyo/cono/morny/data/TelegramStickers.java
@@ -0,0 +1,17 @@
+package cc.sukazyo.cono.morny.data;
+
+/**
+ * 存放 bot 使用到的贴纸
+ * @since 0.4.2.0
+ */
+public class TelegramStickers {
+
+ public static final String ID_ONLINE_STATUS_RETURN = "CAACAgEAAx0CW-CvvgAC5eBhhhODGRuu0pxKLwoQ3yMsowjviAACcycAAnj8xgVVU666si1utiIE";
+ public static final String ID_HELLO = "CAACAgEAAxkBAAMnYYYWKNXO4ibo9dlsmDctHhhV6fIAAqooAAJ4_MYFJJhrHS74xUAiBA";
+ public static final String ID_EXIT = "CAACAgEAAxkBAAMoYYYWt8UjvP0N405SAyvg2SQZmokAAkMiAAJ4_MYFw6yZLu06b-MiBA";
+ public static final String ID_403 = "CAACAgEAAxkBAAMqYYYa_7hpXH6hMOYMX4Nh8AVYd74AAnQnAAJ4_MYFRdmmsQKLDZgiBA";
+ public static final String ID_404 = "CAACAgEAAx0CSQh32gABA966YbRJpbmi2lCHINBDuo1DknSTsbsAAqUoAAJ4_MYFUa8SIaZriAojBA";
+ public static final String ID_WAITING = "CAACAgEAAx0CSQh32gABA-8DYbh7W2VhJ490ucfZMUMrgMR2FW4AAm4nAAJ4_MYFjx6zpxJPWsQjBA";
+ public static final String ID_SENT = "CAACAgEAAx0CSQh32gABA--zYbiyU_wOijEitp-0tSl_k7W6l3gAAgMmAAJ4_MYF4GrompjXPx4jBA";
+
+}
diff --git a/src/main/java/cc/sukazyo/cono/morny/data/ip186/IP186QueryHandler.java b/src/main/java/cc/sukazyo/cono/morny/data/ip186/IP186QueryHandler.java
new file mode 100644
index 0000000..c29d0b9
--- /dev/null
+++ b/src/main/java/cc/sukazyo/cono/morny/data/ip186/IP186QueryHandler.java
@@ -0,0 +1,75 @@
+package cc.sukazyo.cono.morny.data.ip186;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+/**
+ * 通过 {@value #SITE_URL} 进行 {@link #queryIp ip}/{@link #queryWhois whois} 数据查询的工具类
+ *
+ * @since 0.4.2.10
+ */
+public class IP186QueryHandler {
+
+ /**
+ * 请求所使用的 HTTP API 站点链接
+ * @since 0.4.2.10
+ */
+ public static final String SITE_URL = "https://ip.186526.xyz/";
+
+ /**
+ * 进行 {@link #queryIp ip 查询}时所使用的 API 参数.
+ * 目的使 API 直接返回原始数据
+ */
+ private static final String QUERY_IP_PARAM = "type=json&format=true";
+
+ /**
+ * 进行 {@link #queryWhois whois 查询}时所使用的 API 参数.
+ * 目的使 API 直接返回原始数据
+ */
+ private static final String QUERY_WHOIS_PARAM = "type=plain";
+
+ /** 请求时使用的 OkHttp 请求工具实例 */
+ private static final OkHttpClient httpClient = new OkHttpClient();
+
+ /**
+ * 通过 {@value #SITE_URL} 获取 ip 信息.
+ * @see #QUERY_IP_PARAM 发送请求时所使用的 API 参数
+ * @param ip 需要进行查询的 ip
+ * @return 查询结果。data 根据 {@value #SITE_URL} 的规则以 json 序列化
+ * @throws IOException 任何请求或解析错误
+ */
+ @Nonnull
+ public static IP186QueryResponse queryIp (String ip) throws IOException {
+ final String requestUrl = SITE_URL + ip;
+ return commonQuery(requestUrl, QUERY_IP_PARAM);
+ }
+
+ /**
+ * 通过 {@value #SITE_URL} 获取域名信息.
+ * @see #QUERY_WHOIS_PARAM 发送请求时所使用的 API 参数
+ * @param domain 需要进行查询的域名
+ * @return 查询结果。data 根据 {@value #SITE_URL} 的规则以 plain 序列化
+ * @throws IOException 任何请求或解析错误
+ */
+ @Nonnull
+ public static IP186QueryResponse queryWhois (String domain) throws IOException {
+ final String requestUrl = SITE_URL + "whois/" + domain;
+ return commonQuery(requestUrl, QUERY_WHOIS_PARAM);
+ }
+
+ @Nonnull
+ private static IP186QueryResponse commonQuery (String requestUrl, String queryIpParam) throws IOException {
+ Request request = new Request.Builder().url(requestUrl + "?" + queryIpParam).build();
+ try (Response response = httpClient.newCall(request).execute()) {
+ final ResponseBody body = response.body();
+ if (body == null) throw new IOException("Null body.");
+ return new IP186QueryResponse(requestUrl, body.string());
+ }
+ }
+
+}
diff --git a/src/main/java/cc/sukazyo/cono/morny/data/ip186/IP186QueryResponse.java b/src/main/java/cc/sukazyo/cono/morny/data/ip186/IP186QueryResponse.java
new file mode 100644
index 0000000..f30fb3d
--- /dev/null
+++ b/src/main/java/cc/sukazyo/cono/morny/data/ip186/IP186QueryResponse.java
@@ -0,0 +1,11 @@
+package cc.sukazyo.cono.morny.data.ip186;
+
+/**
+ * {@link IP186QueryHandler} 的请求结果数据的通用封装类.
+ *
+ * @since 0.4.2.10
+ * @param url 请求数据的人类可读的来源链接,并非api链接
+ * @param body API 传回的数据内容
+ */
+public record IP186QueryResponse(String url, String body) {
+}
diff --git a/src/main/java/cc/sukazyo/cono/morny/util/EncryptUtils.java b/src/main/java/cc/sukazyo/cono/morny/util/EncryptUtils.java
index 6d502f9..243389a 100644
--- a/src/main/java/cc/sukazyo/cono/morny/util/EncryptUtils.java
+++ b/src/main/java/cc/sukazyo/cono/morny/util/EncryptUtils.java
@@ -1,5 +1,6 @@
package cc.sukazyo.cono.morny.util;
+import javax.annotation.Nonnull;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -18,6 +19,7 @@ public class EncryptUtils {
/***
* 对指定的字符串进行MD5加密
*/
+ @Nonnull
public static String encryptByMD5(String originString) {
try {
//创建具有MD5算法的信息摘要
@@ -28,9 +30,8 @@ public class EncryptUtils {
String s = byteArrayToHex(bytes);
return s.toUpperCase();
} catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
+ throw new IllegalStateException();
}
- return null;
}
/**
diff --git a/src/main/java/cc/sukazyo/cono/morny/util/StringUtils.java b/src/main/java/cc/sukazyo/cono/morny/util/StringUtils.java
index 7ff40c2..ced5080 100644
--- a/src/main/java/cc/sukazyo/cono/morny/util/StringUtils.java
+++ b/src/main/java/cc/sukazyo/cono/morny/util/StringUtils.java
@@ -3,17 +3,9 @@ package cc.sukazyo.cono.morny.util;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import java.util.ArrayList;
-import java.util.Arrays;
public class StringUtils {
- @Nonnull
- public static String repeatChar (char c, int i) {
- final char[] chars = new char[i];
- Arrays.fill(chars, c);
- return new String(chars);
- }
-
@Nonnull
public static String[] formatCommand (@Nonnull String com) {
@@ -66,4 +58,12 @@ public class StringUtils {
return builder.toString();
}
+ @Nonnull
+ public static String escapeHtmlTelegram (String raw) {
+ raw = raw.replaceAll("&", "&");
+ raw = raw.replaceAll("<", "<");
+ raw = raw.replaceAll(">", ">");
+ return raw;
+ }
+
}