1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-25 04:50:26 +08:00

Merge remote-tracking branch 'mirai-console/pre-merge-2' into dev

This commit is contained in:
Him188 2021-12-06 14:56:59 +00:00
commit e1a5426546
509 changed files with 42183 additions and 0 deletions
mirai-console
.editorconfig
.github/workflows
.gitignore.gitmodules
backend
codegen
mirai-console
README.mdbuild.gradle.kts
src
MiraiConsole.ktMiraiConsoleFrontEndDescription.ktMiraiConsoleImplementation.kt
command
data
events
extension
extensions
internal

892
mirai-console/.editorconfig Normal file
View File

@ -0,0 +1,892 @@
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = false
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 = true
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 = false
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 = true
ij_java_align_multiline_method_parentheses = false
ij_java_align_multiline_parameters = true
ij_java_align_multiline_parameters_in_calls = false
ij_java_align_multiline_parenthesized_expression = false
ij_java_align_multiline_records = true
ij_java_align_multiline_resources = true
ij_java_align_multiline_ternary_operation = false
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 = false
ij_java_annotation_parameter_wrap = off
ij_java_array_initializer_new_line_after_left_brace = false
ij_java_array_initializer_right_brace_on_new_line = false
ij_java_array_initializer_wrap = off
ij_java_assert_statement_colon_on_next_line = false
ij_java_assert_statement_wrap = off
ij_java_assignment_wrap = off
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 = 0
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 = 1
ij_java_blank_lines_around_method = 1
ij_java_blank_lines_around_method_in_interface = 1
ij_java_blank_lines_before_class_end = 0
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_at_first_column = true
ij_java_builder_methods = none
ij_java_call_parameters_new_line_after_left_paren = false
ij_java_call_parameters_right_paren_on_new_line = false
ij_java_call_parameters_wrap = off
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 = true
ij_java_doc_align_param_comments = true
ij_java_doc_do_not_wrap_if_one_line = false
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 = false
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 = off
ij_java_extends_keyword_wrap = off
ij_java_extends_list_wrap = off
ij_java_field_annotation_wrap = split_into_lines
ij_java_finally_on_new_line = false
ij_java_for_brace_force = never
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = false
ij_java_for_statement_wrap = off
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 = true
ij_java_keep_first_column_comment = true
ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = true
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 = off
ij_java_method_parameters_new_line_after_left_paren = false
ij_java_method_parameters_right_paren_on_new_line = false
ij_java_method_parameters_wrap = off
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 = off
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 = false
ij_java_resource_list_right_paren_on_new_line = false
ij_java_resource_list_wrap = off
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 = true
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 = false
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
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 = off
ij_java_test_name_suffix = Test
ij_java_throws_keyword_wrap = off
ij_java_throws_list_wrap = off
ij_java_use_external_annotations = false
ij_java_use_fq_class_names = false
ij_java_use_relative_indents = false
ij_java_use_single_class_imports = true
ij_java_variable_annotation_wrap = off
ij_java_visibility = public
ij_java_while_brace_force = never
ij_java_while_on_new_line = false
ij_java_wrap_comments = false
ij_java_wrap_first_method_in_call_chain = false
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, *.jhm, *.jnlp, *.jrxml, *.pom, *.qrc, *.rng, *.tld, *.wadl, *.wsdd, *.wsdl, *.xjb, *.xml, *.xsd, *.xsl, *.xslt, *.xul}]
ij_xml_align_attributes = true
ij_xml_align_text = false
ij_xml_attribute_wrap = normal
ij_xml_block_comment_at_first_column = true
ij_xml_keep_blank_lines = 2
ij_xml_keep_indents_on_empty_lines = false
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 = false
ij_xml_text_wrap = normal
ij_xml_use_custom_settings = false
[{*.bash, *.sh, *.zsh}]
indent_size = 2
tab_width = 2
ij_shell_binary_ops_start_line = false
ij_shell_keep_column_alignment_padding = false
ij_shell_minify_program = false
ij_shell_redirect_followed_by_space = false
ij_shell_switch_cases_indented = false
ij_shell_use_unix_line_separator = true
[{*.kt, *.kts}]
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false
ij_kotlin_align_multiline_method_parentheses = false
ij_kotlin_align_multiline_parameters = true
ij_kotlin_align_multiline_parameters_in_calls = false
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_assignment_wrap = normal
ij_kotlin_blank_lines_after_class_header = 0
ij_kotlin_blank_lines_around_block_when_branches = 0
ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
ij_kotlin_block_comment_at_first_column = true
ij_kotlin_call_parameters_new_line_after_left_paren = true
ij_kotlin_call_parameters_right_paren_on_new_line = true
ij_kotlin_call_parameters_wrap = on_every_item
ij_kotlin_catch_on_new_line = false
ij_kotlin_class_annotation_wrap = split_into_lines
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_for_chained_calls = false
ij_kotlin_continuation_indent_for_expression_bodies = false
ij_kotlin_continuation_indent_in_argument_lists = false
ij_kotlin_continuation_indent_in_elvis = false
ij_kotlin_continuation_indent_in_if_conditions = false
ij_kotlin_continuation_indent_in_parameter_lists = false
ij_kotlin_continuation_indent_in_supertype_lists = false
ij_kotlin_else_on_new_line = false
ij_kotlin_enum_constants_wrap = off
ij_kotlin_extends_list_wrap = normal
ij_kotlin_field_annotation_wrap = split_into_lines
ij_kotlin_finally_on_new_line = false
ij_kotlin_if_rparen_on_new_line = true
ij_kotlin_import_nested_classes = false
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
ij_kotlin_keep_blank_lines_before_right_brace = 2
ij_kotlin_keep_blank_lines_in_code = 2
ij_kotlin_keep_blank_lines_in_declarations = 2
ij_kotlin_keep_first_column_comment = true
ij_kotlin_keep_indents_on_empty_lines = false
ij_kotlin_keep_line_breaks = true
ij_kotlin_lbrace_on_next_line = false
ij_kotlin_line_comment_add_space = false
ij_kotlin_line_comment_at_first_column = true
ij_kotlin_method_annotation_wrap = split_into_lines
ij_kotlin_method_call_chain_wrap = normal
ij_kotlin_method_parameters_new_line_after_left_paren = true
ij_kotlin_method_parameters_right_paren_on_new_line = true
ij_kotlin_method_parameters_wrap = on_every_item
ij_kotlin_name_count_to_use_star_import = 5
ij_kotlin_name_count_to_use_star_import_for_members = 3
ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**, io.ktor.**
ij_kotlin_parameter_annotation_wrap = off
ij_kotlin_space_after_comma = true
ij_kotlin_space_after_extend_colon = true
ij_kotlin_space_after_type_colon = true
ij_kotlin_space_before_catch_parentheses = true
ij_kotlin_space_before_comma = false
ij_kotlin_space_before_extend_colon = true
ij_kotlin_space_before_for_parentheses = true
ij_kotlin_space_before_if_parentheses = true
ij_kotlin_space_before_lambda_arrow = true
ij_kotlin_space_before_type_colon = false
ij_kotlin_space_before_when_parentheses = true
ij_kotlin_space_before_while_parentheses = true
ij_kotlin_spaces_around_additive_operators = true
ij_kotlin_spaces_around_assignment_operators = true
ij_kotlin_spaces_around_equality_operators = true
ij_kotlin_spaces_around_function_type_arrow = true
ij_kotlin_spaces_around_logical_operators = true
ij_kotlin_spaces_around_multiplicative_operators = true
ij_kotlin_spaces_around_range = false
ij_kotlin_spaces_around_relational_operators = true
ij_kotlin_spaces_around_unary_operator = false
ij_kotlin_spaces_around_when_arrow = true
ij_kotlin_variable_annotation_wrap = off
ij_kotlin_while_on_new_line = false
ij_kotlin_wrap_elvis_expressions = 1
ij_kotlin_wrap_expression_body_functions = 1
ij_kotlin_wrap_first_method_in_call_chain = false
[{*.cjs, *.js}]
ij_continuation_indent_size = 4
ij_javascript_align_imports = false
ij_javascript_align_multiline_array_initializer_expression = false
ij_javascript_align_multiline_binary_operation = false
ij_javascript_align_multiline_chained_methods = false
ij_javascript_align_multiline_extends_list = false
ij_javascript_align_multiline_for = true
ij_javascript_align_multiline_parameters = true
ij_javascript_align_multiline_parameters_in_calls = false
ij_javascript_align_multiline_ternary_operation = false
ij_javascript_align_object_properties = 0
ij_javascript_align_union_types = false
ij_javascript_align_var_statements = 0
ij_javascript_array_initializer_new_line_after_left_brace = false
ij_javascript_array_initializer_right_brace_on_new_line = false
ij_javascript_array_initializer_wrap = off
ij_javascript_assignment_wrap = off
ij_javascript_binary_operation_sign_on_next_line = false
ij_javascript_binary_operation_wrap = off
ij_javascript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/**
ij_javascript_blank_lines_after_imports = 1
ij_javascript_blank_lines_around_class = 1
ij_javascript_blank_lines_around_field = 0
ij_javascript_blank_lines_around_function = 1
ij_javascript_blank_lines_around_method = 1
ij_javascript_block_brace_style = end_of_line
ij_javascript_call_parameters_new_line_after_left_paren = false
ij_javascript_call_parameters_right_paren_on_new_line = false
ij_javascript_call_parameters_wrap = off
ij_javascript_catch_on_new_line = false
ij_javascript_chained_call_dot_on_new_line = true
ij_javascript_class_brace_style = end_of_line
ij_javascript_comma_on_new_line = false
ij_javascript_do_while_brace_force = never
ij_javascript_else_on_new_line = false
ij_javascript_enforce_trailing_comma = keep
ij_javascript_extends_keyword_wrap = off
ij_javascript_extends_list_wrap = off
ij_javascript_field_prefix = _
ij_javascript_file_name_style = relaxed
ij_javascript_finally_on_new_line = false
ij_javascript_for_brace_force = never
ij_javascript_for_statement_new_line_after_left_paren = false
ij_javascript_for_statement_right_paren_on_new_line = false
ij_javascript_for_statement_wrap = off
ij_javascript_force_quote_style = false
ij_javascript_force_semicolon_style = false
ij_javascript_function_expression_brace_style = end_of_line
ij_javascript_if_brace_force = never
ij_javascript_import_merge_members = global
ij_javascript_import_prefer_absolute_path = global
ij_javascript_import_sort_members = true
ij_javascript_import_sort_module_name = false
ij_javascript_import_use_node_resolution = true
ij_javascript_imports_wrap = on_every_item
ij_javascript_indent_case_from_switch = true
ij_javascript_indent_chained_calls = true
ij_javascript_indent_package_children = 0
ij_javascript_jsx_attribute_value = braces
ij_javascript_keep_blank_lines_in_code = 2
ij_javascript_keep_first_column_comment = true
ij_javascript_keep_indents_on_empty_lines = false
ij_javascript_keep_line_breaks = true
ij_javascript_keep_simple_blocks_in_one_line = false
ij_javascript_keep_simple_methods_in_one_line = false
ij_javascript_line_comment_add_space = true
ij_javascript_line_comment_at_first_column = false
ij_javascript_method_brace_style = end_of_line
ij_javascript_method_call_chain_wrap = off
ij_javascript_method_parameters_new_line_after_left_paren = false
ij_javascript_method_parameters_right_paren_on_new_line = false
ij_javascript_method_parameters_wrap = off
ij_javascript_object_literal_wrap = on_every_item
ij_javascript_parentheses_expression_new_line_after_left_paren = false
ij_javascript_parentheses_expression_right_paren_on_new_line = false
ij_javascript_place_assignment_sign_on_next_line = false
ij_javascript_prefer_as_type_cast = false
ij_javascript_prefer_explicit_types_function_expression_returns = false
ij_javascript_prefer_explicit_types_function_returns = false
ij_javascript_prefer_explicit_types_vars_fields = false
ij_javascript_prefer_parameters_wrap = false
ij_javascript_reformat_c_style_comments = false
ij_javascript_space_after_colon = true
ij_javascript_space_after_comma = true
ij_javascript_space_after_dots_in_rest_parameter = false
ij_javascript_space_after_generator_mult = true
ij_javascript_space_after_property_colon = true
ij_javascript_space_after_quest = true
ij_javascript_space_after_type_colon = true
ij_javascript_space_after_unary_not = false
ij_javascript_space_before_async_arrow_lparen = true
ij_javascript_space_before_catch_keyword = true
ij_javascript_space_before_catch_left_brace = true
ij_javascript_space_before_catch_parentheses = true
ij_javascript_space_before_class_lbrace = true
ij_javascript_space_before_class_left_brace = true
ij_javascript_space_before_colon = true
ij_javascript_space_before_comma = false
ij_javascript_space_before_do_left_brace = true
ij_javascript_space_before_else_keyword = true
ij_javascript_space_before_else_left_brace = true
ij_javascript_space_before_finally_keyword = true
ij_javascript_space_before_finally_left_brace = true
ij_javascript_space_before_for_left_brace = true
ij_javascript_space_before_for_parentheses = true
ij_javascript_space_before_for_semicolon = false
ij_javascript_space_before_function_left_parenth = true
ij_javascript_space_before_generator_mult = false
ij_javascript_space_before_if_left_brace = true
ij_javascript_space_before_if_parentheses = true
ij_javascript_space_before_method_call_parentheses = false
ij_javascript_space_before_method_left_brace = true
ij_javascript_space_before_method_parentheses = false
ij_javascript_space_before_property_colon = false
ij_javascript_space_before_quest = true
ij_javascript_space_before_switch_left_brace = true
ij_javascript_space_before_switch_parentheses = true
ij_javascript_space_before_try_left_brace = true
ij_javascript_space_before_type_colon = false
ij_javascript_space_before_unary_not = false
ij_javascript_space_before_while_keyword = true
ij_javascript_space_before_while_left_brace = true
ij_javascript_space_before_while_parentheses = true
ij_javascript_spaces_around_additive_operators = true
ij_javascript_spaces_around_arrow_function_operator = true
ij_javascript_spaces_around_assignment_operators = true
ij_javascript_spaces_around_bitwise_operators = true
ij_javascript_spaces_around_equality_operators = true
ij_javascript_spaces_around_logical_operators = true
ij_javascript_spaces_around_multiplicative_operators = true
ij_javascript_spaces_around_relational_operators = true
ij_javascript_spaces_around_shift_operators = true
ij_javascript_spaces_around_unary_operator = false
ij_javascript_spaces_within_array_initializer_brackets = false
ij_javascript_spaces_within_brackets = false
ij_javascript_spaces_within_catch_parentheses = false
ij_javascript_spaces_within_for_parentheses = false
ij_javascript_spaces_within_if_parentheses = false
ij_javascript_spaces_within_imports = false
ij_javascript_spaces_within_interpolation_expressions = false
ij_javascript_spaces_within_method_call_parentheses = false
ij_javascript_spaces_within_method_parentheses = false
ij_javascript_spaces_within_object_literal_braces = false
ij_javascript_spaces_within_object_type_braces = true
ij_javascript_spaces_within_parentheses = false
ij_javascript_spaces_within_switch_parentheses = false
ij_javascript_spaces_within_type_assertion = false
ij_javascript_spaces_within_union_types = true
ij_javascript_spaces_within_while_parentheses = false
ij_javascript_special_else_if_treatment = true
ij_javascript_ternary_operation_signs_on_next_line = false
ij_javascript_ternary_operation_wrap = off
ij_javascript_union_types_wrap = on_every_item
ij_javascript_use_chained_calls_group_indents = false
ij_javascript_use_double_quotes = true
ij_javascript_use_explicit_js_extension = global
ij_javascript_use_path_mapping = always
ij_javascript_use_public_modifier = false
ij_javascript_use_semicolon_after_statement = true
ij_javascript_var_declaration_wrap = normal
ij_javascript_while_brace_force = never
ij_javascript_while_on_new_line = false
ij_javascript_wrap_comments = false
[{*.ft, *.vm, *.vsl}]
ij_vtl_keep_indents_on_empty_lines = false
[{*.gant, *.gradle, *.groovy, *.gson, *.gy}]
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 = false
ij_groovy_array_initializer_new_line_after_left_brace = false
ij_groovy_array_initializer_right_brace_on_new_line = false
ij_groovy_array_initializer_wrap = off
ij_groovy_assert_statement_wrap = off
ij_groovy_assignment_wrap = off
ij_groovy_binary_operation_wrap = off
ij_groovy_blank_lines_after_class_header = 0
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_at_first_column = true
ij_groovy_call_parameters_new_line_after_left_paren = false
ij_groovy_call_parameters_right_paren_on_new_line = false
ij_groovy_call_parameters_wrap = off
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 = false
ij_groovy_enum_constants_wrap = off
ij_groovy_extends_keyword_wrap = off
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 = false
ij_groovy_for_statement_right_paren_on_new_line = false
ij_groovy_for_statement_wrap = off
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 = false
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 = false
ij_groovy_method_parameters_right_paren_on_new_line = false
ij_groovy_method_parameters_wrap = off
ij_groovy_modifier_list_wrap = false
ij_groovy_names_count_to_use_import_on_demand = 3
ij_groovy_parameter_annotation_wrap = off
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 = false
ij_groovy_resource_list_right_paren_on_new_line = false
ij_groovy_resource_list_wrap = off
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 = true
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 = false
ij_groovy_space_before_quest = true
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 = off
ij_groovy_throws_keyword_wrap = off
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 = off
ij_groovy_while_brace_force = never
ij_groovy_while_on_new_line = false
ij_groovy_wrap_long_lines = false
[{*.har, *.jsb2, *.jsb3, *.json, .babelrc, .eslintrc, .stylelintrc, bowerrc, jest.config}]
indent_size = 2
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true
ij_json_space_after_colon = true
ij_json_space_after_comma = true
ij_json_space_before_colon = true
ij_json_space_before_comma = false
ij_json_spaces_within_braces = false
ij_json_spaces_within_brackets = false
ij_json_wrap_long_lines = false
[{*.htm, *.html, *.sht, *.shtm, *.shtml}]
ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3
ij_html_align_attributes = true
ij_html_align_text = false
ij_html_attribute_wrap = normal
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 = html, body, thead, tbody, tfoot
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 = 2
ij_html_keep_indents_on_empty_lines = false
ij_html_keep_line_breaks = true
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 = false
ij_html_text_wrap = normal
ij_html_uniform_ident = false
[{*.markdown, *.md, *.mkd}]
max_line_length = 72
ij_markdown_abbreviations_placement = 0
ij_markdown_abbreviations_sort = 0
ij_markdown_attribute_class = 0
ij_markdown_attribute_equal_space = 0
ij_markdown_attribute_id = 0
ij_markdown_attribute_value_quotes = 0
ij_markdown_attributes_combine_consecutive = false
ij_markdown_attributes_sort = false
ij_markdown_attributes_spaces = 0
ij_markdown_atx_header_trailing_marker = 0
ij_markdown_block_quote_markers = 0
ij_markdown_bullet_list_item_marker = 0
ij_markdown_code_fence_marker_length = 3
ij_markdown_code_fence_marker_type = 0
ij_markdown_code_fence_match_closing_marker = false
ij_markdown_code_fence_minimize_indent = false
ij_markdown_code_fence_space_before_info = false
ij_markdown_code_keep_trailing_spaces = 0
ij_markdown_definition_marker_spaces = 3
ij_markdown_definition_marker_type = 0
ij_markdown_enumerated_reference_format_placement = 0
ij_markdown_enumerated_reference_format_sort = 0
ij_markdown_escape_numbered_lead_in_on_wrap = true
ij_markdown_escape_special_chars_on_wrap = true
ij_markdown_footnote_placement = 0
ij_markdown_footnote_sort = 0
ij_markdown_format_with_soft_wrap = 0
ij_markdown_heading_preference = 0
ij_markdown_keep_at_start_explicit_link = 0
ij_markdown_keep_at_start_image_links = 0
ij_markdown_keep_blank_lines = 2
ij_markdown_keep_trailing_spaces = 1
ij_markdown_list_add_blank_line_before = false
ij_markdown_list_align_numeric = 0
ij_markdown_list_ordered_task_item_priority = 1
ij_markdown_list_renumber_items = true
ij_markdown_list_reset_first_item_number = false
ij_markdown_list_spacing = 0
ij_markdown_macro_placement = 0
ij_markdown_macro_sort = 0
ij_markdown_new_bullet_list_item_marker = 1
ij_markdown_para_wrap_text = true
ij_markdown_reference_placement = 0
ij_markdown_reference_sort = 0
ij_markdown_setext_header_equalize_marker = true
ij_markdown_smart_edit_atx_header = true
ij_markdown_smart_edit_setext_header = true
ij_markdown_smart_edit_table_separator_line = true
ij_markdown_smart_edit_tables = false
ij_markdown_smart_enter_atx_header = true
ij_markdown_smart_enter_setext_header = true
ij_markdown_space_after_atx_marker = 1
ij_markdown_tab_size = 4
ij_markdown_table_adjust_column_width = true
ij_markdown_table_apply_column_alignment = true
ij_markdown_table_caption = 0
ij_markdown_table_caption_spaces = 0
ij_markdown_table_fill_missing_columns = true
ij_markdown_table_lead_trail_pipes = true
ij_markdown_table_left_align_marker = 1
ij_markdown_table_space_around_pipe = true
ij_markdown_table_trim_cells = false
ij_markdown_task_list_item_case = 0
ij_markdown_task_list_item_placement = 0
ij_markdown_toc_format_on_save = false
ij_markdown_toc_generate_html = false
ij_markdown_toc_generate_numbered_list = false
ij_markdown_toc_generate_structure = 0
ij_markdown_toc_generate_text_only = false
ij_markdown_toc_heading_levels = 12
ij_markdown_toc_title = Table of Contents
ij_markdown_toc_title_level = 1
ij_markdown_toc_update_on_doc_format = 1
ij_markdown_trailing_spaces_option_1 = 0
ij_markdown_trailing_spaces_option_10 = 0
ij_markdown_trailing_spaces_option_11 = 0
ij_markdown_trailing_spaces_option_12 = 0
ij_markdown_trailing_spaces_option_13 = 0
ij_markdown_trailing_spaces_option_14 = 0
ij_markdown_trailing_spaces_option_15 = 0
ij_markdown_trailing_spaces_option_16 = 0
ij_markdown_trailing_spaces_option_17 = 0
ij_markdown_trailing_spaces_option_18 = 0
ij_markdown_trailing_spaces_option_19 = 0
ij_markdown_trailing_spaces_option_2 = 0
ij_markdown_trailing_spaces_option_20 = 0
ij_markdown_trailing_spaces_option_3 = 0
ij_markdown_trailing_spaces_option_4 = 0
ij_markdown_trailing_spaces_option_5 = 0
ij_markdown_trailing_spaces_option_6 = 0
ij_markdown_trailing_spaces_option_7 = 0
ij_markdown_trailing_spaces_option_8 = 0
ij_markdown_trailing_spaces_option_9 = 0
ij_markdown_unescape_special_chars_on_wrap = true
ij_markdown_use_actual_char_width = true
ij_markdown_verbatim_minimize_indent = false
[{*.properties, spring.handlers, spring.schemas}]
ij_properties_align_group_field_declarations = false
ij_properties_keep_blank_lines = false
ij_properties_key_value_delimiter = equals
ij_properties_spaces_around_key_value_delimiter = false
[{*.yaml, *.yml}]
indent_size = 2
ij_yaml_align_values_properties = do_not_align
ij_yaml_autoinsert_sequence_marker = true
ij_yaml_block_mapping_on_new_line = false
ij_yaml_indent_sequence_value = true
ij_yaml_keep_indents_on_empty_lines = false
ij_yaml_keep_line_breaks = true
ij_yaml_sequence_on_new_line = false
ij_yaml_space_before_colon = false
ij_yaml_spaces_within_braces = true
ij_yaml_spaces_within_brackets = true

View File

@ -0,0 +1,64 @@
# This is a basic workflow to help you get started with Actions
name: Dev Tag Publishing
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- '*-dev*'
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Gradle clean
run: ./gradlew clean
- name: Gradle build
run: ./gradlew build # if test's failed, don't publish
- name: Check keys
run: ./gradlew
:mirai-console:ensureBintrayAvailable
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console:fillBuildConstants
run: ./gradlew
:mirai-console:fillBuildConstants
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console:bintrayUpload
run: ./gradlew
:mirai-console:bintrayUpload
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console-terminal:bintrayUpload
run: ./gradlew
:mirai-console-terminal:bintrayUpload
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console-compiler-common:bintrayUpload
run: ./gradlew
:mirai-console-compiler-common:bintrayUpload
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console-intellij:bintrayUpload
run: ./gradlew
:mirai-console-intellij:bintrayUpload
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Publish Gradle plugin
run: ./gradlew
:mirai-console-gradle:publishPlugins
-Dgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }}
-Dgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }}

View File

@ -0,0 +1,19 @@
name: Gradle CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build --scan

View File

@ -0,0 +1,64 @@
# This is a basic workflow to help you get started with Actions
name: Bintray Publish
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
release:
types:
- released
- prereleased
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Gradle clean
run: ./gradlew clean
- name: Gradle build
run: ./gradlew build # if test's failed, don't publish
- name: Check keys
run: ./gradlew
:mirai-console:ensureBintrayAvailable
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console:fillBuildConstants
run: ./gradlew
:mirai-console:fillBuildConstants
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console:bintrayUpload
run: ./gradlew
:mirai-console:bintrayUpload
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console-terminal:bintrayUpload
run: ./gradlew
:mirai-console-terminal:bintrayUpload
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console-compiler-common:bintrayUpload
run: ./gradlew
:mirai-console-compiler-common:bintrayUpload
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console-intellij:bintrayUpload
run: ./gradlew
:mirai-console-intellij:bintrayUpload
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Publish Gradle plugin
run: ./gradlew
:mirai-console-gradle:publishPlugins
-Dgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }}
-Dgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }}

View File

@ -0,0 +1,30 @@
# This is a basic workflow to help you get started with Actions
name: TagRelease
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- '*-dev*'
jobs:
build:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body: ""
draft: false
prerelease: true

49
mirai-console/.gitignore vendored Normal file
View File

@ -0,0 +1,49 @@
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
target/
build/
.idea/
*.iml
/.idea/
.idea/*
/.idea/*
/test
.gradle/
local.properties
# Maven publishing credits
keys.properties
/plugins/
bintray.user.txt
bintray.key.txt
token.txt
/*/token.txt

3
mirai-console/.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "frontend/mirai-android"]
path = frontend/mirai-android
url = https://github.com/mzdluo123/MiraiAndroid

View File

@ -0,0 +1,6 @@
# Mirai Console - Backend.codegen
后端代码生成模块,用于最小化重复代码的人工成本。
- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/MessageScopeCodegen.kt#L33)
- `Value``PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/ValuePluginDataCodegen.kt#L18)

View File

@ -0,0 +1,34 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
id("java")
}
kotlin {
sourceSets {
all {
languageSettings.optIn("kotlin.Experimental")
languageSettings.optIn("kotlin.RequiresOptIn")
languageSettings.progressiveMode = true
languageSettings.optIn("net.mamoe.mirai.utils.MiraiInternalAPI")
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
languageSettings.optIn("kotlin.experimental.ExperimentalTypeInference")
languageSettings.optIn("kotlin.contracts.ExperimentalContracts")
}
}
}
dependencies {
api(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
api(`mirai-core-utils`)
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("FunctionName", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "PRE_RELEASE_CLASS", "unused")
package net.mamoe.mirai.console.codegen
import org.intellij.lang.annotations.Language
import kotlin.reflect.KClass
import kotlin.reflect.full.functions
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.isSubclassOf
inline fun <reified T> runCodegenInObject() = runCodegenInObject(T::class)
fun runCodegenInObject(clazz: KClass<*>) {
clazz.nestedClasses
.filter { it.isSubclassOf(RegionCodegen::class) }
.associateWith { kClass -> kClass.functions.find { it.name == "main" && it.hasAnnotation<JvmStatic>() } }
.filter { it.value != null }
.forEach { (kClass, entryPoint) ->
println("---------------------------------------------")
println("Running Codegen: ${kClass.simpleName}")
entryPoint!!.call(kClass.objectInstance, arrayOf<String>())
println("---------------------------------------------")
}
}
abstract class Replacer(private val name: String) : (String) -> String {
override fun toString(): String {
return name
}
}
fun Codegen.Replacer(block: (String) -> String): Replacer {
return object : Replacer(this@Replacer::class.simpleName ?: "<unnamed>") {
override fun invoke(p1: String): String = block(p1)
}
}
class CodegenScope : MutableList<Replacer> by mutableListOf() {
fun applyTo(fileContent: String): String {
return this.fold(fileContent) { acc, replacer -> replacer(acc) }
}
@CodegenDsl
operator fun Codegen.invoke(vararg ktTypes: KtType) {
if (ktTypes.isEmpty() && this is DefaultInvoke) {
invoke(defaultInvokeArgs)
}
invoke(ktTypes.toList())
}
@CodegenDsl
operator fun Codegen.invoke(ktTypes: Collection<KtType>) {
add(Replacer { str ->
str + buildString {
ktTypes.forEach { ktType -> applyTo(this, ktType) }
}
})
}
@RegionCodegenDsl
operator fun RegionCodegen.invoke(vararg ktTypes: KtType) = invoke(ktTypes.toList())
@RegionCodegenDsl
operator fun RegionCodegen.invoke(ktTypes: Collection<KtType>) {
add(Replacer { content ->
content.replace(Regex("""//// region $regionName CODEGEN ////([\s\S]*?)( *)//// endregion $regionName CODEGEN ////""")) { result ->
val indent = result.groups[2]!!.value
val indentedCode = CodegenScope()
.apply { (this@invoke as Codegen).invoke(*ktTypes.toTypedArray()) } // add codegen task
.applyTo("") // perform codegen
.lines().dropLastWhile(String::isBlank).joinToString("\n") // remove blank following lines
.mapLine { "${indent}$it" } // indent
"""
|//// region $regionName CODEGEN ////
|
|${indentedCode}
|
|${indent}//// endregion $regionName CODEGEN ////
""".trimMargin()
}
})
}
@DslMarker
annotation class CodegenDsl
}
internal fun String.mapLine(mapper: (String) -> CharSequence) = this.lines().joinToString("\n", transform = mapper)
@DslMarker
annotation class RegionCodegenDsl
interface DefaultInvoke {
val defaultInvokeArgs: List<KtType>
}
abstract class Codegen {
fun applyTo(stringBuilder: StringBuilder, ktType: KtType) = this.run { stringBuilder.apply(ktType) }
protected abstract fun StringBuilder.apply(ktType: KtType)
}
abstract class RegionCodegen(private val targetFile: String, regionName: String? = null) : Codegen() {
val regionName: String by lazy {
regionName ?: this::class.simpleName!!.substringBefore("Codegen")
}
fun startIndependently() {
codegen(targetFile) {
this@RegionCodegen.invoke()
}
}
}
abstract class PrimitiveCodegen : Codegen() {
protected abstract fun StringBuilder.apply(ktType: KtPrimitive)
fun StringBuilder.apply(ktType: List<KtPrimitive>) = ktType.forEach { apply(it) }
}
fun StringBuilder.appendKCode(@Language("kt") ktCode: String): StringBuilder = append(kCode(ktCode)).appendLine()

View File

@ -0,0 +1,203 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.codegen
import net.mamoe.mirai.utils.capitalize
internal val TypeCandidatesForMessageScope = arrayOf(
KtType("Contact"),
KtType("CommandSender"),
)
internal val KtMessageScope = KtType("MessageScope")
internal fun <A> Array<A>.arrangements(): List<Pair<A, A>> {
val result = mutableListOf<Pair<A, A>>()
for (a in this) {
for (b in this) {
result.add(a to b)
}
}
return result
}
internal object MessageScopeCodegen {
object IterableMessageScopeBuildersCodegen : RegionCodegen("MessageScope.kt"), DefaultInvoke {
@JvmStatic
fun main(args: Array<String>) = super.startIndependently()
override val defaultInvokeArgs: List<KtType> = listOf(KtString) // invoke once
@Suppress(
"RedundantVisibilityModifier", "ClassName", "KDocUnresolvedReference", "RedundantSuspendModifier",
"SpellCheckingInspection"
)
override fun StringBuilder.apply(ktType: KtType) {
for (collectionName in arrayOf("Iterable", "Sequence", "Array")) {
for (candidate in (TypeCandidatesForMessageScope + KtMessageScope)) {
appendKCode(
"""
@JvmName("toMessageScope${candidate.standardName.capitalize()}${collectionName.capitalize()}")
public fun $collectionName<$candidate?>.toMessageScope(): MessageScope {
return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope(acc, messageScope.asMessageScopeOrNoop()) }
}
"""
)
appendLine()
}
}
for (candidate in (TypeCandidatesForMessageScope + KtMessageScope)) {
appendKCode(
"""
@JvmSynthetic
@JvmName("toMessageScope${candidate.standardName.capitalize()}Flow")
public suspend fun Flow<$candidate>.toMessageScope(): MessageScope { // Flow<Any?>.firstOrNull isn't yet supported
return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope(acc, messageScope.asMessageScope()) }
}
"""
)
appendLine()
}
}
}
object MessageScopeBuildersCodegen : RegionCodegen("MessageScope.kt"), DefaultInvoke {
@JvmStatic
fun main(args: Array<String>) = super.startIndependently()
override val defaultInvokeArgs: List<KtType> = listOf(KtString) // invoke once
@Suppress("RedundantVisibilityModifier", "ClassName", "KDocUnresolvedReference", "unused")
override fun StringBuilder.apply(ktType: KtType) {
for (candidate in TypeCandidatesForMessageScope) {
appendKCode(
"""
public fun ${candidate}.asMessageScope(): MessageScope = createScopeDelegate(this)
"""
)
appendLine()
}
//
// for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
// appendKCode(
// """
// @LowPriorityInOverloadResolution
// public fun ${a}.scopeWith(vararg others: ${b}): MessageScope {
// return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
// }
// """
// )
// appendLine()
// }
for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
appendKCode(
"""
@LowPriorityInOverloadResolution
public fun ${a}?.scopeWith(vararg others: ${b}?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
"""
)
appendLine()
}
//
// for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
// appendKCode(
// """
// public fun ${a}.scopeWith(other: ${b}): MessageScope {
// return CombinedScope(asMessageScope(), other.asMessageScope())
// }
// """
// )
// appendLine()
// }
for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
appendKCode(
"""
public fun ${a}?.scopeWith(other: ${b}?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
this == null && other != null -> other.asMessageScope()
this != null && other == null -> this.asMessageScope()
this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope())
else -> null!!
}
}
"""
)
appendLine()
}
//
// for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
// appendKCode(
// """
// public inline fun <R> ${a}.scopeWith(vararg others: ${b}, action: MessageScope.() -> R): R {
// return scopeWith(*others).invoke(action)
// }
// """
// )
// appendLine()
// }
for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
appendKCode(
"""
public inline fun <R> ${a}?.scopeWith(vararg others: ${b}?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
"""
)
appendLine()
}
for (a in (TypeCandidatesForMessageScope + KtMessageScope)) {
appendKCode(
"""
@Deprecated(
"Senseless scopeWith. Use asMessageScope.",
ReplaceWith("this.asMessageScope()", "net.mamoe.mirai.console.util.asMessageScope")
)
public inline fun ${a}.scopeWith(): MessageScope = asMessageScope()
"""
)
appendLine()
}
for (a in (TypeCandidatesForMessageScope + KtMessageScope)) {
appendKCode(
"""
@Deprecated(
"Senseless scopeWith. Use .asMessageScope().invoke.",
ReplaceWith(
"this.asMessageScope()(action)",
"net.mamoe.mirai.console.util.asMessageScope",
"net.mamoe.mirai.console.util.invoke",
)
)
public inline fun <R> ${a}.scopeWith(action: MessageScope.() -> R): R = asMessageScope()(action)
"""
)
appendLine()
}
}
}
/**
* 运行本 object 中所有嵌套 object Codegen
*/
@OptIn(ExperimentalStdlibApi::class)
@JvmStatic
fun main(args: Array<String>) {
runCodegenInObject(this::class)
}
}

View File

@ -0,0 +1,200 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("PRE_RELEASE_CLASS", "ClassName", "RedundantVisibilityModifier", "KDocUnresolvedReference")
package net.mamoe.mirai.console.codegen
import kotlin.reflect.full.functions
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.isSubclassOf
internal object ValuePluginDataCodegen {
/**
* The interface
*/
object PrimitiveValuesCodegen : RegionCodegen("Value.kt"), DefaultInvoke {
@JvmStatic
fun main(args: Array<String>) = super.startIndependently()
override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString
override fun StringBuilder.apply(ktType: KtType) {
@Suppress("ClassName")
appendKCode(
"""
/**
* 表示一个不可空 [$ktType] [Value].
*/
public interface ${ktType}Value : PrimitiveValue<$ktType>
"""
)
}
}
object BuiltInSerializerConstantsPrimitivesCodegen : RegionCodegen("_PluginData.value.kt"), DefaultInvoke {
@JvmStatic
fun main(args: Array<String>) = super.startIndependently()
override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString
override fun StringBuilder.apply(ktType: KtType) {
appendLine(
kCode(
"""
@JvmStatic
internal val ${ktType.standardName}SerializerDescriptor = ${ktType.standardName}.serializer().descriptor
"""
).lines().joinToString("\n") { " $it" }
)
}
}
object PrimitiveValuesImplCodegen : RegionCodegen("_PrimitiveValueDeclarations.kt"), DefaultInvoke {
@JvmStatic
fun main(args: Array<String>) = super.startIndependently()
override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString
override fun StringBuilder.apply(ktType: KtType) {
appendKCode(
"""
internal abstract class ${ktType.standardName}ValueImpl : ${ktType.standardName}Value, SerializerAwareValue<${ktType.standardName}>, KSerializer<Unit>, AbstractValueImpl<${ktType.standardName}> {
constructor()
constructor(default: ${ktType.standardName}) {
_value = default
}
private var _value: ${ktType.standardName}? = null
final override var value: ${ktType.standardName}
get() = _value ?: error("${ktType.standardName}Value.value should be initialized before get.")
set(v) {
if (v != this._value) {
if (this._value == null) {
this._value = v
} else {
this._value = v
onChanged()
}
}
}
protected abstract fun onChanged()
final override val serializer: KSerializer<Unit> get() = this
final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.${ktType.standardName}SerializerDescriptor
final override fun serialize(encoder: Encoder, value: Unit) = ${ktType.standardName}.serializer().serialize(encoder, this.value)
final override fun deserialize(decoder: Decoder) = setValueBySerializer(${ktType.standardName}.serializer().deserialize(decoder))
override fun toString(): String = _value${if (ktType != KtString) "?.toString()" else ""} ?: "${ktType.standardName}Value.value not yet initialized."
override fun equals(other: Any?): Boolean = other is ${ktType.standardName}ValueImpl && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return if (value == null) 1
else value.hashCode() * 31
}
}
"""
)
}
}
object PluginData_value_PrimitivesImplCodegen : RegionCodegen("_PluginData.value.kt"), DefaultInvoke {
@JvmStatic
fun main(args: Array<String>) = super.startIndependently()
override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString
override fun StringBuilder.apply(ktType: KtType) {
appendKCode(
"""
internal fun PluginData.valueImpl(default: ${ktType.standardName}): SerializerAwareValue<${ktType.standardName}> {
return object : ${ktType.standardName}ValueImpl(default) {
override fun onChanged() = this@valueImpl.onValueChanged(this)
}
}
internal fun PluginData.${ktType.lowerCaseName}ValueImpl(): SerializerAwareValue<${ktType.standardName}> {
return object : ${ktType.standardName}ValueImpl() {
override fun onChanged() = this@${ktType.lowerCaseName}ValueImpl.onValueChanged(this)
}
}
"""
)
}
}
object PluginData_valueImplPrimitiveCodegen : RegionCodegen("_PluginData.value.kt"), DefaultInvoke {
@JvmStatic
fun main(args: Array<String>) = super.startIndependently()
override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString
override fun StringBuilder.apply(ktType: KtType) {
appendKCode(
"""
${ktType.standardName}::class -> ${ktType.lowerCaseName}ValueImpl()
""".trimIndent()
)
}
}
object PluginData_value_primitivesCodegen : RegionCodegen("PluginData.kt"), DefaultInvoke {
@JvmStatic
fun main(args: Array<String>) = super.startIndependently()
override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString
override fun StringBuilder.apply(ktType: KtType) {
@Suppress("unused")
appendKCode(
"""
/**
* 创建一个 [${ktType.standardName}] 类型的 [Value], 并设置初始值为 [default]
*/
public fun PluginData.value(default: ${ktType.standardName}): SerializerAwareValue<${ktType.standardName}> = valueImpl(default)
"""
)
appendLine()
}
}
object JPluginData_value_primitivesCodegen : RegionCodegen("JPluginData.kt"), DefaultInvoke {
@JvmStatic
fun main(args: Array<String>) = super.startIndependently()
override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString
override fun StringBuilder.apply(ktType: KtType) {
@Suppress("unused")
appendKCode(
"""
/**
* 创建一个 [${ktType.standardName}] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: ${ktType.standardName}): SerializerAwareValue<${ktType.standardName}> = delegate.valueImpl(default)
"""
)
appendLine()
}
}
/**
* 运行本 object 中所有嵌套 object Codegen
*/
@OptIn(ExperimentalStdlibApi::class)
@JvmStatic
fun main(args: Array<String>) {
ValuePluginDataCodegen::class.nestedClasses
.filter { it.isSubclassOf(RegionCodegen::class) }
.associateWith { kClass -> kClass.functions.find { it.name == "main" && it.hasAnnotation<JvmStatic>() } }
.filter { it.value != null }
.forEach { (kClass, entryPoint) ->
println("---------------------------------------------")
println("Running Codegen: ${kClass.simpleName}")
entryPoint!!.call(kClass.objectInstance, arrayOf<String>())
println("---------------------------------------------")
}
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("PRE_RELEASE_CLASS")
package net.mamoe.mirai.console.codegen.old
/**
* used to generate Java PluginData
*/
open class JClazz(val primitiveName: String, val packageName: String) {
open val funName: String = "value"
}
class JListClazz(item: JClazz) : JClazz("List<${item.packageName}>", "List<${item.packageName}>") {
override val funName = item.primitiveName.lowercase() + "List"
}
class JArrayClazz(item: JClazz) : JClazz(item.primitiveName + "[]", item.primitiveName + "[]")
class JMapClazz(key: JClazz, value: JClazz) :
JClazz("Map<${key.packageName},${value.packageName}>", "Map<${key.packageName},${value.packageName}>")
internal val J_NUMBERS = listOf(
JClazz("int", "Integer"),
JClazz("short", "Short"),
JClazz("byte", "Byte"),
JClazz("long", "Long"),
JClazz("float", "Float"),
JClazz("double", "Double")
)
internal val J_EXTRA = listOf(
JClazz("String", "String"),
JClazz("boolean", "Boolean"),
JClazz("char", "Char")
)
fun JClazz.getTemplate(): String = """
@NotNull default Value<${this.packageName}> $funName(${this.primitiveName} defaultValue){
return _PluginDataKt.value(this,defaultValue);
}
"""
fun main() {
println(buildString {
appendLine(COPYRIGHT)
appendLine()
appendLine(FILE_SUPPRESS)
appendLine()
appendLine(
"/**\n" +
" * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.JPluginDataCodegen.kt\n" +
" * !!! DO NOT MODIFY THIS FILE MANUALLY\n" +
" */\n" +
"\"\"\""
)
appendLine()
appendLine()
//do simplest
(J_EXTRA + J_NUMBERS).forEach {
appendLine(it.getTemplate())
}
(J_EXTRA + J_NUMBERS).forEach {
appendLine(JListClazz(it).getTemplate())
}
(J_EXTRA + J_NUMBERS).forEach {
appendLine(JArrayClazz(it).getTemplate())
}
(J_EXTRA + J_NUMBERS).forEach { key ->
(J_EXTRA + J_NUMBERS).forEach { value ->
appendLine(JMapClazz(key, value).getTemplate())
}
}
})
}

View File

@ -0,0 +1,171 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("PRE_RELEASE_CLASS")
package net.mamoe.mirai.console.codegen.old
import org.intellij.lang.annotations.Language
import java.io.File
fun main() {
println(File("").absolutePath) // default project base dir
File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/_PluginData.kt").apply {
createNewFile()
}.writeText(buildString {
appendLine(COPYRIGHT)
appendLine()
appendLine(FILE_SUPPRESS)
appendLine()
appendLine(PACKAGE)
appendLine()
appendLine(IMPORTS)
appendLine()
appendLine()
appendLine(DO_NOT_MODIFY)
appendLine()
appendLine()
appendLine(genAllValueUseSite())
})
}
private val DO_NOT_MODIFY = """
/**
* !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.PluginDataValueUseSiteCodegen.kt
* !!! DO NOT MODIFY THIS FILE MANUALLY
*/
""".trimIndent()
private val PACKAGE = """
package net.mamoe.mirai.console.data
""".trimIndent()
internal val FILE_SUPPRESS = """
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused")
""".trimIndent()
private val IMPORTS = """
import net.mamoe.mirai.console.data.internal.valueImpl
import kotlin.internal.LowPriorityInOverloadResolution
""".trimIndent()
fun genAllValueUseSite(): String = buildString {
@Suppress("SpellCheckingInspection")
fun appendln(@Language("kt") code: String) {
this.appendLine(code.trimIndent())
}
// PRIMITIVE
for (number in NUMBERS + OTHER_PRIMITIVES) {
appendln(genValueUseSite(number, number))
}
// PRIMITIVE ARRAYS
for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) {
appendln(
genValueUseSite(
"${number}Array",
"${number}Array"
)
)
}
// TYPED ARRAYS
for (number in NUMBERS + OTHER_PRIMITIVES) {
appendln(
genValueUseSite(
"Array<${number}>",
"Typed${number}Array"
)
)
}
// PRIMITIVE LISTS / PRIMITIVE SETS
for (collectionName in listOf("List", "Set")) {
for (number in NUMBERS + OTHER_PRIMITIVES) {
appendln(
genValueUseSite(
"${collectionName}<${number}>",
"${number}${collectionName}"
)
)
}
}
// MUTABLE LIST / MUTABLE SET
for (collectionName in listOf("List", "Set")) {
for (number in NUMBERS + OTHER_PRIMITIVES) {
appendLine()
appendln(
"""
@JvmName("valueMutable")
fun PluginData.value(default: Mutable${collectionName}<${number}>): Mutable${number}${collectionName}Value = valueImpl(default)
""".trimIndent()
)
}
}
// SPECIAL
appendLine()
@Suppress("unused", "SpellCheckingInspection", "KDocUnresolvedReference")
appendln(
"""
fun <T : PluginData> PluginData.value(default: T): Value<T> {
require(this::class != default::class) {
"Recursive nesting is prohibited"
}
return valueImpl(default).also {
if (default is PluginData.NestedPluginData) {
default.attachedValue = it
}
}
}
inline fun <T : PluginData> PluginData.value(default: T, crossinline initializer: T.() -> Unit): Value<T> =
value(default).also { it.value.apply(initializer) }
inline fun <reified T : PluginData> PluginData.value(default: List<T>): PluginDataListValue<T> = valueImpl(default)
@JvmName("valueMutable")
inline fun <reified T : PluginData> PluginData.value(default: MutableList<T>): MutablePluginDataListValue<T> = valueImpl(default)
inline fun <reified T : PluginData> PluginData.value(default: Set<T>): PluginDataSetValue<T> = valueImpl(default)
@JvmName("valueMutable")
inline fun <reified T : PluginData> PluginData.value(default: MutableSet<T>): MutablePluginDataSetValue<T> = valueImpl(default)
/**
* 创建一个只引用对象而不跟踪其属性的值.
*
* @param T 类型. 必须拥有 [kotlinx.serialization.Serializable] 注解 (因此编译器会自动生成序列化器)
*/
@DangerousReferenceOnlyValue
@JvmName("valueDynamic")
@LowPriorityInOverloadResolution
inline fun <reified T : Any> PluginData.value(default: T): Value<T> = valueImpl(default)
@RequiresOptIn(
""${'"'}
这种只保存引用的 Value 可能会导致意料之外的结果, 在使用时须保持谨慎.
对值的改变不会触发自动保存, 也不会同步到 UI . UI 中只能编辑序列化之后的值.
""${'"'}, level = RequiresOptIn.Level.WARNING,
)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FUNCTION)
annotation class DangerousReferenceOnlyValue
"""
)
}
fun genValueUseSite(kotlinTypeName: String, miraiValueName: String): String =
"""
fun PluginData.value(default: $kotlinTypeName): ${miraiValueName}Value = valueImpl(default)
""".trimIndent()

View File

@ -0,0 +1,271 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("PRE_RELEASE_CLASS")
package net.mamoe.mirai.console.codegen.old
import org.intellij.lang.annotations.Language
import java.io.File
fun main() {
println(File("").absolutePath) // default project base dir
File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/internal/_ValueImpl.kt").apply {
createNewFile()
}.writeText(buildString {
appendLine(COPYRIGHT)
appendLine()
appendLine(PACKAGE)
appendLine()
appendLine(IMPORTS)
appendLine()
appendLine()
appendLine(DO_NOT_MODIFY)
appendLine()
appendLine()
appendLine(genAllValueImpl())
})
}
private val DO_NOT_MODIFY = """
/**
* !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.ValueImplCodegen.kt
* !!! DO NOT MODIFY THIS FILE MANUALLY
*/
""".trimIndent()
private val PACKAGE = """
package net.mamoe.mirai.console.data.internal
""".trimIndent()
private val IMPORTS = """
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import net.mamoe.mirai.console.data.*
""".trimIndent()
@Suppress("SpellCheckingInspection")
fun genAllValueImpl(): String = buildString {
fun appendln(@Language("kt") code: String) {
this.appendLine(code.trimIndent())
}
// PRIMITIVE
for (number in NUMBERS + OTHER_PRIMITIVES) {
appendln(
genPrimitiveValueImpl(
number,
number,
"$number.serializer()",
false
)
)
appendLine()
}
// PRIMITIVE ARRAYS
for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) {
appendln(
genPrimitiveValueImpl(
"${number}Array",
"${number}Array",
"${number}ArraySerializer()",
true
)
)
appendLine()
}
// TYPED ARRAYS
for (number in NUMBERS + OTHER_PRIMITIVES) {
appendln(
genPrimitiveValueImpl(
"Array<${number}>",
"Typed${number}Array",
"ArraySerializer(${number}.serializer())",
true
)
)
appendLine()
}
// PRIMITIVE LISTS / SETS
for (collectionName in listOf("List", "Set")) {
for (number in NUMBERS + OTHER_PRIMITIVES) {
appendln(
genCollectionValueImpl(
collectionName,
"${collectionName}<${number}>",
"${number}${collectionName}",
"${collectionName}Serializer(${number}.serializer())",
false
)
)
appendLine()
}
}
appendLine()
// MUTABLE LIST / MUTABLE SET
for (collectionName in listOf("List", "Set")) {
for (number in NUMBERS + OTHER_PRIMITIVES) {
@Suppress("unused")
appendln(
"""
@JvmName("valueImplMutable${number}${collectionName}")
internal fun PluginData.valueImpl(
default: Mutable${collectionName}<${number}>
): Mutable${number}${collectionName}Value {
var internalValue: Mutable${collectionName}<${number}> = default
val delegt = dynamicMutable${collectionName} { internalValue }
return object : Mutable${number}${collectionName}Value(), Mutable${collectionName}<${number}> by delegt {
override var value: Mutable${collectionName}<${number}>
get() = internalValue
set(new) {
if (new != internalValue) {
internalValue = new
onElementChanged(this)
}
}
private val outerThis get() = this
override val serializer: KSerializer<Mutable${collectionName}<${number}>> = object : KSerializer<Mutable${collectionName}<${number}>> {
private val delegate = ${collectionName}Serializer(${number}.serializer())
override val descriptor: SerialDescriptor get() = delegate.descriptor
override fun deserialize(decoder: Decoder): Mutable${collectionName}<${number}> {
return delegate.deserialize(decoder).toMutable${collectionName}().observable {
onElementChanged(outerThis)
}
}
override fun serialize(encoder: Encoder, value: Mutable${collectionName}<${number}>) {
delegate.serialize(encoder, value)
}
}
}
}
"""
)
appendLine()
}
}
appendLine()
@Suppress("unused")
appendln(
"""
internal fun <T : PluginData> PluginData.valueImpl(default: T): Value<T> {
return object : PluginDataValue<T>() {
private var internalValue: T = default
override var value: T
get() = internalValue
set(new) {
if (new != internalValue) {
internalValue = new
onElementChanged(this)
}
}
override val serializer = object : KSerializer<T>{
override val descriptor: SerialDescriptor
get() = internalValue.updaterSerializer.descriptor
override fun deserialize(decoder: Decoder): T {
internalValue.updaterSerializer.deserialize(decoder)
return internalValue
}
override fun serialize(encoder: Encoder, value: T) {
internalValue.updaterSerializer.serialize(encoder, PluginDataSerializerMark)
}
}
}
}
"""
)
}
fun genPrimitiveValueImpl(
kotlinTypeName: String,
miraiValueName: String,
serializer: String,
isArray: Boolean
): String =
"""
internal fun PluginData.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value {
return object : ${miraiValueName}Value() {
private var internalValue: $kotlinTypeName = default
override var value: $kotlinTypeName
get() = internalValue
set(new) {
${
if (isArray) """
if (!new.contentEquals(internalValue)) {
internalValue = new
onElementChanged(this)
}
""".trim()
else """
if (new != internalValue) {
internalValue = new
onElementChanged(this)
}
""".trim()
}
}
override val serializer get() = $serializer
}
}
""".trimIndent() + "\n"
@Suppress("SpellCheckingInspection")
fun genCollectionValueImpl(
collectionName: String,
kotlinTypeName: String,
miraiValueName: String,
serializer: String,
isArray: Boolean
): String =
"""
internal fun PluginData.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value {
var internalValue: $kotlinTypeName = default
val delegt = dynamic$collectionName { internalValue }
return object : ${miraiValueName}Value(), $kotlinTypeName by delegt {
override var value: $kotlinTypeName
get() = internalValue
set(new) {
${
if (isArray) """
if (!new.contentEquals(internalValue)) {
internalValue = new
onElementChanged(this)
}
""".trim()
else """
if (new != internalValue) {
internalValue = new
onElementChanged(this)
}
""".trim()
}
}
override val serializer get() = $serializer
}
}
""".trimIndent() + "\n"

View File

@ -0,0 +1,271 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("ClassName", "unused", "PRE_RELEASE_CLASS")
package net.mamoe.mirai.console.codegen.old
import org.intellij.lang.annotations.Language
import java.io.File
fun main() {
println(File("").absolutePath) // default project base dir
File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/_Value.kt").apply {
createNewFile()
}.writeText(genPublicApi())
}
internal val COPYRIGHT = """
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
""".trim()
internal val NUMBERS = listOf(
"Int",
"Short",
"Byte",
"Long",
"Float",
"Double"
)
internal val UNSIGNED_NUMBERS = listOf(
"UInt",
"UShort",
"UByte",
"ULong"
)
internal val OTHER_PRIMITIVES = listOf(
"Boolean",
"Char",
"String"
)
fun genPublicApi() = buildString {
fun appendln(@Language("kt") code: String) {
this.appendLine(code.trimIndent())
}
appendln(COPYRIGHT.trim())
appendLine()
appendln(
"""
package net.mamoe.mirai.console.data
import kotlinx.serialization.KSerializer
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
"""
)
appendLine()
appendln(
"""
/**
* !!! This file is auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt
* !!! for better performance
* !!! DO NOT MODIFY THIS FILE MANUALLY
*/
"""
)
appendLine()
appendln(
"""
sealed class Value<T : Any> : ReadWriteProperty<PluginData, T> {
abstract var value: T
/**
* 用于更新 [value] 的序列化器
*/
abstract val serializer: KSerializer<T>
override fun getValue(thisRef: PluginData, property: KProperty<*>): T = value
override fun setValue(thisRef: PluginData, property: KProperty<*>, value: T) {
this.value = value
}
override fun equals(other: Any?): Boolean {
if (other==null)return false
if (other::class != this::class) return false
other as Value<*>
return other.value == this.value
}
override fun hashCode(): Int = value.hashCode()
}
"""
)
appendLine()
// PRIMITIVES
appendln(
"""
sealed class PrimitiveValue<T : Any> : Value<T>()
sealed class NumberValue<T : Number> : Value<T>()
"""
)
for (number in NUMBERS) {
val template = """
abstract class ${number}Value internal constructor() : NumberValue<${number}>()
"""
appendln(template)
}
appendLine()
for (number in OTHER_PRIMITIVES) {
val template = """
abstract class ${number}Value internal constructor() : PrimitiveValue<${number}>()
"""
appendln(template)
}
appendLine()
// ARRAYS
appendln(
"""
// T can be primitive array or typed Array
sealed class ArrayValue<T : Any> : Value<T>()
"""
)
// PRIMITIVE ARRAYS
appendln(
"""
sealed class PrimitiveArrayValue<T : Any> : ArrayValue<T>()
"""
)
appendLine()
for (number in (NUMBERS + OTHER_PRIMITIVES).filterNot { it == "String" }) {
appendln(
"""
abstract class ${number}ArrayValue internal constructor() : PrimitiveArrayValue<${number}Array>(), Iterable<${number}> {
override fun iterator(): Iterator<${number}> = this.value.iterator()
}
"""
)
appendLine()
}
appendLine()
// TYPED ARRAYS
appendln(
"""
sealed class TypedPrimitiveArrayValue<E> : ArrayValue<Array<E>>() , Iterable<E>{
override fun iterator() = this.value.iterator()
}
"""
)
appendLine()
for (number in (NUMBERS + OTHER_PRIMITIVES)) {
appendln(
"""
abstract class Typed${number}ArrayValue internal constructor() : TypedPrimitiveArrayValue<${number}>()
"""
)
}
appendLine()
// TYPED LISTS / SETS
for (collectionName in listOf("List", "Set")) {
appendln(
"""
sealed class ${collectionName}Value<E> : Value<${collectionName}<E>>(), ${collectionName}<E>
"""
)
for (number in (NUMBERS + OTHER_PRIMITIVES)) {
val template = """
abstract class ${number}${collectionName}Value internal constructor() : ${collectionName}Value<${number}>()
"""
appendln(template)
}
appendLine()
// SETTING
appendln(
"""
abstract class PluginData${collectionName}Value<T: PluginData> internal constructor() : Value<${collectionName}<T>>(), ${collectionName}<T>
"""
)
appendLine()
}
// SETTING VALUE
appendln(
"""
abstract class PluginDataValue<T : PluginData> internal constructor() : Value<T>()
"""
)
appendLine()
// MUTABLE LIST / MUTABLE SET
for (collectionName in listOf("List", "Set")) {
appendln(
"""
abstract class Mutable${collectionName}Value<T : Any> internal constructor() : Value<Mutable${collectionName}<Value<T>>>(), Mutable${collectionName}<T>
"""
)
appendLine()
for (number in (NUMBERS + OTHER_PRIMITIVES)) {
appendln(
"""
abstract class Mutable${number}${collectionName}Value internal constructor() : Value<Mutable${collectionName}<${number}>>(), Mutable${collectionName}<${number}>
"""
)
}
appendLine()
// SETTING
appendln(
"""
abstract class MutablePluginData${collectionName}Value<T: PluginData> internal constructor() : Value<Mutable${collectionName}<T>>(), Mutable${collectionName}<T>
"""
)
appendLine()
}
appendLine()
// DYNAMIC
appendln(
"""
/**
* 只引用这个对象, 而不跟踪其成员.
* 仅适用于基础类型, 用于 mutable list/map 等情况; 或标注了 [Serializable] 的类.
*/
abstract class DynamicReferenceValue<T : Any> : Value<T>()
"""
)
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate", "unused", "PRE_RELEASE_CLASS")
package net.mamoe.mirai.console.codegen
import org.intellij.lang.annotations.Language
import java.io.File
typealias KtByte = KtType.KtPrimitive.KtByte
typealias KtShort = KtType.KtPrimitive.KtShort
typealias KtInt = KtType.KtPrimitive.KtInt
typealias KtLong = KtType.KtPrimitive.KtLong
typealias KtFloat = KtType.KtPrimitive.KtFloat
typealias KtDouble = KtType.KtPrimitive.KtDouble
typealias KtChar = KtType.KtPrimitive.KtChar
typealias KtBoolean = KtType.KtPrimitive.KtBoolean
typealias KtString = KtType.KtString
typealias KtCollection = KtType.KtCollection
typealias KtMap = KtType.KtMap
typealias KtPrimitive = KtType.KtPrimitive
sealed class KtType {
/**
* Its classname in standard library
*/
abstract val standardName: String
override fun toString(): String = standardName
/**
* Not Including [String]
*/
sealed class KtPrimitive(
override val standardName: String,
val jPrimitiveName: String = standardName.lowercase(),
val jObjectName: String = standardName
) : KtType() {
object KtByte : KtPrimitive("Byte")
object KtShort : KtPrimitive("Short")
object KtInt : KtPrimitive("Int", jObjectName = "Integer")
object KtLong : KtPrimitive("Long")
object KtFloat : KtPrimitive("Float")
object KtDouble : KtPrimitive("Double")
object KtChar : KtPrimitive("Char", jObjectName = "Character")
object KtBoolean : KtPrimitive("Boolean")
}
object KtString : KtType() {
override val standardName: String get() = "String"
}
/**
* [List], [Set]
*/
data class KtCollection(override val standardName: String) : KtType()
object KtMap : KtType() {
override val standardName: String get() = "Map"
}
data class Custom(override val standardName: String) : KtType() {
override fun toString(): String {
return standardName
}
}
companion object {
operator fun invoke(standardName: String): KtType = Custom(standardName)
}
}
val KtPrimitiveIntegers = listOf(KtByte, KtShort, KtInt, KtLong)
val KtPrimitiveFloatings = listOf(KtFloat, KtDouble)
val KtPrimitiveNumbers = KtPrimitiveIntegers + KtPrimitiveFloatings
val KtPrimitiveNonNumbers = listOf(KtChar, KtBoolean)
val KtPrimitives = KtPrimitiveNumbers + KtPrimitiveNonNumbers
operator fun KtType.plus(type: KtType): List<KtType> {
return listOf(this, type)
}
val KtType.lowerCaseName: String get() = this.standardName.lowercase()
inline fun kCode(@Language("kt") source: String) = source.trimIndent()
fun codegen(targetFile: String, block: CodegenScope.() -> Unit) {
//// region PrimitiveValue CODEGEN ////
//// region PrimitiveValue CODEGEN ////
targetFile.findFileSmart().also {
println("Codegen target: ${it.absolutePath}")
}.apply {
writeText(
CodegenScope().apply(block).onEach {
println("Applying replacement: $it")
}.applyTo(readText())
)
}
}
fun String.findFileSmart(): File = kotlin.run {
if (contains("/")) { // absolute
File(this)
} else {
val list = File(".").walk().filter { it.name == this }.toList()
if (list.isNotEmpty()) return list.single()
File(".").walk().filter { it.name.contains(this) }.single()
}
}.also {
require(it.exists()) { "file doesn't exist" }
}

View File

@ -0,0 +1,3 @@
# Mirai Console - Backend
Mirai Console 后端模块. 发布为 `net.mamoe:mirai-console`.

View File

@ -0,0 +1,78 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("UnusedImport")
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.time.Instant
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
id("java")
`maven-publish`
id("net.mamoe.kotlin-jvm-blocking-bridge")
}
version = Versions.console
description = "Mirai Console Backend"
kotlin {
explicitApiWarning()
}
dependencies {
compileAndTestRuntime(`mirai-core-api`)
compileAndTestRuntime(`mirai-core-utils`)
compileAndTestRuntime(`kotlin-stdlib-jdk8`)
compileAndTestRuntime(`kotlinx-atomicfu-jvm`)
compileAndTestRuntime(`kotlinx-coroutines-core-jvm`)
compileAndTestRuntime(`kotlinx-serialization-core-jvm`)
compileAndTestRuntime(`kotlinx-serialization-json-jvm`)
compileAndTestRuntime(`kotlin-reflect`)
implementation(project(":mirai-console-compiler-annotations"))
smartImplementation(`yamlkt-jvm`)
smartImplementation(`jetbrains-annotations`)
smartImplementation(`caller-finder`)
smartApi(`kotlinx-coroutines-jdk8`)
testApi(`mirai-core`)
testApi(`kotlin-stdlib-jdk8`)
}
tasks {
val compileKotlin by getting {}
register("fillBuildConstants") {
group = "mirai"
doLast {
(compileKotlin as KotlinCompile).source.filter { it.name == "MiraiConsoleBuildConstants.kt" }.single()
.let { file ->
file.writeText(
file.readText()
.replace(
Regex("""val buildDate: Instant = Instant.ofEpochSecond\(.*\)""")
) {
"""val buildDate: Instant = Instant.ofEpochSecond(${
Instant.now().epochSecond
})"""
}
.replace(
Regex("""const val versionConst:\s+String\s+=\s+".*"""")
) { """const val versionConst: String = "${project.version}"""" }
)
}
}
}
}
configurePublishing("mirai-console")

View File

@ -0,0 +1,221 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "unused")
@file:OptIn(ConsoleInternalApi::class)
package net.mamoe.mirai.console
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotFactory
import net.mamoe.mirai.console.MiraiConsole.INSTANCE
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.extensions.BotConfigurationAlterer
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.AnsiMessageBuilder
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.verbose
import java.io.File
import java.nio.file.Path
import java.time.Instant
/**
* mirai-console 实例
*
* @see INSTANCE
* @see MiraiConsoleImplementation
*/
public interface MiraiConsole : CoroutineScope {
/**
* Console 运行根目录, 由前端决定确切路径.
*
* 所有子模块都会在这个目录之下创建子目录.
*
* @see PluginManager.pluginsPath
* @see PluginManager.pluginsDataPath
* @see PluginManager.pluginsConfigPath
*/
public val rootPath: Path
/**
* Console 主日志.
*
* **实现细节**: 这个 [MiraiLogger] [MiraiLogger.identity] 通常为 `main`
*
* **注意**: 插件不应该在任何时刻使用它.
*/
@ConsoleInternalApi
public val mainLogger: MiraiLogger
/**
* 内建加载器列表, 一般需要包含 [JvmPluginLoader].
*
* @return 不可变 [List] ([java.util.Collections.unmodifiableList])
*/
public val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>>
/**
* Console 后端构建时间
*/
public val buildDate: Instant
/**
* Console 后端版本号
*/
public val version: SemVersion
@ConsoleExperimentalApi
public val pluginCenter: PluginCenter
/**
* 创建一个 logger
*/
@ConsoleExperimentalApi
public fun createLogger(identity: String?): MiraiLogger
/**
* 是否支持使用 Ansi 输出彩色信息
*
* : 不是每个前端都可能提供 `org.fusesource.jansi:jansi` 库支持,
* 请不要直接使用 `org.fusesource.jansi:jansi`
*
* @see [AnsiMessageBuilder]
*/
@ConsoleExperimentalApi
public val isAnsiSupported: Boolean
public companion object INSTANCE : MiraiConsole by MiraiConsoleImplementationBridge {
/**
* 获取 [MiraiConsole] [Job]
*/ // MiraiConsole.INSTANCE.getJob()
public val job: Job
get() = MiraiConsole.coroutineContext[Job]
?: throw MalformedMiraiConsoleImplementationError("Internal error: Job not found in MiraiConsole.coroutineContext")
/**
* 添加一个 [Bot] 实例到全局 Bot 列表, 但不登录.
*
* 调用 [Bot.login] 可登录.
*
* @see Bot.botInstances 获取现有 [Bot] 实例列表
* @see BotConfigurationAlterer ExtensionPoint
*/
// don't static
@ConsoleExperimentalApi("This is a low-level API and might be removed in the future.")
public fun addBot(id: Long, password: String, configuration: BotConfiguration.() -> Unit = {}): Bot =
addBotImpl(id, password, configuration)
/**
* 添加一个 [Bot] 实例到全局 Bot 列表, 但不登录.
*
* 调用 [Bot.login] 可登录.
*
* @see Bot.instances 获取现有 [Bot] 实例列表
* @see BotConfigurationAlterer ExtensionPoint
*/
@ConsoleExperimentalApi("This is a low-level API and might be removed in the future.")
public fun addBot(id: Long, password: ByteArray, configuration: BotConfiguration.() -> Unit = {}): Bot =
addBotImpl(id, password, configuration)
@Suppress("UNREACHABLE_CODE")
private fun addBotImpl(id: Long, password: Any, configuration: BotConfiguration.() -> Unit = {}): Bot {
var config = BotConfiguration().apply {
workingDir = MiraiConsole.rootDir
.resolve("bots")
.resolve(id.toString())
.also { it.mkdirs() }
mainLogger.verbose { "Bot $id working in $workingDir" }
val deviceInRoot = MiraiConsole.rootDir.resolve("device.json")
val deviceInWorkingDir = workingDir.resolve("device.json")
val deviceInfoInWorkingDir = workingDir.resolve("deviceInfo.json")
if (!deviceInWorkingDir.exists()) {
when {
deviceInfoInWorkingDir.exists() -> {
// rename bots/id/deviceInfo.json to bots/id/device.json
mainLogger.verbose { "Renaming $deviceInfoInWorkingDir to $deviceInWorkingDir" }
deviceInfoInWorkingDir.renameTo(deviceInWorkingDir)
}
deviceInRoot.exists() -> {
// copy root/device.json to bots/id/device.json
mainLogger.verbose { "Coping $deviceInRoot to $deviceInWorkingDir" }
deviceInRoot.copyTo(deviceInWorkingDir)
}
}
}
fileBasedDeviceInfo("device.json")
redirectNetworkLogToDirectory()
this.botLoggerSupplier = {
MiraiLogger.Factory.create(Bot::class, "Bot.${it.id}")
}
parentCoroutineContext = MiraiConsole.childScopeContext("Bot $id")
autoReconnectOnForceOffline()
this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this)
configuration()
}
config = GlobalComponentStorage.run {
BotConfigurationAlterer.foldExtensions(config) { acc, extension ->
extension.alterConfiguration(id, acc)
}
}
return when (password) {
is ByteArray -> BotFactory.newBot(id, password, config)
is String -> BotFactory.newBot(id, password, config)
else -> throw IllegalArgumentException("Bad password type: `${password.javaClass.name}`. Require ByteArray or String")
}
}
@ConsoleExperimentalApi("This is a low-level API and might be removed in the future.")
public val isActive: Boolean
get() = job.isActive
}
}
/**
* @see MiraiConsole.rootPath
*/
public val MiraiConsole.rootDir: File get() = rootPath.toFile()
/**
* [MiraiConsoleImplementation] 实现有误时抛出.
*
* @see MiraiConsoleImplementation.start
*/
public class MalformedMiraiConsoleImplementationError : Error {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.MiraiExperimentalApi
/**
* 有关前端实现的信息
*/
public interface MiraiConsoleFrontEndDescription {
/**
* 此前端实现的名称
*/
public val name: String
/**
* 此前端实现的提供者
*/
public val vendor: String
/**
* 此前端实现的名称
*/
public val version: SemVersion
/**
* 兼容的 [MiraiConsole] 后端版本号
*
* `Semver("[1.0.0, 2.0.0)", Semver.SemverType.IVY)`
*
* 返回 `null` 表示禁止 [MiraiConsole] 后端检查版本兼容性.
*/
@MiraiExperimentalApi
public val compatibleBackendVersion: SemVersion?
get() = null
/**
* 返回显示在 [MiraiConsole] 启动时的信息
*/
public fun render(): String = "Frontend ${name}: version ${version}, provided by $vendor"
}

View File

@ -0,0 +1,293 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.extension.ComponentStorage
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.logging.LoggerController
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger
import java.nio.file.Path
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.annotation.AnnotationTarget.*
import kotlin.coroutines.CoroutineContext
/**
* 标记一个仅用于 [MiraiConsole] 前端实现的 API.
*
* 这些 API 只应由前端实现者使用, 而不应该被插件或其他调用者使用.
*/
@Retention(AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
@MustBeDocumented
public annotation class ConsoleFrontEndImplementation
/**
* 实现 [MiraiConsole] 的接口
*
* **注意**: 随着 Console 的更新, 在版本号 `x.y.z` `y` 修改时此接口可能就会发生 ABI 变动. 意味着前端实现着需要跟随 Console 更新.
*
* @see MiraiConsoleImplementation.start 启动
*/
@ConsoleFrontEndImplementation
public interface MiraiConsoleImplementation : CoroutineScope {
/**
* [MiraiConsole] [CoroutineScope.coroutineContext], 必须拥有如下元素
*
* - [Job]: 用于管理整个 [MiraiConsole] 的生命周期. 当此 [Job] [Job.cancel] , [MiraiConsole] 就会结束.
* - [CoroutineExceptionHandler]: 用于处理 [MiraiConsole] 所有协程抛出的 **未被捕捉** 的异常. 不是所有异常都会被传递到这里.
*/
public override val coroutineContext: CoroutineContext
/**
* Console 运行根目录绝对路径 (否则可能会被一些 native 插件覆盖相对路径)
* @see MiraiConsole.rootPath 获取更多信息
*/
public val rootPath: Path
/**
* 本前端实现的描述信息
*/
public val frontEndDescription: MiraiConsoleFrontEndDescription
/**
* 内建加载器列表, 一般需要包含 [JvmPluginLoader].
*
* @return 不可变的 [List], [Collections.unmodifiableList]
*/
public val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>>
/**
* Kotlin 用户实现
*
* @see [ConsoleCommandSender]
*/
@ConsoleFrontEndImplementation
public interface ConsoleCommandSenderImpl {
@JvmSynthetic
public suspend fun sendMessage(message: Message)
@JvmSynthetic
public suspend fun sendMessage(message: String)
}
/**
* Java 用户实现
*
* @see [ConsoleCommandSender]
*/
@Suppress("INAPPLICABLE_JVM_NAME")
@ConsoleFrontEndImplementation
public interface JConsoleCommandSenderImpl : ConsoleCommandSenderImpl {
@JvmName("sendMessage")
public fun sendMessageJ(message: Message)
@JvmName("sendMessage")
public fun sendMessageJ(message: String)
@JvmSynthetic
public override suspend fun sendMessage(message: Message): Unit =
withContext(Dispatchers.IO) { sendMessageJ(message) }
@JvmSynthetic
public override suspend fun sendMessage(message: String): Unit =
withContext(Dispatchers.IO) { sendMessageJ(message) }
}
/**
* [ConsoleCommandSender]
*/
public val consoleCommandSender: ConsoleCommandSenderImpl
public val dataStorageForJvmPluginLoader: PluginDataStorage
public val configStorageForJvmPluginLoader: PluginDataStorage
public val dataStorageForBuiltIns: PluginDataStorage
public val configStorageForBuiltIns: PluginDataStorage
/**
* @see ConsoleInput 的实现
* @see JConsoleInput
*/
public val consoleInput: ConsoleInput
/**
* Java 用户实现 [ConsoleInput]
*/
@Suppress("INAPPLICABLE_JVM_NAME")
@ConsoleFrontEndImplementation
public interface JConsoleInput : ConsoleInput {
/**
* @see ConsoleInput.requestInput
*/
@JvmName("requestInput")
public fun requestInputJ(hint: String): String
override suspend fun requestInput(hint: String): String {
return withContext(Dispatchers.IO) { requestInputJ(hint) }
}
}
/**
* 创建一个 [LoginSolver]
*
* **备注**: 此函数通常在构造 [Bot] 实例, [MiraiConsole.addBot] 时调用.
*
* @param requesterBot 请求者 [Bot.id]
* @param configuration 请求者 [Bot.configuration]
*
* @see LoginSolver.Default
*/
public fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver
/**
* 创建一个 [MiraiLogger].
*
* **注意**: [MiraiConsole] 会将 [net.mamoe.mirai.utils.MiraiLogger.setDefaultLoggerCreator] 设置为 `MiraiConsole::createLogger`.
* 因此不要在 [createLogger] 中调用 [net.mamoe.mirai.utils.MiraiLogger.create]
*/
public fun createLogger(identity: String?): MiraiLogger
/**
* 该前端是否支持使用 Ansi 输出彩色信息
*
* : 若为 `true`, 建议携带 `org.fusesource.jansi:jansi`
*/
public val isAnsiSupported: Boolean get() = false
/**
* 前端预先定义的 [LoggerController], 以允许前端使用自己的配置系统
*/
public val loggerController: LoggerController get() = LoggerControllerImpl
/// Hooks & Backend Access
/**
* 后端 [phase] 阶段执行前会调用此方法, 如果此方法抛出了一个错误会直接中断 console 初始化
*
* @since 2.5.0-dev-2
*/
public fun prePhase(phase: String) {}
/**
* 后端 [phase] 阶段执行后会调用此方法, 如果此方法抛出了一个错误会直接中断 console 初始化
*
* @since 2.5.0-dev-2
*/
public fun postPhase(phase: String) {}
/**
* 后端在 [start] 前会调用此方法
*
* @since 2.5.0-dev-2
*/
public fun preStart() {}
/**
* 后端在 [start] 后会调用此方法
*
* @since 2.5.0-dev-2
*/
public fun postStart() {}
/**
* 前端访问后端内部实现的桥
*
* @see backendAccess
* @since 2.5.0-dev-2
*/
@ConsoleFrontEndImplementation
public interface BackendAccess {
// GlobalComponentStorage
public val globalComponentStorage: ComponentStorage
// PluginManagerImpl.resolvedPlugins
public val resolvedPlugins: MutableList<Plugin>
}
/**
* @see BackendAccess
* @since 2.5.0-dev-2
* @throws IllegalStateException 当前端实例不是 `this` 时抛出
*/
public val backendAccess: BackendAccess
get() = backendAccessInstance
public companion object {
private val backendAccessInstance = object : BackendAccess {
override val globalComponentStorage: ComponentStorage get() = GlobalComponentStorage
override val resolvedPlugins: MutableList<Plugin> get() = PluginManagerImpl.resolvedPlugins
}
@Volatile
internal var instance: MiraiConsoleImplementation? = null
internal val instanceInitialized: Boolean get() = instance != null
private val initLock = ReentrantLock()
/**
* 可由前端调用, 获取当前的 [MiraiConsoleImplementation] 实例
*
* 必须在 [start] 之后才能使用, 否则抛出 [UninitializedPropertyAccessException]
*/
@JvmStatic
@ConsoleFrontEndImplementation
public fun getInstance(): MiraiConsoleImplementation = instance ?: throw UninitializedPropertyAccessException()
/** 由前端调用, 初始化 [MiraiConsole] 实例并启动 */
@JvmStatic
@ConsoleFrontEndImplementation
@Throws(MalformedMiraiConsoleImplementationError::class)
public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock {
val instance = instance
if (instance != null && instance.isActive) {
error(
"Mirai Console is already initialized and is currently running. " +
"Run MiraiConsole.cancel to kill old instance before starting another instance."
)
}
this@Companion.instance = this
kotlin.runCatching {
MiraiConsoleImplementationBridge.doStart()
}.onFailure { e ->
kotlin.runCatching {
MiraiConsole.mainLogger.error("Failed to init MiraiConsole.", e)
}.onFailure {
e.printStackTrace()
}
kotlin.runCatching {
MiraiConsole.cancel()
}.onFailure {
it.printStackTrace()
}
}
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.internal.command.findOrCreateCommandPermission
import net.mamoe.mirai.console.permission.Permission
/**
* [Command] 的基础实现
*
* @see SimpleCommand
* @see CompositeCommand
* @see RawCommand
*/
public abstract class AbstractCommand
@JvmOverloads constructor(
public final override val owner: CommandOwner,
public final override val primaryName: String,
public final override val secondaryNames: Array<out String>,
public override val description: String = "<no description available>",
parentPermission: Permission = owner.parentPermission,
) : Command {
@ExperimentalCommandDescriptors
override val prefixOptional: Boolean
get() = false
init {
Command.checkCommandName(primaryName)
secondaryNames.forEach(Command.Companion::checkCommandName)
}
public override val usage: String get() = description
public override val permission: Permission by lazy { findOrCreateCommandPermission(parentPermission) }
}

View File

@ -0,0 +1,638 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.map
import net.mamoe.mirai.console.command.descriptor.PermissionIdValueArgumentParser
import net.mamoe.mirai.console.command.descriptor.PermitteeIdValueArgumentParser
import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
import net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.*
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.MD5
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.internal.util.runIgnoreException
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.PermissionService.Companion.cancel
import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail
import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions
import net.mamoe.mirai.console.permission.PermissionService.Companion.hasPermission
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
import net.mamoe.mirai.console.permission.PermitteeId
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.plugin.version
import net.mamoe.mirai.console.util.*
import net.mamoe.mirai.event.events.EventCancelledException
import net.mamoe.mirai.message.nextMessageOrNull
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.secondsToMillis
import java.lang.management.ManagementFactory
import java.lang.management.MemoryUsage
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlin.concurrent.thread
import kotlin.math.floor
import kotlin.system.exitProcess
@ConsoleExperimentalApi
@Suppress("EXPOSED_SUPER_INTERFACE")
public interface BuiltInCommand : Command
// for identification
internal interface BuiltInCommandInternal : Command, BuiltInCommand
/**
* 内建指令列表
*
* [查看文档](https://github.com/mamoe/mirai-console/docs/BuiltInCommands.md)
*/
@Suppress("unused", "RESTRICTED_CONSOLE_COMMAND_OWNER")
public object BuiltInCommands {
@ConsoleExperimentalApi
public val parentPermission: Permission by lazy {
PermissionService.INSTANCE.register(
ConsoleCommandOwner.permissionId("*"),
"The parent of any built-in commands"
)
}
internal val all: Array<out Command> by lazy {
this::class.nestedClasses.mapNotNull { it.objectInstance as? Command }.toTypedArray()
}
internal fun registerAll() {
BuiltInCommands::class.nestedClasses.forEach {
(it.objectInstance as? Command)?.register()
}
}
public object HelpCommand : SimpleCommand(
ConsoleCommandOwner, "help",
description = "查看指令帮助",
), BuiltInCommandInternal {
/**
* @since 2.8.0
*/
// for https://github.com/mamoe/mirai-console/issues/416
@JvmStatic
public fun generateDefaultHelp(permitteeId: PermitteeId): String {
return allRegisteredCommands
.asSequence()
.filter { permitteeId.hasPermission(it.permission) }
.joinToString("\n\n") { command ->
val lines = command.usage.lines()
if (lines.isEmpty()) "/${command.primaryName} ${command.description}"
else
"" + lines.first() + "\n" + lines.drop(1).joinToString("\n") { " $it" }
}.lines().filterNot(String::isBlank).joinToString("\n")
}
@Handler
public suspend fun CommandSender.handle() {
sendMessage(generateDefaultHelp(this.permitteeId))
}
}
init {
Runtime.getRuntime().addShutdownHook(thread(false) {
MiraiConsole.cancel()
})
}
public object StopCommand : SimpleCommand(
ConsoleCommandOwner, "stop", "shutdown", "exit",
description = "关闭 Mirai Console",
), BuiltInCommandInternal {
private val closingLock = Mutex()
@OptIn(DelicateCoroutinesApi::class)
@Handler
public suspend fun CommandSender.handle() {
GlobalScope.launch {
kotlin.runCatching {
closingLock.withLock {
if (!MiraiConsole.isActive) return@withLock
sendMessage("Stopping mirai-console")
kotlin.runCatching {
Bot.instances.forEach { bot ->
lateinit var logger: MiraiLogger
kotlin.runCatching {
logger = bot.logger
bot.closeAndJoin()
}.onFailure { t ->
kotlin.runCatching { logger.error("Error in closing bot", t) }
}
}
MiraiConsole.job.cancelAndJoin()
}.fold(
onSuccess = {
runIgnoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
},
onFailure = {
@OptIn(ConsoleInternalApi::class)
MiraiConsole.mainLogger.error("Exception in stop", it)
runIgnoreException<EventCancelledException> {
sendMessage(
it.localizedMessage ?: it.message ?: it.toString()
)
}
}
)
}
}.exceptionOrNull()?.let(MiraiConsole.mainLogger::error)
exitProcess(0)
}
}
}
public object LogoutCommand : SimpleCommand(
ConsoleCommandOwner, "logout", "登出",
description = "登出一个账号",
), BuiltInCommandInternal {
@Handler
public suspend fun CommandSender.handle(
@Name("qq") id: Long
){
if (Bot.getInstanceOrNull(id)?.close() == null) {
sendMessage("$id 未登录")
} else {
sendMessage("$id 已登出")
}
}
}
public object LoginCommand : SimpleCommand(
ConsoleCommandOwner, "login", "登录",
description = "登录一个账号",
), BuiltInCommandInternal {
private suspend fun Bot.doLogin() = kotlin.runCatching {
login(); this
}.onFailure { close() }.getOrThrow()
@Handler
@JvmOverloads
public suspend fun CommandSender.handle(
@Name("qq") id: Long,
password: String? = null,
protocol: BotConfiguration.MiraiProtocol? = null,
) {
fun BotConfiguration.setup(protocol: BotConfiguration.MiraiProtocol?): BotConfiguration {
if (protocol != null) this.protocol = protocol
return this
}
suspend fun getPassword(id: Long): Any? {
val acc = AutoLoginConfig.accounts.firstOrNull { it.account == id.toString() }
if (acc == null) {
sendMessage("Could not find '$id' in AutoLogin config. Please specify password.")
return null
}
return if (acc.password.kind == MD5) acc.password.value.toByteArray() else acc.password.value
}
val pwd: Any = password ?: getPassword(id) ?: return
kotlin.runCatching {
when (pwd) {
is String -> MiraiConsole.addBot(id, pwd) { setup(protocol) }.doLogin()
is ByteArray -> MiraiConsole.addBot(id, pwd) { setup(protocol) }.doLogin()
else -> throw AssertionError("Assertion failed, please report to https://github.com/mamoe/mirai-console/issues/new/choose, debug=${pwd.javaClass}")// Unreachable
}
}.fold(
onSuccess = { scopeWith(ConsoleCommandSender).sendMessage("${it.nick} ($id) Login successful") },
onFailure = { throwable ->
scopeWith(ConsoleCommandSender).sendMessage(
"Login failed: ${throwable.localizedMessage ?: throwable.message ?: throwable.toString()}" +
if (this is CommandSenderOnMessage<*>) {
CommandManagerImpl.launch(CoroutineName("stacktrace delayer from Login")) {
fromEvent.nextMessageOrNull(60.secondsToMillis) { it.message.contentEquals("stacktrace") }
}
"\n 1 分钟内发送 stacktrace 以获取堆栈信息"
} else ""
)
throw throwable
}
)
}
}
public object PermissionCommand : CompositeCommand(
ConsoleCommandOwner, "permission", "权限", "perm",
description = "管理权限",
overrideContext = buildCommandArgumentContext {
PermitteeId::class with PermitteeIdValueArgumentParser
Permission::class with PermissionIdValueArgumentParser.map { id ->
kotlin.runCatching {
id.findCorrespondingPermissionOrFail()
}.getOrElse { throw CommandArgumentParserException("指令不存在: $id", it) }
}
},
), BuiltInCommandInternal {
// TODO: 2020/9/10 improve Permission command
@Description("授权一个权限")
@SubCommand("permit", "grant", "add")
public suspend fun CommandSender.permit(
@Name("被许可人 ID") target: PermitteeId,
@Name("权限 ID") permission: Permission,
) {
target.permit(permission)
sendMessage("OK")
}
@Description("撤销一个权限")
@SubCommand("cancel", "deny", "remove")
public suspend fun CommandSender.cancel(
@Name("被许可人 ID") target: PermitteeId,
@Name("权限 ID") permission: Permission,
) {
target.cancel(permission, false)
sendMessage("OK")
}
@Description("撤销一个权限及其所有子权限")
@SubCommand("cancelAll", "denyAll", "removeAll")
public suspend fun CommandSender.cancelAll(
@Name("被许可人 ID") target: PermitteeId,
@Name("权限 ID") permission: Permission,
) {
target.cancel(permission, true)
sendMessage("OK")
}
@Description("查看被授权权限列表")
@SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp")
public suspend fun CommandSender.permittedPermissions(
@Name("被许可人 ID") target: PermitteeId,
@Name("包括重复") all: Boolean = false,
) {
var grantedPermissions = target.getPermittedPermissions().toList()
if (!all) {
grantedPermissions = grantedPermissions.filter { thisPerm ->
grantedPermissions.none { other -> thisPerm.parentsWithSelf.drop(1).any { it == other } }
}
}
if (grantedPermissions.isEmpty()) {
sendMessage("${target.asString()} 未被授予任何权限. 使用 `${CommandManager.commandPrefix}permission grant` 给予权限.")
} else {
sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() })
}
}
@Description("查看所有权限列表")
@SubCommand("listPermissions", "lp")
public suspend fun CommandSender.listPermissions() {
sendMessage(
PermissionService.INSTANCE.getRegisteredPermissions()
.joinToString("\n") { it.id.toString() + " " + it.description })
}
}
public object AutoLoginCommand : CompositeCommand(
ConsoleCommandOwner, "autoLogin", "自动登录",
description = "自动登录设置",
overrideContext = buildCommandArgumentContext {
ConfigurationKey::class with ConfigurationKey.Parser
}
), BuiltInCommandInternal {
@Description("查看自动登录账号列表")
@SubCommand
public suspend fun CommandSender.list() {
sendMessage(buildString {
for (account in AutoLoginConfig.accounts) {
if (account.account == "123456") continue
append("- ")
append("账号: ")
append(account.account)
appendLine()
append(" 密码: ")
append(account.password.value)
appendLine()
if (account.configuration.isNotEmpty()) {
appendLine(" 配置:")
for ((key, value) in account.configuration) {
append(" $key = $value")
}
appendLine()
}
}
})
}
@Description("添加自动登录, passwordKind 可选 PLAIN 或 MD5")
@SubCommand
public suspend fun CommandSender.add(account: Long, password: String, passwordKind: PasswordKind = PLAIN) {
val accountStr = account.toString()
if (AutoLoginConfig.accounts.any { it.account == accountStr }) {
sendMessage("已有相同账号在自动登录配置中. 请先删除该账号.")
return
}
AutoLoginConfig.accounts.add(AutoLoginConfig.Account(accountStr, Password(passwordKind, password)))
sendMessage("已成功添加 '$account'.")
}
@Description("清除所有配置")
@SubCommand
public suspend fun CommandSender.clear() {
AutoLoginConfig.accounts.clear()
sendMessage("已清除所有自动登录配置.")
}
@Description("删除一个账号")
@SubCommand
public suspend fun CommandSender.remove(account: Long) {
val accountStr = account.toString()
if (AutoLoginConfig.accounts.removeIf { it.account == accountStr }) {
sendMessage("已成功删除 '$account'.")
return
}
sendMessage("账号 '$account' 未配置自动登录.")
}
@Description("设置一个账号的一个配置项")
@SubCommand
public suspend fun CommandSender.setConfig(account: Long, configKey: ConfigurationKey, value: String) {
val accountStr = account.toString()
val oldAccount = AutoLoginConfig.accounts.find { it.account == accountStr } ?: kotlin.run {
sendMessage("未找到账号 $account.")
return
}
if (value.isEmpty()) return removeConfig(account, configKey)
val newAccount = oldAccount.copy(configuration = oldAccount.configuration.toMutableMap().apply {
put(configKey, value)
})
AutoLoginConfig.accounts.remove(oldAccount)
AutoLoginConfig.accounts.add(newAccount)
sendMessage("成功修改 '$account' 的配置 '$configKey' 为 '$value'")
}
@Description("删除一个账号的一个配置项")
@SubCommand
public suspend fun CommandSender.removeConfig(account: Long, configKey: ConfigurationKey) {
val accountStr = account.toString()
val oldAccount = AutoLoginConfig.accounts.find { it.account == accountStr } ?: kotlin.run {
sendMessage("未找到账号 $account.")
return
}
val newAccount = oldAccount.copy(configuration = oldAccount.configuration.toMutableMap().apply {
remove(configKey)
})
AutoLoginConfig.accounts.remove(oldAccount)
AutoLoginConfig.accounts.add(newAccount)
sendMessage("成功删除 '$account' 的配置 '$configKey'.")
}
}
public object StatusCommand : SimpleCommand(
ConsoleCommandOwner, "status", "states", "状态",
description = "获取 Mirai Console 运行状态"
), BuiltInCommandInternal {
internal interface MemoryUsageGet {
val heapMemoryUsage: MUsage
val nonHeapMemoryUsage: MUsage
val objectPendingFinalizationCount: Int
}
internal val memoryUsageGet: MemoryUsageGet = kotlin.runCatching {
ByMemoryMXBean
}.getOrElse { ByRuntime }
internal object ByMemoryMXBean : MemoryUsageGet {
val memoryMXBean = ManagementFactory.getMemoryMXBean()
val MemoryUsage.m: MUsage
get() = MUsage(
committed, init, used, max
)
override val heapMemoryUsage: MUsage
get() = memoryMXBean.heapMemoryUsage.m
override val nonHeapMemoryUsage: MUsage
get() = memoryMXBean.nonHeapMemoryUsage.m
override val objectPendingFinalizationCount: Int
get() = memoryMXBean.objectPendingFinalizationCount
}
internal object ByRuntime : MemoryUsageGet {
override val heapMemoryUsage: MUsage
get() {
val runtime = Runtime.getRuntime()
return MUsage(
committed = 0,
init = 0,
used = runtime.maxMemory() - runtime.freeMemory(),
max = runtime.maxMemory()
)
}
override val nonHeapMemoryUsage: MUsage
get() = MUsage(-1, -1, -1, -1)
override val objectPendingFinalizationCount: Int
get() = -1
}
internal data class MUsage(
val committed: Long,
val init: Long,
val used: Long,
val max: Long,
)
@Handler
public suspend fun CommandSender.handle() {
sendAnsiMessage {
val buildDateFormatted =
MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
append("Running MiraiConsole v")
gold().append(MiraiConsoleBuildConstants.versionConst)
reset().append(", built on ")
lightBlue().append(buildDateFormatted).reset().append(".\n")
append(MiraiConsoleImplementationBridge.frontEndDescription.render()).append("\n\n")
append("Permission Service: ").append(
if (PermissionService.INSTANCE is BuiltInPermissionService) {
lightYellow()
"Built In Permission Service"
} else {
val plugin = PermissionServiceProvider.providerPlugin
if (plugin == null) {
PermissionService.INSTANCE.toString()
} else {
green().append(plugin.name).reset().append(" v").gold()
plugin.version.toString()
}
}
)
reset().append("\n\n")
append("Plugins: ")
if (PluginManagerImpl.resolvedPlugins.isEmpty()) {
gray().append("<none>")
} else {
PluginManagerImpl.resolvedPlugins.joinTo(this) { plugin ->
green().append(plugin.name).reset().append(" v").gold()
plugin.version.toString()
}
}
reset().append("\n\n")
append("Object Pending Finalization Count: ")
.emeraldGreen()
.append(memoryUsageGet.objectPendingFinalizationCount)
.reset()
.append("\n")
val l1 = arrayOf("committed", "init", "used", "max")
val l2 = renderMemoryUsage(memoryUsageGet.heapMemoryUsage)
val l3 = renderMemoryUsage(memoryUsageGet.nonHeapMemoryUsage)
val lmax = calculateMax(l1, l2.first, l3.first)
append(" ")
l1.forEachIndexed { index, s ->
if (index != 0) append(" | ")
renderMUNum(lmax[index], s.length) { append(s); reset() }
}
reset()
append("\n")
fun rendMU(l: Pair<Array<String>, LongArray>) {
val max = l.second[3]
val e50 = max / 2
val e90 = max * 90 / 100
l.first.forEachIndexed { index, s ->
if (index != 0) append(" | ")
renderMUNum(lmax[index], s.length) {
if (index == 3) {
// MAX
append(s)
} else {
if (max < 0L) {
append(s)
} else {
val v = l.second[index]
when {
v < e50 -> {
green()
}
v < e90 -> {
lightRed()
}
else -> {
red()
}
}
append(s)
reset()
}
}
}
}
}
append(" Heap Memory: ")
rendMU(l2)
append("\nNon-Heap Memory: ")
rendMU(l3)
}
}
private const val MEM_B = 1024L
private const val MEM_KB = 1024L shl 10
private const val MEM_MB = 1024L shl 20
private const val MEM_GB = 1024L shl 30
@Suppress("NOTHING_TO_INLINE")
private inline fun StringBuilder.appendDouble(number: Double): StringBuilder =
append(floor(number * 100) / 100)
private fun renderMemoryUsageNumber(num: Long) = buildString {
when {
num == -1L -> {
append(num)
}
num < MEM_B -> {
append(num).append("B")
}
num < MEM_KB -> {
appendDouble(num / 1024.0).append("KB")
}
num < MEM_MB -> {
appendDouble((num ushr 10) / 1024.0).append("MB")
}
else -> {
appendDouble((num ushr 20) / 1024.0).append("GB")
}
}
}
private fun AnsiMessageBuilder.renderMemoryUsage(usage: MUsage) = arrayOf(
renderMemoryUsageNumber(usage.committed),
renderMemoryUsageNumber(usage.init),
renderMemoryUsageNumber(usage.used),
renderMemoryUsageNumber(usage.max),
) to longArrayOf(
usage.committed,
usage.init,
usage.used,
usage.max,
)
private var emptyLine = " ".repeat(10)
private fun Appendable.emptyLine(size: Int) {
if (emptyLine.length <= size) {
emptyLine = String(CharArray(size) { ' ' })
}
append(emptyLine, 0, size)
}
private inline fun AnsiMessageBuilder.renderMUNum(size: Int, contentLength: Int, code: () -> Unit) {
val s = size - contentLength
val left = s / 2
val right = s - left
emptyLine(left)
code()
emptyLine(right)
}
private fun calculateMax(
vararg lines: Array<String>
): IntArray = IntArray(lines[0].size) { r ->
lines.maxOf { it[r].length }
}
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware
import net.mamoe.mirai.console.command.descriptor.CommandSignature
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/**
* 指令
*
* ### 权限
* 每个指令都会被分配一个权限 [permission]. 默认没有人拥有这个权限. 请通过 [BuiltInCommands.PermissionCommand] 赋予权限.
*
* @see CommandManager.registerCommand 注册这个指令
*
* @see RawCommand 无参数解析, 接收原生参数的指令
* @see CompositeCommand 复合指令
* @see SimpleCommand 简单的, 支持参数自动解析的指令
*
* @see CommandArgumentContextAware
*/
public interface Command {
/**
* 主指令名. 将会参与构成 [Permission.id].
*
* 不允许包含 [空格][Char.isWhitespace], '.', ':'.
*/
@ResolveContext(COMMAND_NAME)
public val primaryName: String
/**
* 次要指令名
* @see Command.primaryName 获取主指令名
*/
@ResolveContext(COMMAND_NAME)
public val secondaryNames: Array<out String>
/**
* 指令可能的参数列表.
*/
@ConsoleExperimentalApi("Property name is experimental")
@ExperimentalCommandDescriptors
public val overloads: List<@JvmWildcard CommandSignature>
/**
* 用法说明, 用于发送给用户. [usage] 一般包含 [description].
*/
public val usage: String
/**
* 描述, 用于显示在 [BuiltInCommands.HelpCommand]
*/
public val description: String
/**
* 为此指令分配的权限.
*
* ### 实现约束
* - [Permission.id] 应由 [CommandOwner.permissionId] 创建. 因此保证相同的 [PermissionId.namespace]
* - [PermissionId.name] 应为 [主指令名][primaryName]
*/
public val permission: Permission
/**
* `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选.
*
* 会影响聊天语境中的解析.
*
* #### 实验性 API
* 由于指令解析允许被扩展, 此属性可能不适用所有解析器, 因此还未决定是否保留.
*/
@ExperimentalCommandDescriptors
@ConsoleExperimentalApi
public val prefixOptional: Boolean
/**
* 指令拥有者.
* @see CommandOwner
*/
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER)
public val owner: CommandOwner
public companion object {
/**
* 获取所有指令名称 (包含 [primaryName] [secondaryNames]).
*
* @return 数组大小至少为 1. 第一个元素总是 [primaryName]. 随后是保持原顺序的 [secondaryNames]
*/
@JvmStatic
public val Command.allNames: Array<String>
get() = arrayOf(primaryName, *secondaryNames)
/**
* 检查指令名的合法性. 在非法时抛出 [IllegalArgumentException]
*/
@JvmStatic
@Throws(IllegalArgumentException::class)
public fun checkCommandName(@ResolveContext(COMMAND_NAME) name: String) {
when {
name.isBlank() -> throw IllegalArgumentException("Command name should not be blank.")
name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in command name.")
name.contains(':') -> throw IllegalArgumentException("':' is forbidden in command name.")
name.contains('.') -> throw IllegalArgumentException("'.' is forbidden in command name.")
}
}
}
}

View File

@ -0,0 +1,204 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.command.resolve.InterceptedReason
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.contracts.contract
/**
* 指令的执行返回
*/
@ExperimentalCommandDescriptors
public sealed class CommandExecuteResult {
/** 指令执行时发生的错误 (如果有) */
public abstract val exception: Throwable?
/** 尝试执行的指令 (如果匹配到) */
public abstract val command: Command?
/** 解析的 [CommandCall] (如果匹配到) */
public abstract val call: CommandCall?
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public abstract val resolvedCall: ResolvedCommandCall?
// abstract val to allow smart casting
/** 指令执行成功 */
public class Success(
/** 尝试执行的指令 */
public override val command: Command,
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall,
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall,
) : CommandExecuteResult() {
/** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null
}
/** 指令执行失败 */
public abstract class Failure : CommandExecuteResult()
/** 执行执行时发生了一个非法参数错误 */
public class IllegalArgument(
/** 指令执行时发生的错误 */
public override val exception: IllegalCommandArgumentException,
/** 尝试执行的指令 */
public override val command: Command,
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall,
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall,
) : Failure()
/** 指令方法调用过程出现了错误 */
public class ExecutionFailed(
/** 指令执行时发生的错误 */
public override val exception: Throwable,
/** 尝试执行的指令 */
public override val command: Command,
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall,
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall,
) : Failure()
/** 没有匹配的指令 */
public class UnresolvedCommand(
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall?,
) : Failure() {
/** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null
/** 尝试执行的指令, 总是 `null` */
public override val command: Nothing? get() = null
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall? get() = null
}
/** 没有匹配的指令 */
public class Intercepted(
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall?,
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall?,
/** 尝试执行的指令 (如果匹配到) */
public override val command: Command?,
/** 拦截原因 */
public val reason: InterceptedReason,
) : Failure() {
/** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null
}
/** 权限不足 */
public class PermissionDenied(
/** 尝试执行的指令 */
public override val command: Command,
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall,
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall,
) : Failure() {
/** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null
}
/** 没有匹配的指令 */
public class UnmatchedSignature(
/** 尝试执行的指令 */
public override val command: Command,
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall,
/** 尝试执行的指令 */
@ExperimentalCommandDescriptors
@ConsoleExperimentalApi
public val failureReasons: List<UnmatchedCommandSignature>,
) : Failure() {
/** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall? get() = null
}
}
@ExperimentalCommandDescriptors
@ConsoleExperimentalApi
public class UnmatchedCommandSignature(
public val signature: CommandSignature,
public val failureReason: FailureReason,
)
@ExperimentalCommandDescriptors
@ConsoleExperimentalApi
public sealed class FailureReason {
public class InapplicableReceiverArgument(
public override val parameter: CommandReceiverParameter<*>,
public val argument: CommandSender,
) : InapplicableArgument()
public class InapplicableValueArgument(
public override val parameter: CommandValueParameter<*>,
public val argument: CommandValueArgument,
) : InapplicableArgument()
public abstract class InapplicableArgument : FailureReason() {
public abstract val parameter: CommandParameter<*>
}
public abstract class ArgumentLengthMismatch : FailureReason()
public data class ResolutionAmbiguity(
/**
* Including [self][UnmatchedCommandSignature.signature].
*/
public val allCandidates: List<CommandSignature>,
) : FailureReason()
public object TooManyArguments : ArgumentLengthMismatch()
public object NotEnoughArguments : ArgumentLengthMismatch()
}
/**
* [this] [CommandExecuteResult.Success] 时返回 `true`
*/
@ExperimentalCommandDescriptors
@JvmSynthetic
public fun CommandExecuteResult.isSuccess(): Boolean {
contract {
returns(true) implies (this@isSuccess is CommandExecuteResult.Success)
returns(false) implies (this@isSuccess !is CommandExecuteResult.Success)
}
return this is CommandExecuteResult.Success
}
/**
* [this] [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] , [CommandExecuteResult.UnmatchedSignature] [CommandExecuteResult.UnresolvedCommand] 时返回 `true`
*/
@ExperimentalCommandDescriptors
@JvmSynthetic
public fun CommandExecuteResult.isFailure(): Boolean {
contract {
returns(true) implies (this@isFailure !is CommandExecuteResult.Success)
returns(false) implies (this@isFailure is CommandExecuteResult.Success)
}
return this !is CommandExecuteResult.Success
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command
/**
* [CommandManager.executeCommand] 中抛出异常时包装的异常.
*/
public class CommandExecutionException(
/**
* 执行者
*/
public val sender: CommandSender,
/**
* 执行过程发生异常的指令
*/
public val command: Command,
/**
* 匹配到的指令名
*/
public val name: String,
cause: Throwable,
) : RuntimeException(
"Exception while executing command '${command.primaryName}'",
cause
) {
public override fun toString(): String =
"CommandExecutionException(command=$command, name='$name')"
}

View File

@ -0,0 +1,259 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress(
"NOTHING_TO_INLINE", "unused",
"MemberVisibilityCanBePrivate", "INAPPLICABLE_JVM_NAME"
)
@file:JvmName("CommandManagerKt")
package net.mamoe.mirai.console.command
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.command.parse.CommandCallParser
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.executeCommand
import net.mamoe.mirai.console.internal.command.executeCommandImpl
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.message.data.*
/**
* 指令管理器
*/
public interface CommandManager {
/**
* 获取所有已经注册了指令列表.
*
* @return 这一时刻的浅拷贝.
*/
public val allRegisteredCommands: List<Command>
/**
* 指令前缀, '/'
*/
public val commandPrefix: String
/**
* 获取已经注册了的属于这个 [CommandOwner] 的指令列表.
*
* @return 这一时刻的浅拷贝.
*/
public fun getRegisteredCommands(owner: CommandOwner): List<Command>
/**
* 取消注册所有属于 [owner] 的指令
*/
public fun unregisterAllCommands(owner: CommandOwner)
/**
* 注册一个指令.
*
* @param override 是否覆盖重名指令.
*
* 若原有指令 P, [Command.secondaryNames] 'a', 'b', 'c'.
* 新指令 Q, [Command.secondaryNames] 'b', 将会覆盖原指令 A 注册的 'b'.
*
* 即注册完成后, 'a' 'c' 将会解析到指令 P, 'b' 会解析到指令 Q.
*
* @return
* 若已有重名指令, [override] `false`, 返回 `false`;
* 若已有重名指令, [override] `true`, 覆盖原有指令并返回 `true`.
*
*
* 注意: [内建指令][BuiltInCommands] 也可以被覆盖.
*/
public fun registerCommand(command: Command, override: Boolean = false): Boolean
/**
* 查找并返回重名的指令. 返回重名指令.
*/
public fun findDuplicateCommand(command: Command): Command?
/**
* 取消注册这个指令.
*
* 若指令未注册, 返回 `false`.
*/
public fun unregisterCommand(command: Command): Boolean
/**
* [command] 已经 [注册][registerCommand] 时返回 `true`
*/
public fun isCommandRegistered(command: Command): Boolean
/**
* 解析并执行一个指令.
*
* ### 指令解析流程
* 1. [CommandCallParser] [MessageChain] 解析为 [CommandCall]
* 2. [CommandCallResolver] [CommandCall] 解析为 [ResolvedCommandCall]
* 1. [message] 的第一个消息元素的 [内容][Message.contentToString] 被作为指令名, 在已注册指令列表中搜索. (包含 [Command.prefixOptional] 相关的处理)
* 2. 参数语法分析.
* 在当前的实现下, [message] 被以空格和 [SingleMessage] 分割.
* "MessageChain("foo bar", [Image], " test")" 被分割为 "foo", "bar", [Image], "test".
* 注意: 字符串与消息元素之间不需要空格, 会被强制分割. "bar[mirai:image:]" 会被分割为 "bar" [Image] 类型的消息元素.
* 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand]
*
* ### 扩展
* 参数语法分析过程可能会被扩展, 插件可以自定义处理方式 ([CommandCallParser]), 因此可能不会简单地使用空格分隔.
*
* @param message 一条完整的指令. "/managers add 123456.123456"
* @param checkPermission `true` 时检查权限
*
* @see CommandCallParser
* @see CommandCallResolver
*
* @see CommandSender.executeCommand
* @see Command.execute
*
* @return 执行结果
*/
@ExperimentalCommandDescriptors
@JvmBlockingBridge
public suspend fun executeCommand(
caller: CommandSender,
message: Message,
checkPermission: Boolean = true,
): CommandExecuteResult {
return executeCommandImpl(message, caller, checkPermission)
}
/**
* 执行一个确切的指令
*
* @param command 目标指令
* @param arguments 参数列表
*
* @see executeCommand 获取更多信息
* @see Command.execute
*/
@ConsoleExperimentalApi
@JvmName("executeCommand")
@ExperimentalCommandDescriptors
@JvmSynthetic
public suspend fun executeCommand(
sender: CommandSender,
command: Command,
arguments: Message = EmptyMessageChain,
checkPermission: Boolean = true,
): CommandExecuteResult {
// TODO: 2020/10/18 net.mamoe.mirai.console.command.CommandManager.execute
val chain = buildMessageChain {
append(CommandManager.commandPrefix)
append(command.primaryName)
append(' ')
append(arguments)
}
return CommandManager.executeCommand(sender, chain, checkPermission)
}
/**
* [指令名称][commandName] 匹配对应的 [Command].
*
* #### 实现细节
* - [commandName] 带有 [commandPrefix] 时可以匹配到所有指令
* - [commandName] 不带有 [commandPrefix] 时只能匹配到 [Command.prefixOptional] 的指令
*
* @param commandName 可能带有或不带有 [commandPrefix].
*/
public fun matchCommand(commandName: String): Command?
public companion object INSTANCE : CommandManager by CommandManagerImpl {
/**
* @see CommandManager.getRegisteredCommands
*/
@get:JvmName("registeredCommands0")
@get:JvmSynthetic
public inline val CommandOwner.registeredCommands: List<Command>
get() = getRegisteredCommands(this)
/**
* @see CommandManager.registerCommand
*/
@JvmSynthetic
public inline fun Command.register(override: Boolean = false): Boolean = registerCommand(this, override)
/**
* @see CommandManager.unregisterCommand
*/
@JvmSynthetic
public inline fun Command.unregister(): Boolean = unregisterCommand(this)
/**
* @see CommandManager.isCommandRegistered
*/
@get:JvmSynthetic
public inline val Command.isRegistered: Boolean
get() = isCommandRegistered(this)
/**
* @see CommandManager.unregisterAll
*/
@JvmSynthetic
public inline fun CommandOwner.unregisterAll(): Unit = unregisterAllCommands(this)
/**
* @see CommandManager.findDuplicate
*/
@JvmSynthetic
public inline fun Command.findDuplicate(): Command? = findDuplicateCommand(this)
}
}
/**
* 解析并执行一个指令
*
* @param message 一条完整的指令. "/managers add 123456.123456"
* @param checkPermission `true` 时检查权限
*
* @return 执行结果
* @see executeCommand
*/
@JvmName("execute0")
@ExperimentalCommandDescriptors
@JvmSynthetic
public suspend inline fun CommandSender.executeCommand(
message: String,
checkPermission: Boolean = true,
): CommandExecuteResult = CommandManager.executeCommand(this, PlainText(message).toMessageChain(), checkPermission)
/**
* 执行一个确切的指令
* @see executeCommand 获取更多信息
*/
@JvmName("execute0")
@ExperimentalCommandDescriptors
@JvmSynthetic
public suspend inline fun Command.execute(
sender: CommandSender,
vararg arguments: Message = emptyArray(),
checkPermission: Boolean = true,
): CommandExecuteResult = CommandManager.executeCommand(sender, this, arguments.toMessageChain(), checkPermission)
/**
* 执行一个确切的指令
* @see executeCommand 获取更多信息
*/
@JvmName("execute0")
@ExperimentalCommandDescriptors
@JvmSynthetic
public suspend inline fun Command.execute(
sender: CommandSender,
arguments: String = "",
checkPermission: Boolean = true,
): CommandExecuteResult = execute(sender, PlainText(arguments), checkPermission = checkPermission)

View File

@ -0,0 +1,45 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAME
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermissionIdNamespace
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
/**
* 指令的所有者.
*
* @see CommandManager.unregisterAllCommands 取消注册所有属于一个 [CommandOwner] 的指令
* @see CommandManager.registeredCommands 获取已经注册了的属于一个 [CommandOwner] 的指令列表.
*
* @see JvmPlugin 是一个 [CommandOwner]
*/
public interface CommandOwner : PermissionIdNamespace {
/**
* 在构造指令时, [Command.permission] 默认会使用 [parentPermission] 作为 [Permission.parent]
*/
public val parentPermission: Permission
}
/**
* 代表控制台所有者. 所有的 mirai-console 内建的指令都属于 [ConsoleCommandOwner].
*
* 插件注册指令时不应该使用 [ConsoleCommandOwner].
*/
public object ConsoleCommandOwner : CommandOwner {
public override val parentPermission: Permission get() = BuiltInCommands.parentPermission
public override fun permissionId(
@ResolveContext(PERMISSION_NAME) name: String,
): PermissionId = PermissionId("console", name)
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command
/**
* [CommandManager.executeCommand] , [CommandSender] 未拥有 [Command.permission] 所要求的权限时抛出的异常.
*
* 总是作为 [CommandExecutionException.cause].
*/
public class CommandPermissionDeniedException(
/**
* 执行者
*/
public val commandSender: CommandSender,
/**
* 执行过程发生异常的指令
*/
public val command: Command,
) : RuntimeException("Permission denied while executing command '${command.primaryName}'") {
public override fun toString(): String =
"CommandPermissionDeniedException(command=$command)"
}

View File

@ -0,0 +1,736 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress(
"NOTHING_TO_INLINE", "FunctionName",
"unused", "MemberVisibilityCanBePrivate"
)
package net.mamoe.mirai.console.command
import kotlinx.coroutines.CoroutineScope
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender
import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender
import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.data.castOrNull
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
import net.mamoe.mirai.console.permission.AbstractPermitteeId
import net.mamoe.mirai.console.permission.Permittee
import net.mamoe.mirai.console.permission.PermitteeId
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
import net.mamoe.mirai.console.util.MessageScope
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.PlainText
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
/**
* 指令发送者.
*
* 只有 [CommandSender] 才能 [执行指令][CommandManager.executeCommand]
*
* ## 获得指令发送者
* - [MessageEvent.toCommandSender]
* - [FriendMessageEvent.toCommandSender]
* - [GroupMessageEvent.toCommandSender]
* - [TempMessageEvent.toCommandSender]
* - [StrangerMessageEvent.toCommandSender]
* - [OtherClientMessageEvent.toCommandSender]
*
* - [Member.asCommandSender]
* - [NormalMember.asTempCommandSender]
* - [Member.asMemberCommandSender]
* - [Friend.asCommandSender]
* - [User.asCommandSender]
* - [Stranger.asCommandSender]
* - [OtherClient.asCommandSender]
*
* ## 实现 [CommandSender]
* Console 前端外, 在任何时候都不要实现 [CommandSender] (包括使用委托). 必须使用上述扩展获取 [CommandSender] 实例.
*
* Console 前端可实现 [ConsoleCommandSender]
*
* ## 子类型
*
* 所有 [CommandSender] 都应继承 [AbstractCommandSender].
*
* [AbstractCommandSender] 是密封类, 一级子类为:
* - [AbstractUserCommandSender] 代表用户
* - [ConsoleCommandSender] 代表控制台
*
* 二级子类, 当指令由插件 [主动执行][CommandManager.executeCommand] , 插件应使用 [toCommandSender] [asCommandSender], 因此,
* - 若在群聊环境, 对应 [CommandSender] [MemberCommandSender]
* - 若在私聊环境, 对应 [CommandSender] [FriendCommandSender]
* - 若在临时会话环境, 对应 [CommandSender] [TempCommandSender]
* - 若在陌生人会话环境, 对应 [CommandSender] [StrangerCommandSender]
* - 若在其他客户端会话环境, 对应 [CommandSender] [OtherClientCommandSender]
*
* 三级子类, 当真实收到由用户执行的指令时:
* - 若在群聊环境, 对应 [CommandSender] [MemberCommandSenderOnMessage]
* - 若在私聊环境, 对应 [CommandSender] [FriendCommandSenderOnMessage]
* - 若在临时会话环境, 对应 [CommandSender] [TempCommandSenderOnMessage]
* - 若在陌生人会话环境, 对应 [CommandSender] [StrangerCommandSenderOnMessage]
* - 若在其他客户端会话环境, 对应 [CommandSender] [OtherClientCommandSenderOnMessage]
*
*
* 类型关系如图. 箭头指向的是父类.
*
* ```
* CoroutineScope
*
* |
* CommandSender <---------+---------------+-------------------------------+
* | | |
* | | | |
* | UserCommandSender GroupAwareCommandSender CommandSenderOnMessage
* |
* | | | |
* AbstractCommandSender | | |
* | | |
* | sealed | | |
* +-------------+-------------+ | | |
* | | | | |
* | | | | | }
* ConsoleCommandSender AbstractUserCommandSender | | } 一级子类
* | | }
* | sealed | |
* | | |
* +----------------------+ | |
* | | | |
* | +------+------------+---------------+ |
* | | | |
* | | | | }
* FriendCommandSender MemberCommandSender TempCommandSender | } 二级子类
* | }
* | | | |
* | | | | }
* FriendCommandSenderOnMessage MemberCommandSenderOnMessage TempCommandSenderOnMessage | } 三级子类
* | | | | }
* | | | |
* +-----------------------------+----------------------------+---------------+
* ```
*
* ## Scoping: [MessageScope]
* 在处理多个消息对象时, 可通过 [MessageScope] 简化操作.
*
* 查看 [MessageScope] 以获取更多信息.
*
* @see ConsoleCommandSender 控制台
* @see UserCommandSender [User] ([群成员][Member], [好友][Friend])
* @see toCommandSender
* @see asCommandSender
*/
public interface CommandSender : CoroutineScope, Permittee {
/**
* 与这个 [CommandSender] 相关的 [Bot].
* 当通过控制台执行时为 `null`.
*/
public val bot: Bot?
/**
* 与这个 [CommandSender] 相关的 [Contact].
*
* - 当一个群员执行指令时, [subject] 为所在 [][Group]
* - 当通过控制台执行时为 `null`.
*/
public val subject: Contact?
/**
* 指令原始发送**.
* - 当通过控制台执行时为 `null`.
*/
public val user: User?
/**
* [User.nameCardOrNick] [ConsoleCommandSender.NAME]
*/
public val name: String
/**
* 立刻发送一条消息.
*
* 对于 [MemberCommandSender], 这个函数总是发送给所在群
*/
@JvmBlockingBridge
public suspend fun sendMessage(message: Message): MessageReceipt<Contact>?
/**
* 立刻发送一条消息.
*
* 对于 [MemberCommandSender], 这个函数总是发送给所在群
*/
@JvmBlockingBridge
public suspend fun sendMessage(message: String): MessageReceipt<Contact>?
public companion object {
///////////////////////////////////////////////////////////////////////////
// Constructors
///////////////////////////////////////////////////////////////////////////
/**
* 构造 [FriendCommandSenderOnMessage]
*/
@JvmName("from")
@JvmStatic
public fun FriendMessageEvent.toCommandSender(): FriendCommandSenderOnMessage =
FriendCommandSenderOnMessage(this)
/**
* 构造 [MemberCommandSenderOnMessage]
*/
@JvmName("from")
@JvmStatic
public fun GroupMessageEvent.toCommandSender(): MemberCommandSenderOnMessage =
MemberCommandSenderOnMessage(this)
/**
* 构造 [TempCommandSenderOnMessage]
*/
@JvmStatic
@JvmName("from")
public fun GroupTempMessageEvent.toCommandSender(): GroupTempCommandSenderOnMessage =
GroupTempCommandSenderOnMessage(this)
/**
* 构造 [StrangerCommandSenderOnMessage]
*/
@JvmStatic
@JvmName("from")
public fun StrangerMessageEvent.toCommandSender(): StrangerCommandSenderOnMessage =
StrangerCommandSenderOnMessage(this)
/**
* 构造 [OtherClientCommandSenderOnMessage]
*/
@JvmStatic
@JvmName("from")
public fun OtherClientMessageEvent.toCommandSender(): OtherClientCommandSenderOnMessage =
OtherClientCommandSenderOnMessage(this)
/**
* 构造 [CommandSenderOnMessage]
*/
@JvmStatic
@JvmName("from")
@Suppress("UNCHECKED_CAST")
public fun <T : MessageEvent> T.toCommandSender(): CommandSenderOnMessage<T> = when (this) {
is FriendMessageEvent -> toCommandSender()
is GroupMessageEvent -> toCommandSender()
is GroupTempMessageEvent -> toCommandSender()
is StrangerMessageEvent -> toCommandSender()
is OtherClientMessageEvent -> toCommandSender()
else -> throw IllegalArgumentException("Unsupported MessageEvent: ${this::class.qualifiedNameOrTip}")
} as CommandSenderOnMessage<T>
/**
* 得到 [TempCommandSender]
*/
@JvmStatic
@JvmName("of")
public fun NormalMember.asTempCommandSender(): GroupTempCommandSender = GroupTempCommandSender(this)
/**
* 得到 [MemberCommandSender]
*/
@JvmStatic
@JvmName("of")
public fun Member.asMemberCommandSender(): MemberCommandSender = MemberCommandSender(this)
/**
* 得到 [MemberCommandSender] [TempCommandSender]
* @see asTempCommandSender
* @see asMemberCommandSender
*/
@JvmStatic
@JvmName("of")
public fun Member.asCommandSender(isTemp: Boolean): UserCommandSender {
return if (isTemp && this is NormalMember) asTempCommandSender() else asMemberCommandSender()
}
/**
* 得到 [FriendCommandSender]
*/
@JvmStatic
@JvmName("of")
public fun Friend.asCommandSender(): FriendCommandSender = FriendCommandSender(this)
/**
* 得到 [StrangerCommandSender]
*/
@JvmStatic
@JvmName("of")
public fun Stranger.asCommandSender(): StrangerCommandSender = StrangerCommandSender(this)
/**
* 得到 [OtherClientCommandSender]
*/
@JvmStatic
@JvmName("of")
public fun OtherClient.asCommandSender(): OtherClientCommandSender = OtherClientCommandSender(this)
/**
* 得到 [UserCommandSender]
*
* @param isTemp
*/
@JvmStatic
@JvmName("of")
public fun User.asCommandSender(isTemp: Boolean): UserCommandSender = when (this) {
is Friend -> this.asCommandSender()
is Member -> if (isTemp && this is NormalMember) GroupTempCommandSender(this) else MemberCommandSender(this)
is Stranger -> this.asCommandSender()
else -> error("stub")
}
}
}
/**
* 所有 [CommandSender] 都必须继承自此对象.
* @see CommandSender 查看更多信息
*/
public sealed class AbstractCommandSender : CommandSender, CoroutineScope {
public abstract override val bot: Bot?
public abstract override val subject: Contact?
public abstract override val user: User?
public abstract override fun toString(): String
}
/**
* [this] [ConsoleCommandSender] 时返回 `true`
*/
public fun CommandSender.isConsole(): Boolean {
contract {
returns(true) implies (this@isConsole is ConsoleCommandSender)
}
return this is ConsoleCommandSender
}
/**
* [this] 不为 [ConsoleCommandSender] 时返回 `true`
*/
public fun CommandSender.isNotConsole(): Boolean {
contract {
returns(true) implies (this@isNotConsole !is ConsoleCommandSender)
}
return this !is ConsoleCommandSender
}
/**
* [this] [UserCommandSender] 时返回 `true`
*/
public fun CommandSender.isUser(): Boolean {
contract {
returns(true) implies (this@isUser is UserCommandSender)
}
return this is UserCommandSender
}
/**
* [this] 不为 [UserCommandSender], 即为 [ConsoleCommandSender] 时返回 `true`
*/
public fun CommandSender.isNotUser(): Boolean {
contract {
returns(true) implies (this@isNotUser is ConsoleCommandSender)
}
return this !is UserCommandSender
}
/**
* 折叠 [AbstractCommandSender] 的可能性.
*
* - [this] [ConsoleCommandSender] 时执行 [ifIsConsole]
* - [this] [UserCommandSender] 时执行 [ifIsUser]
* - 否则执行 [otherwise]
*
* ### 示例
* ```
* // 当一个指令执行过程出错
* val exception: Exception = ...
*
* sender.fold(
* ifIsConsole = { // this: ConsoleCommandSender
* sendMessage(exception.stackTraceToString()) // 展示整个 stacktrace
* },
* ifIsUser = { // this: UserCommandSender
* sendMessage(exception.message.toString()) // 只展示 Exception.message
* }
* )
* ```
*
* @return [ifIsConsole], [ifIsUser] [otherwise] 执行结果.
*/
@JvmSynthetic
public inline fun <R> CommandSender.fold(
ifIsConsole: ConsoleCommandSender.() -> R,
ifIsUser: UserCommandSender.() -> R,
otherwise: CommandSender.() -> R = { error("CommandSender ${this::class.qualifiedName} is not supported") },
): R {
contract {
callsInPlace(ifIsConsole, InvocationKind.AT_MOST_ONCE)
callsInPlace(ifIsUser, InvocationKind.AT_MOST_ONCE)
callsInPlace(otherwise, InvocationKind.AT_MOST_ONCE)
}
return when (val sender = this) {
is ConsoleCommandSender -> ifIsConsole(sender)
is UserCommandSender -> ifIsUser(sender)
else -> otherwise(sender)
}
}
/**
* 折叠 [AbstractCommandSender] 的两种可能性, 即在群内发送或在私聊环境发送.
*
* - [this] [MemberCommandSender] 时执行 [inGroup]
* - [this] [TempCommandSender] [FriendCommandSender] 时执行 [inPrivate]
*
* ### 实验性 API
*
* 这是预览版本 API. 如果你对 [UserCommandSender.foldContext] 有建议, 请在 [mamoe/mirai-console/issues](https://github.com/mamoe/mirai-console/issues/new) 提交.
*
* @return [inGroup] [inPrivate] 执行结果.
*/
@JvmSynthetic
@ConsoleExperimentalApi
public inline fun <R> UserCommandSender.foldContext(
inGroup: MemberCommandSender.() -> R,
inPrivate: UserCommandSender.() -> R,
): R {
contract {
callsInPlace(inGroup, InvocationKind.AT_MOST_ONCE)
callsInPlace(inPrivate, InvocationKind.AT_MOST_ONCE)
}
return when (val sender = this) {
is MemberCommandSender -> inGroup(sender)
else -> inPrivate(sender)
}
}
/**
* 尝试获取 [Group].
*
* [GroupAwareCommandSender] 时返回 [GroupAwareCommandSender.group], 否则返回 `null`
*
* ### 契约
* 本函数定义契约,
* - 若返回非 `null` 实例, Kotlin 编译器认为 [this] [GroupAwareCommandSender] 实例并执行智能类型转换.
* - 若返回 `null`, Kotlin 编译器认为 [this] [FriendCommandSender] 实例并执行智能类型转换.
*/
public fun CommandSender.getBotOrNull(): Bot? {
contract {
returns(null) implies (this@getBotOrNull is AbstractUserCommandSender)
returnsNotNull() implies (this@getBotOrNull is ConsoleCommandSender)
}
return this.castOrNull<UserCommandSender>()?.bot
}
/**
* 控制台指令执行者. 代表由控制台执行指令
*
* 控制台拥有一切指令的执行权限.
*/
public object ConsoleCommandSender : AbstractCommandSender() {
public const val NAME: String = "ConsoleCommandSender"
public override val bot: Nothing? get() = null
public override val subject: Nothing? get() = null
public override val user: Nothing? get() = null
public override val name: String get() = NAME
public override fun toString(): String = NAME
public override val permitteeId: AbstractPermitteeId.Console = AbstractPermitteeId.Console
public override val coroutineContext: CoroutineContext by lazy { MiraiConsole.childScopeContext(NAME) }
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): Nothing? {
MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message)
return null
}
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): Nothing? {
MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message)
return null
}
}
/**
* 知道 [Group] 环境的 [UserCommandSender]
*
* 可能的子类:
*
* - [MemberCommandSender] 代表一个 [群员][Member] 执行指令
* - [TempCommandSender] 代表一个 [群员][Member] 通过临时会话执行指令
*/
public interface GroupAwareCommandSender : UserCommandSender {
public val group: Group
}
/**
* 尝试获取 [Group].
*
* [GroupAwareCommandSender] 时返回 [GroupAwareCommandSender.group], 否则返回 `null`
*
* ### 契约
* 本函数定义契约,
* - 若返回非 `null` 实例, Kotlin 编译器认为 [this] [GroupAwareCommandSender] 实例并执行智能类型转换.
* - 若返回 `null`, Kotlin 编译器认为 [this] [FriendCommandSender] 实例并执行智能类型转换.
*/
public fun CommandSender.getGroupOrNull(): Group? {
contract {
returns(null) implies (this@getGroupOrNull is FriendCommandSender)
returnsNotNull() implies (this@getGroupOrNull is GroupAwareCommandSender)
}
return this.castOrNull<GroupAwareCommandSender>()?.group
}
///////////////////////////////////////////////////////////////////////////
// UserCommandSender
///////////////////////////////////////////////////////////////////////////
/**
* 代表一个用户执行指令
*
* @see MemberCommandSender 代表一个 [群员][Member] 执行指令
* @see FriendCommandSender 代表一个 [好友][Friend] 执行指令
* @see TempCommandSender 代表一个 [群员][Member] 通过临时会话执行指令
* @see StrangerCommandSender 代表一个 [陌生人][Stranger] 执行指令
*
* @see CommandSenderOnMessage
*/
public interface UserCommandSender : CommandSender {
/**
* @see MessageEvent.sender
*/
public override val user: User // override nullability
/**
* @see MessageEvent.subject
*/
public override val subject: Contact // override nullability
public override val bot: Bot // override nullability
}
/**
* [UserCommandSender] 的实现
*/
public sealed class AbstractUserCommandSender : UserCommandSender, AbstractCommandSender() {
public override val bot: Bot get() = user.bot // don't final
public final override val name: String get() = user.nameCardOrNick
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Contact> = sendMessage(PlainText(message))
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<Contact> = user.sendMessage(message)
}
/**
* 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand])
* @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令
*/
public open class FriendCommandSender internal constructor(
public final override val user: Friend,
) : AbstractUserCommandSender(), CoroutineScope by user.childScope("FriendCommandSender") {
public override val subject: Contact get() = user
public override fun toString(): String = "FriendCommandSender($user)"
public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactFriend(user.id)
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Friend> = sendMessage(PlainText(message))
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<Friend> = user.sendMessage(message)
}
/**
* 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand])
* @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令.
*/
public open class MemberCommandSender internal constructor(
public final override val user: Member,
) : AbstractUserCommandSender(), GroupAwareCommandSender, CoroutineScope by user.childScope("MemberCommandSender") {
public final override val group: Group get() = user.group
public override val subject: Group get() = group
public override fun toString(): String = "MemberCommandSender($user)"
public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactMember(group.id, user.id)
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Group> = sendMessage(PlainText(message))
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<Group> = subject.sendMessage(message)
}
/**
* 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand])
* @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令
*/
@Deprecated(
"mirai 正计划支持其他渠道发起的临时会话, 届时此事件会变动. 原 TempCommandSender 已更改为 GroupTempCommandSender",
replaceWith = ReplaceWith("GroupTempCommandSender", "net.mamoe.mirai.console.command.GroupTempCommandSender"),
DeprecationLevel.ERROR
)
public sealed class TempCommandSender(
public override val user: NormalMember,
) : AbstractUserCommandSender(), GroupAwareCommandSender, CoroutineScope by user.childScope("TempCommandSender")
/**
* 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand])
* @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令
*/
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
public open class GroupTempCommandSender internal constructor(
public final override val user: NormalMember,
) : @Suppress("DEPRECATION_ERROR") TempCommandSender(user),
CoroutineScope by user.childScope("GroupTempCommandSender") {
public override val group: Group get() = user.group
public override val subject: NormalMember get() = user
public override fun toString(): String = "GroupTempCommandSender($user)"
public override val permitteeId: PermitteeId =
AbstractPermitteeId.ExactGroupTemp(user.group.id, user.id)
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Member> = sendMessage(PlainText(message))
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<Member> = user.sendMessage(message)
}
/**
* 代表一个 [陌生人][Stranger] 通过私聊执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand])
* @see StrangerCommandSenderOnMessage 代表一个 [陌生人][Stranger] 主动在私聊发送消息执行指令
*/
public open class StrangerCommandSender internal constructor(
public final override val user: Stranger,
) : AbstractUserCommandSender(), CoroutineScope by user.childScope("StrangerCommandSender") {
public override val subject: Stranger get() = user
public override fun toString(): String = "StrangerCommandSender($user)"
public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactStranger(user.id)
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Stranger> = sendMessage(PlainText(message))
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<Stranger> = user.sendMessage(message)
}
/**
* 代表一个 [其他客户端][OtherClient] 通过私聊执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand])
* @see OtherClientCommandSenderOnMessage 代表一个[其他客户端][OtherClient] 主动在私聊发送消息执行指令
*/
public open class OtherClientCommandSender internal constructor(
public val client: OtherClient,
) : AbstractCommandSender(), CoroutineScope by client.childScope("OtherClientCommandSender") {
public final override val user: Friend get() = client.bot.asFriend
public final override val bot: Bot get() = client.bot
public final override val name: String get() = client.bot.nick
public override val subject: Friend get() = user
public override fun toString(): String = "OtherClientCommandSender($user)"
public override val permitteeId: PermitteeId = AbstractPermitteeId.AnyOtherClient
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<OtherClient> =
sendMessage(PlainText(message))
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<OtherClient> = client.sendMessage(message)
}
///////////////////////////////////////////////////////////////////////////
// CommandSenderOnMessage
///////////////////////////////////////////////////////////////////////////
/**
* 代表一个真实 [用户][User] 主动私聊机器人或在群内发送消息而执行指令
*
* @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令.
* @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令
* @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令
*/
public interface CommandSenderOnMessage<T : MessageEvent> : CommandSender {
/**
* 消息源 [MessageEvent]
*/
public val fromEvent: T
}
/**
* 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令
* @see FriendCommandSender 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式
*/
public class FriendCommandSenderOnMessage internal constructor(
public override val fromEvent: FriendMessageEvent,
) : FriendCommandSender(fromEvent.sender), CommandSenderOnMessage<FriendMessageEvent>
/**
* 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令.
* @see MemberCommandSender 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式
*/
public class MemberCommandSenderOnMessage internal constructor(
public override val fromEvent: GroupMessageEvent,
) : MemberCommandSender(fromEvent.sender), CommandSenderOnMessage<GroupMessageEvent>
/**
* 代表一个 [群员][Member] 主动在临时会话发送消息执行指令
* @see TempCommandSender 代表一个 [群员][Member] 执行指令, 但不一定是通过私聊方式
*/
@Deprecated(
"mirai 正计划支持其他渠道发起的临时会话, 届时此事件会变动. 原 TempCommandSenderOnMessage 已更改为 GroupTempCommandSenderOnMessage",
replaceWith = ReplaceWith(
"GroupTempCommandSenderOnMessage",
"net.mamoe.mirai.console.command.GroupTempCommandSenderOnMessage"
),
DeprecationLevel.ERROR
)
public sealed class TempCommandSenderOnMessage(
public override val fromEvent: GroupTempMessageEvent,
) : GroupTempCommandSender(fromEvent.sender), CommandSenderOnMessage<GroupTempMessageEvent>
/**
* 代表一个 [群员][Member] 主动在临时会话发送消息执行指令
* @see TempCommandSender 代表一个 [群员][Member] 执行指令, 但不一定是通过私聊方式
*/
public class GroupTempCommandSenderOnMessage internal constructor(
public override val fromEvent: GroupTempMessageEvent,
) : @Suppress("DEPRECATION_ERROR") TempCommandSenderOnMessage(fromEvent), CommandSenderOnMessage<GroupTempMessageEvent>
/**
* 代表一个 [陌生人][Stranger] 主动在私聊发送消息执行指令
* @see StrangerCommandSender 代表一个 [陌生人][Stranger] 执行指令, 但不一定是通过私聊方式
*/
public class StrangerCommandSenderOnMessage internal constructor(
public override val fromEvent: StrangerMessageEvent,
) : StrangerCommandSender(fromEvent.sender), CommandSenderOnMessage<StrangerMessageEvent>
/**
* 代表一个 [其他客户端][OtherClient] 主动在私聊发送消息执行指令
* @see OtherClientCommandSender 代表一个 [其他客户端][OtherClient] 执行指令, 但不一定是通过私聊方式
*/
public class OtherClientCommandSenderOnMessage internal constructor(
public override val fromEvent: OtherClientMessageEvent,
) : OtherClientCommandSender(fromEvent.client), CommandSenderOnMessage<OtherClientMessageEvent>

View File

@ -0,0 +1,134 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.java.JCompositeCommand
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
import net.mamoe.mirai.console.internal.command.CommandReflector
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.FUNCTION
/**
* 复合指令. 指令注册时候会通过反射构造指令解析器.
*
* Java 示例查看 [JCompositeCommand].
*
* Kotlin 示例:
* ```
* @OptIn(ConsoleExperimentalAPI::class)
* object MyCompositeCommand : CompositeCommand(
* MyPluginMain, "manage", // "manage" 是主指令名
* description = "示例指令", permission = MyCustomPermission,
* // prefixOptional = true // 还有更多参数可填, 此处忽略
* ) {
*
* // [参数智能解析]
* //
* // 在控制台执行 "/manage <群号>.<群员> <持续时间>",
* // 或在聊天群内发送 "/manage <@一个群员> <持续时间>",
* // 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>",
* // 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>"
* // 时调用这个函数
* @SubCommand // 表示这是一个子指令,使用函数名作为子指令名称
* suspend fun CommandSender.mute(target: Member, duration: Int) { // 通过 /manage mute <target> <duration> 调用
* sendMessage("/manage mute 被调用了, 参数为: $target, $duration")
*
* val result = kotlin.runCatching {
* target.mute(duration).toString()
* }.getOrElse {
* it.stackTraceToString()
* } // 失败时返回堆栈信息
*
* sendMessage("结果: $result")
* }
*
* @SubCommand
* suspend fun ConsoleCommandSender.foo() {
* // 使用 ConsoleCommandSender 作为接收者,表示指令只能由控制台执行。
* // 当用户尝试在聊天环境执行时将会收到错误提示。
* }
*
* @SubCommand("list", "查看列表") // 可以设置多个子指令名。此时函数名会被忽略。
* suspend fun CommandSender.list() { // 执行 "/manage list" 时调用这个函数
* sendMessage("/manage list 被调用了")
* }
*
* // 支持 Image 类型, 需在聊天中执行此指令.
* @SubCommand
* suspend fun UserCommandSender.test(image: Image) { // 执行 "/manage test <一张图片>" 时调用这个函数
* // 由于 Image 类型消息只可能在聊天环境,可以直接使用 UserCommandSender。
* sendMessage("/manage image 被调用了, 图片是 ${image.imageId}")
* }
* }
* ```
*
* @see buildCommandArgumentContext
*/
public abstract class CompositeCommand(
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
@ResolveContext(COMMAND_NAME) primaryName: String,
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
description: String = "no description available",
parentPermission: Permission = owner.parentPermission,
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission),
CommandArgumentContextAware {
private val reflector by lazy { CommandReflector(this, CompositeCommandSubCommandAnnotationResolver) }
@ExperimentalCommandDescriptors
public final override val overloads: List<@JvmWildcard CommandSignatureFromKFunction> by lazy {
reflector.findSubCommands().also {
reflector.validate(it)
}
}
/**
* 自动根据带有 [SubCommand] 注解的函数签名生成 [usage]. 也可以被覆盖.
*/
public override val usage: String by lazy {
@OptIn(ExperimentalCommandDescriptors::class)
reflector.generateUsage(overloads)
}
/**
* [CommandValueArgumentParser] 的环境
*/
public final override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
/**
* 标记一个函数为子指令, [value] 为空时使用函数名.
* @param value 子指令名
*/
@Retention(RUNTIME)
@Target(FUNCTION)
protected annotation class SubCommand(
@ResolveContext(COMMAND_NAME) vararg val value: String = [],
)
/** 指令描述 */
@Retention(RUNTIME)
@Target(FUNCTION)
protected annotation class Description(val value: String)
/** 参数名, 将参与构成 [usage] */
@ConsoleExperimentalApi("Classname might change")
@Retention(RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
protected annotation class Name(val value: String)
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
/**
* 在处理参数时遇到的 _正常_ 错误. 如参数不符合规范, 参数值越界等.
*
* [message] 将会发送给指令调用方.
*
* 如果指令调用方是 [ConsoleCommandSender],
* 还会将 [cause], [suppressedExceptions] 发送给 [ConsoleCommandSender] (如果存在)
*
* @see CommandArgumentParserException
*/
public open class IllegalCommandArgumentException @JvmOverloads constructor(
message: String,
cause: Throwable? = null,
) : IllegalArgumentException(message, cause)

View File

@ -0,0 +1,87 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.java.JRawCommand
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
import net.mamoe.mirai.console.internal.command.findOrCreateCommandPermission
import net.mamoe.mirai.console.internal.data.typeOf0
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.buildMessageChain
/**
* 无参数解析, 接收原生参数的指令.
*
* ### 指令执行流程
* [CommandManager.executeCommand] 所述第 3 , [RawCommand] 不会对参数做任何解析.
*
* @see JRawCommand Java 用户继承.
*
* @see SimpleCommand 简单指令
* @see CompositeCommand 复合指令
*/
public abstract class RawCommand(
/**
* 指令拥有者.
* @see CommandOwner
*/
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER)
public override val owner: CommandOwner,
/** 主指令名. */
@ResolveContext(COMMAND_NAME)
public override val primaryName: String,
/** 次要指令名. */
@ResolveContext(COMMAND_NAME)
public override vararg val secondaryNames: String,
/** 用法说明, 用于发送给用户 */
public override val usage: String = "<no usages given>",
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
public override val description: String = "<no descriptions given>",
/** 指令父权限 */
parentPermission: Permission = owner.parentPermission,
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
@OptIn(ExperimentalCommandDescriptors::class)
public override val prefixOptional: Boolean = false,
) : Command {
public override val permission: Permission by lazy { findOrCreateCommandPermission(parentPermission) }
@ExperimentalCommandDescriptors
override val overloads: List<@JvmWildcard CommandSignature> = listOf(
CommandSignatureImpl(
receiverParameter = CommandReceiverParameter(false, typeOf0<CommandSender>()),
valueParameters = listOf(
AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>(
"args",
true
)
)
) { call ->
val sender = call.caller
val arguments = call.rawValueArguments
sender.onCommand(buildMessageChain { arguments.forEach { +it.value } })
}
)
/**
* 在指令被执行时调用.
*
* @param args 指令参数.
*
* @see CommandManager.executeCommand 查看更多信息
*/
public abstract suspend fun CommandSender.onCommand(args: MessageChain)
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.java.JSimpleCommand
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
import net.mamoe.mirai.console.internal.command.CommandReflector
import net.mamoe.mirai.console.internal.command.IllegalCommandDeclarationException
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
/**
* 简单的, 支持参数自动解析的指令.
*
* 要查看指令解析流程, 参考 [CommandManager.executeCommand].
* 要查看参数解析方式, 参考 [CommandValueArgumentParser].
*
* Java 示例查看 [JSimpleCommand].
*
* Kotlin 示例:
* ```
* object MySimpleCommand : SimpleCommand(
* MyPlugin, "tell",
* description = "Message somebody"
* ) {
* @Handler
* suspend fun CommandSender.onCommand(target: User, message: String) {
* target.sendMessage(message)
* }
* }
* ```
*
* @see JSimpleCommand Java 实现
* @see [CommandManager.executeCommand]
*/
public abstract class SimpleCommand(
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
@ResolveContext(COMMAND_NAME) primaryName: String,
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
description: String = "no description available",
parentPermission: Permission = owner.parentPermission,
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission),
CommandArgumentContextAware {
private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) }
@ExperimentalCommandDescriptors
public final override val overloads: List<@JvmWildcard CommandSignatureFromKFunction> by lazy {
reflector.findSubCommands().also {
reflector.validate(it)
if (it.isEmpty())
throw IllegalCommandDeclarationException(
this,
"SimpleCommand must have at least one subcommand, whereas zero present."
)
}
}
/**
* 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖.
*/
public override val usage: String by lazy {
@OptIn(ExperimentalCommandDescriptors::class)
reflector.generateUsage(overloads)
}
/**
* 标注指令处理器
*/
@Target(FUNCTION)
protected annotation class Handler
/** 参数名, 将参与构成 [usage] */
@ConsoleExperimentalApi("Classname might change")
@Target(VALUE_PARAMETER)
protected annotation class Name(val value: String)
/**
* 指令参数环境. 默认为 [CommandArgumentContext.Builtins] `+` `overrideContext`
*/
public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
}

View File

@ -0,0 +1,327 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.SimpleCommand
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext.ParserPair
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermitteeId
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageContent
import net.mamoe.mirai.message.data.PlainText
import java.util.*
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
/**
* 指令参数环境, [CommandValueArgumentParser] 的集合, 用于 [CompositeCommand] [SimpleCommand].
*
* 在指令解析时, 总是从 [CommandArgumentContextAware.context] 搜索相关解析器
*
* 要构造 [CommandArgumentContext], 参考 [buildCommandArgumentContext]
*
* @see SimpleCommandArgumentContext 简单实现
* @see EmptyCommandArgumentContext 空实现, 类似 [emptyList]
*
* @see CommandArgumentContext.Builtins 内建 [CommandValueArgumentParser]
*
* @see buildCommandArgumentContext DSL 构造
*/
public interface CommandArgumentContext {
/**
* [KClass] [CommandValueArgumentParser] 的匹配
*/
public data class ParserPair<T : Any>(
val klass: KClass<T>,
val parser: CommandValueArgumentParser<T>,
) {
public companion object {
@JvmStatic
public fun <T : Any> ParserPair<T>.toPair(): Pair<KClass<T>, CommandValueArgumentParser<T>> =
klass to parser
}
}
/**
* 获取一个 [kClass] 类型的解析器.
*/
public operator fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>?
public fun toList(): List<ParserPair<*>>
public companion object {
/**
* For Java callers.
*
* @see EmptyCommandArgumentContext
*/
@JvmField // public static final CommandArgumentContext EMPTY;
public val EMPTY: CommandArgumentContext = EmptyCommandArgumentContext
}
private object EnumCommandArgumentContext : CommandArgumentContext {
private val cache = WeakHashMap<Class<*>, CommandValueArgumentParser<*>>()
private val enumKlass = Enum::class
override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? {
return if (kClass.isSubclassOf(enumKlass)) {
val jclass = kClass.java.asSubclass(Enum::class.java)
@Suppress("UNCHECKED_CAST")
(cache[jclass] ?: kotlin.run {
EnumValueArgumentParser(jclass).also { cache[jclass] = it }
}) as CommandValueArgumentParser<T>
} else null
}
override fun toList(): List<ParserPair<*>> = emptyList()
}
/**
* 内建的默认 [CommandValueArgumentParser]
*/
public object Builtins : CommandArgumentContext by listOf(
EnumCommandArgumentContext,
buildCommandArgumentContext {
Int::class with IntValueArgumentParser
Byte::class with ByteValueArgumentParser
Short::class with ShortValueArgumentParser
Boolean::class with BooleanValueArgumentParser
String::class with StringValueArgumentParser
Long::class with LongValueArgumentParser
Double::class with DoubleValueArgumentParser
Float::class with FloatValueArgumentParser
Image::class with ImageValueArgumentParser
PlainText::class with PlainTextValueArgumentParser
Contact::class with ExistingContactValueArgumentParser
User::class with ExistingUserValueArgumentParser
Member::class with ExistingMemberValueArgumentParser
Group::class with ExistingGroupValueArgumentParser
Friend::class with ExistingFriendValueArgumentParser
Bot::class with ExistingBotValueArgumentParser
PermissionId::class with PermissionIdValueArgumentParser
PermitteeId::class with PermitteeIdValueArgumentParser
MessageContent::class with RawContentValueArgumentParser
},
).fold(EmptyCommandArgumentContext, CommandArgumentContext::plus)
}
/**
* 拥有 [buildCommandArgumentContext] 的类
*
* @see SimpleCommand
* @see CompositeCommand
*/
public interface CommandArgumentContextAware {
/**
* [CommandValueArgumentParser] 的集合
*/
public val context: CommandArgumentContext
}
/**
* @see CommandArgumentContext.EMPTY
*/
public object EmptyCommandArgumentContext : CommandArgumentContext by SimpleCommandArgumentContext(listOf())
/**
* 合并两个 [buildCommandArgumentContext], [replacer] 将会替换 [this] 中重复的 parser.
*/
public operator fun CommandArgumentContext.plus(replacer: CommandArgumentContext): CommandArgumentContext {
if (replacer === EmptyCommandArgumentContext) return this
if (this == EmptyCommandArgumentContext) return replacer
return object : CommandArgumentContext {
override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? =
replacer[kClass] ?: this@plus[kClass]
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
}
}
/**
* 合并 [this] [replacer], [replacer] 将会替换 [this] 中重复的 parser.
*/
public operator fun CommandArgumentContext.plus(replacer: List<ParserPair<*>>): CommandArgumentContext {
if (replacer.isEmpty()) return this
if (this === EmptyCommandArgumentContext) return SimpleCommandArgumentContext(replacer)
return object : CommandArgumentContext {
@Suppress("UNCHECKED_CAST")
override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? =
replacer.firstOrNull { kClass.isSubclassOf(it.klass) }?.parser as CommandValueArgumentParser<T>?
?: this@plus[kClass]
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
}
}
/**
* 自定义 [buildCommandArgumentContext]
*
* @see buildCommandArgumentContext
*/
@Suppress("UNCHECKED_CAST")
public class SimpleCommandArgumentContext(
public val list: List<ParserPair<*>>,
) : CommandArgumentContext {
override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? =
(this.list.firstOrNull { kClass == it.klass }?.parser
?: this.list.firstOrNull { kClass.isSubclassOf(it.klass) }?.parser) as CommandValueArgumentParser<T>?
override fun toList(): List<ParserPair<*>> = list
}
/**
* 构建一个 [buildCommandArgumentContext].
*
* Kotlin 实现:
* ```
* val context = buildCommandArgumentContext {
* Int::class with IntArgParser
* Member::class with ExistingMemberArgParser
* Group::class with { s: String, sender: CommandSender ->
* Bot.getInstance(s.toLong()).getGroup(s.toLong())
* }
* Bot::class with { s: String ->
* Bot.getInstance(s.toLong())
* }
* }
* ```
*
* Java 实现:
* ```java
* CommandArgumentContext context =
* new CommandArgumentContextBuilder()
* .add(clazz1, parser1)
* .add(String.class, new CommandArgumentParser<String>() {
* public String parse(String raw, CommandSender sender) {
* // ...
* }
* })
* // 更多 add
* .build()
* ```
*
* @see CommandArgumentContextBuilder
* @see buildCommandArgumentContext
*/
@JvmSynthetic
public fun buildCommandArgumentContext(block: CommandArgumentContextBuilder.() -> Unit): CommandArgumentContext {
contract {
callsInPlace(block, EXACTLY_ONCE)
}
return CommandArgumentContextBuilder().apply(block).build()
}
/**
* 参考 [buildCommandArgumentContext]
*/
public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutableListOf() {
/**
* 添加一个指令解析器.
*/
@JvmName("add")
public infix fun <T : Any> Class<T>.with(parser: CommandValueArgumentParser<T>): CommandArgumentContextBuilder =
this.kotlin with parser
/**
* 添加一个指令解析器
*/
@JvmName("add")
public inline infix fun <T : Any> KClass<T>.with(parser: CommandValueArgumentParser<T>): CommandArgumentContextBuilder {
add(ParserPair(this, parser))
return this@CommandArgumentContextBuilder
}
/**
* 添加一个指令解析器
*/
@JvmSynthetic
@LowPriorityInOverloadResolution
public inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandValueArgumentParser<T>.(s: String, sender: CommandSender) -> T,
): CommandArgumentContextBuilder {
add(ParserPair(this, object : CommandValueArgumentParser<T> {
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
}))
return this@CommandArgumentContextBuilder
}
/**
* 添加一个指令解析器
*/
@JvmSynthetic
public inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandValueArgumentParser<T>.(s: String) -> T,
): CommandArgumentContextBuilder {
add(ParserPair(this, object : CommandValueArgumentParser<T> {
override fun parse(raw: String, sender: CommandSender): T = parser(raw)
}))
return this@CommandArgumentContextBuilder
}
@JvmSynthetic
public inline fun <reified T : Any> add(parser: CommandValueArgumentParser<T>): CommandArgumentContextBuilder {
add(ParserPair(T::class, parser))
return this@CommandArgumentContextBuilder
}
/**
* 添加一个指令解析器
*/
@ConsoleExperimentalApi
@JvmSynthetic
public inline infix fun <reified T : Any> add(
crossinline parser: CommandValueArgumentParser<*>.(s: String) -> T,
): CommandArgumentContextBuilder = T::class with object : CommandValueArgumentParser<T> {
override fun parse(raw: String, sender: CommandSender): T = parser(raw)
}
/**
* 添加一个指令解析器
*/
@ConsoleExperimentalApi
@JvmSynthetic
@LowPriorityInOverloadResolution
public inline infix fun <reified T : Any> add(
crossinline parser: CommandValueArgumentParser<*>.(s: String, sender: CommandSender) -> T,
): CommandArgumentContextBuilder = T::class with object : CommandValueArgumentParser<T> {
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
}
/**
* 完成构建, 得到 [CommandArgumentContext]
*/
public fun build(): CommandArgumentContext = SimpleCommandArgumentContext(this.distinctByReversed { it.klass })
}
internal inline fun <T, K> List<T>.distinctByReversed(selector: (T) -> K): List<T> {
val set = HashSet<K>()
val list = ArrayList<T>()
for (i in this.indices.reversed()) {
val element = this[i]
if (set.add(element.let(selector))) {
list.add(element)
}
}
return list
}

View File

@ -0,0 +1,561 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPOSED_SUPER_CLASS")
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender
import net.mamoe.mirai.console.internal.command.fuzzySearchMember
import net.mamoe.mirai.console.permission.AbstractPermitteeId
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermitteeId
import net.mamoe.mirai.console.permission.RootPermission
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.data.*
/**
* 使用 [String.toInt] 解析
*/
public object IntValueArgumentParser : InternalCommandValueArgumentParserExtensions<Int>() {
public override fun parse(raw: String, sender: CommandSender): Int =
raw.toIntOrNull() ?: illegalArgument("无法解析 $raw 为整数")
}
/**
* 使用 [String.toLong] 解析
*/
public object LongValueArgumentParser : InternalCommandValueArgumentParserExtensions<Long>() {
public override fun parse(raw: String, sender: CommandSender): Long =
raw.toLongOrNull() ?: illegalArgument("无法解析 $raw 为长整数")
}
/**
* 使用 [String.toShort] 解析
*/
public object ShortValueArgumentParser : InternalCommandValueArgumentParserExtensions<Short>() {
public override fun parse(raw: String, sender: CommandSender): Short =
raw.toShortOrNull() ?: illegalArgument("无法解析 $raw 为短整数")
}
/**
* 使用 [String.toByte] 解析
*/
public object ByteValueArgumentParser : InternalCommandValueArgumentParserExtensions<Byte>() {
public override fun parse(raw: String, sender: CommandSender): Byte =
raw.toByteOrNull() ?: illegalArgument("无法解析 $raw 为字节")
}
/**
* 使用 [String.toDouble] 解析
*/
public object DoubleValueArgumentParser : InternalCommandValueArgumentParserExtensions<Double>() {
public override fun parse(raw: String, sender: CommandSender): Double =
raw.toDoubleOrNull() ?: illegalArgument("无法解析 $raw 为小数")
}
/**
* 使用 [String.toFloat] 解析
*/
public object FloatValueArgumentParser : InternalCommandValueArgumentParserExtensions<Float>() {
public override fun parse(raw: String, sender: CommandSender): Float =
raw.toFloatOrNull() ?: illegalArgument("无法解析 $raw 为小数")
}
/**
* 直接返回 [String], 或取用 [SingleMessage.contentToString]
*/
public object StringValueArgumentParser : InternalCommandValueArgumentParserExtensions<String>() {
public override fun parse(raw: String, sender: CommandSender): String = raw
}
/**
* 解析 [String] 通过 [Image].
*/
public object ImageValueArgumentParser : InternalCommandValueArgumentParserExtensions<Image>() {
public override fun parse(raw: String, sender: CommandSender): Image {
return kotlin.runCatching {
Image(raw)
}.getOrElse {
illegalArgument("无法解析 $raw 为图片.")
}
}
override fun parse(raw: MessageContent, sender: CommandSender): Image {
if (raw is Image) return raw
return super.parse(raw, sender)
}
}
public object PlainTextValueArgumentParser : InternalCommandValueArgumentParserExtensions<PlainText>() {
public override fun parse(raw: String, sender: CommandSender): PlainText {
return PlainText(raw)
}
override fun parse(raw: MessageContent, sender: CommandSender): PlainText {
if (raw is PlainText) return raw
return super.parse(raw, sender)
}
}
/**
* 当字符串内容为(不区分大小写) "true", "yes", "enabled", "on", "1"
*/
public object BooleanValueArgumentParser : InternalCommandValueArgumentParserExtensions<Boolean>() {
public override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str ->
str.equals("true", ignoreCase = true)
|| str.equals("yes", ignoreCase = true)
|| str.equals("enabled", ignoreCase = true)
|| str.equals("on", ignoreCase = true)
|| str.equals("1", ignoreCase = true)
}
}
/**
* 根据 [Bot.id] 解析一个登录后的 [Bot]
*/
public object ExistingBotValueArgumentParser : InternalCommandValueArgumentParserExtensions<Bot>() {
public override fun parse(raw: String, sender: CommandSender): Bot =
if (raw == "~") sender.inferBotOrFail()
else raw.findBotOrFail()
public override fun parse(raw: MessageContent, sender: CommandSender): Bot =
if (raw is At) {
Bot.getInstanceOrNull(raw.target)
?: illegalArgument("@ 的对象不是一个 Bot")
} else super.parse(raw, sender)
}
/**
* 解析任意一个存在的好友.
*/
public object ExistingFriendValueArgumentParser : InternalCommandValueArgumentParserExtensions<Friend>() {
private val syntax = """
- `botId.friendId`
- `botId.friendNick` (模糊搜索, 寻找最优匹配)
- `~` (指代指令调用人自己作为好友. 仅聊天环境下)
当只登录了一个 [Bot] , `botId` 参数可省略
""".trimIndent()
public override fun parse(raw: String, sender: CommandSender): Friend {
if (raw == "~") return sender.inferFriendOrFail()
val components = raw.split(".")
return when (components.size) {
2 -> components.let { (botId, groupId) ->
botId.findBotOrFail().findFriendOrFail(groupId)
}
1 -> components.let { (groupId) ->
sender.inferBotOrFail().findFriendOrFail(groupId)
}
else -> illegalArgument("好友语法错误. \n${syntax}")
}
}
public override fun parse(raw: MessageContent, sender: CommandSender): Friend {
if (raw is At) {
checkArgument(sender is MemberCommandSender)
return sender.inferBotOrFail().getFriend(raw.target)
?: illegalArgument("At 的对象 ${raw.target} 非 Bot 好友")
} else {
illegalArgument("无法解析 $raw 为好友")
}
}
}
/**
* 解析任意一个存在的群.
*/
public object ExistingGroupValueArgumentParser : InternalCommandValueArgumentParserExtensions<Group>() {
private val syntax = """
- `botId.groupId`
- `~` (指代指令调用人自己所在群. 仅群聊天环境下)
当只登录了一个 [Bot] , `botId` 参数可省略
""".trimIndent()
public override fun parse(raw: String, sender: CommandSender): Group {
if (raw == "~") return sender.inferGroupOrFail()
val components = raw.split(".")
return when (components.size) {
2 -> components.let { (botId, groupId) ->
botId.findBotOrFail().findGroupOrFail(groupId)
}
1 -> components.let { (groupId) ->
sender.inferBotOrFail().findGroupOrFail(groupId)
}
0 -> components.let {
sender.inferGroupOrFail()
}
else -> illegalArgument("群语法错误. \n${syntax}")
}
}
}
public object ExistingUserValueArgumentParser : InternalCommandValueArgumentParserExtensions<User>() {
private val syntax: String = """
- `botId.groupId.memberId`
- `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配)
- `~` (指代指令调用人自己. 仅聊天环境下)
- `botId.groupId.$` (随机成员. )
- `botId.friendId
当处于一个群内时, `botId` `groupId` 参数都可省略
当只登录了一个 [Bot] , `botId` 参数可省略
""".trimIndent()
override fun parse(raw: String, sender: CommandSender): User {
return parseImpl(
sender,
raw,
ExistingMemberValueArgumentParser::parse,
ExistingFriendValueArgumentParser::parse
)
}
override fun parse(raw: MessageContent, sender: CommandSender): User {
return parseImpl(
sender,
raw,
ExistingMemberValueArgumentParser::parse,
ExistingFriendValueArgumentParser::parse
)
}
private fun <T> parseImpl(
sender: CommandSender,
raw: T,
parseFunction: (T, CommandSender) -> User,
parseFunction2: (T, CommandSender) -> User,
): User {
if (sender.inferGroup() != null) {
kotlin.runCatching {
return parseFunction(raw, sender)
}.recoverCatching {
return parseFunction2(raw, sender)
}.getOrElse {
illegalArgument("无法推断目标好友或群员. \n$syntax")
}
}
kotlin.runCatching {
return parseFunction2(raw, sender)
}.getOrElse {
illegalArgument("无法推断目标好友或群员. \n$syntax")
}
}
}
public object ExistingContactValueArgumentParser : InternalCommandValueArgumentParserExtensions<Contact>() {
private val syntax: String = """
- `botId.groupId.memberId`
- `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配)
- `botId.groupId.$` (随机成员. 仅聊天环境下)
- `botId.friendId
- `botId.groupId`
当处于一个群内时, `botId` `groupId` 参数都可省略
当只登录了一个 [Bot] , `botId` 参数可省略
""".trimIndent()
override fun parse(raw: String, sender: CommandSender): Contact {
return parseImpl(sender, raw, ExistingUserValueArgumentParser::parse, ExistingGroupValueArgumentParser::parse)
}
override fun parse(raw: MessageContent, sender: CommandSender): Contact {
return parseImpl(sender, raw, ExistingUserValueArgumentParser::parse, ExistingGroupValueArgumentParser::parse)
}
private fun <T> parseImpl(
sender: CommandSender,
raw: T,
parseFunction: (T, CommandSender) -> Contact,
parseFunction2: (T, CommandSender) -> Contact,
): Contact {
kotlin.runCatching {
return parseFunction(raw, sender)
}.recoverCatching {
return parseFunction2(raw, sender)
}.getOrElse {
illegalArgument("无法推断目标好友, 群或群员. \n$syntax")
}
}
}
/**
* 解析任意一个群成员.
*/
public object ExistingMemberValueArgumentParser : InternalCommandValueArgumentParserExtensions<Member>() {
private val syntax: String = """
- `botId.groupId.memberId`
- `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配)
- `~` (指代指令调用人自己. 仅聊天环境下)
- `groupId.$` (随机成员)
当处于一个群内时, `botId` `groupId` 参数都可省略
当只登录了一个 [Bot] , `botId` 参数可省略
""".trimIndent()
public override fun parse(raw: String, sender: CommandSender): Member {
if (raw == "~") return (sender as? MemberCommandSender)?.user ?: illegalArgument("当前语境下无法推断自身作为群员")
if (raw == "\$") return (sender as? MemberCommandSender)?.group?.members?.randomOrNull()
?: illegalArgument("当前语境下无法推断随机群员")
val components = raw.split(".")
return when (components.size) {
3 -> components.let { (botId, groupId, memberIdOrCard) ->
botId.findBotOrFail().findGroupOrFail(groupId).findMemberOrFail(memberIdOrCard)
}
2 -> components.let { (groupId, memberIdOrCard) ->
sender.inferBotOrFail().findGroupOrFail(groupId).findMemberOrFail(memberIdOrCard)
}
1 -> components.let { (memberIdOrCard) ->
sender.inferGroupOrFail().findMemberOrFail(memberIdOrCard)
}
else -> illegalArgument("群成员语法错误. \n$syntax")
}
}
public override fun parse(raw: MessageContent, sender: CommandSender): Member {
return if (raw is At) {
checkArgument(sender is MemberCommandSender)
val bot = sender.inferBotOrFail()
val group = sender.inferGroupOrFail()
if (raw.target == bot.id) {
return group.botAsMember
}
group[raw.target] ?: illegalArgument("无法找到群员 ${raw.target}")
} else {
parse(raw.content, sender)
}
}
}
public object PermissionIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermissionId>() {
override fun parse(raw: String, sender: CommandSender): PermissionId {
return kotlin.runCatching { PermissionId.parseFromString(raw) }.getOrElse {
if (raw == "*") return RootPermission.id // for convenience
illegalArgument("无法解析 $raw 为权限 ID.")
}
}
}
public object PermitteeIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermitteeId>() {
override fun parse(raw: String, sender: CommandSender): PermitteeId {
return if (raw == "~") sender.permitteeId
else {
kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse {
val long = raw.toLongOrNull()
if (long != null) {
return AbstractPermitteeId.ExactUser(long)// for convenience
}
illegalArgument("无法解析 $raw 为被许可人 ID.")
}
}
}
override fun parse(raw: MessageContent, sender: CommandSender): PermitteeId {
if (raw is At) {
return ExistingUserValueArgumentParser.parse(raw, sender).asCommandSender(false).permitteeId
}
return super.parse(raw, sender)
}
}
/** 直接返回原始参数 [MessageContent] */
public object RawContentValueArgumentParser : CommandValueArgumentParser<MessageContent> {
override fun parse(raw: String, sender: CommandSender): MessageContent = PlainText(raw)
override fun parse(raw: MessageContent, sender: CommandSender): MessageContent = raw
}
/**
* 解析参数为枚举 [T]
*
* :
* - 当枚举值大小写无冲突时会尝试忽略大小写
* - 当大小写驼峰可用时会尝试使用大小写驼峰
*
* 例如:
* ```
* enum class StdType { STD_IN, STD_OUT, STD_ERR }
* ```
* 对于 StdType 有以下值可用:
* - `STD_IN`, `STD_OUT`, `STD_ERR` (忽视大小写)
* - `stdIn`, `stdOut`, `stdErr` (不忽视大小写)
*
* @since 2.2
*/
public class EnumValueArgumentParser<T : Enum<T>>(
private val type: Class<T>,
) : InternalCommandValueArgumentParserExtensions<T>() {
// 此 Exception 仅用于中断 enum 搜索, 不需要使用堆栈信息
private object NoEnumException : RuntimeException()
init {
check(Enum::class.java.isAssignableFrom(type)) {
"$type not a enum class"
}
}
private fun <T> Sequence<T>.hasDuplicates(): Boolean = iterator().hasDuplicates()
private fun <T> Iterator<T>.hasDuplicates(): Boolean {
val observed = HashSet<T>()
for (elem in this) {
if (!observed.add(elem))
return true
}
return false
}
@Suppress("NOTHING_TO_INLINE")
private inline fun noConstant(): Nothing {
throw NoEnumException
}
private val delegate: (String) -> T = kotlin.run {
val enums = type.enumConstants.asSequence()
// step 1: 分析是否能够忽略大小写
if (enums.map { it.name.lowercase() }.hasDuplicates()) {
({ java.lang.Enum.valueOf(type, it) })
} else { // step 2: 分析是否能使用小驼峰命名
val lowerCaseEnumDirection = enums.map { it.name.lowercase() to it }.toList().toMap()
val camelCase = enums.mapNotNull { elm ->
val name = elm.name.split('_')
if (name.size == 1) { // No splitter
null
} else {
buildString {
val iterator = name.iterator()
append(iterator.next().lowercase())
for (v in iterator) {
if (v.isEmpty()) continue
append(v[0].uppercase())
append(v.substring(1, v.length).lowercase())
}
} to elm
}
}
val camelCaseDirection = if ((
enums.map { it.name.lowercase() } + camelCase.map { it.first.lowercase() }
).hasDuplicates()
) { // 确认驼峰命名与源没有冲突
emptyMap()
} else {
camelCase.toList().toMap()
}
({
camelCaseDirection[it]
?: lowerCaseEnumDirection[it.lowercase()]
?: noConstant()
})
}
}
override fun parse(raw: String, sender: CommandSender): T {
return try {
delegate(raw)
} catch (e: Throwable) {
illegalArgument("无法解析 $raw${type.simpleName}")
}
}
}
internal abstract class InternalCommandValueArgumentParserExtensions<T : Any> :
AbstractCommandValueArgumentParser<T>() {
private fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")
protected fun Long.findBotOrFail(): Bot = Bot.getInstanceOrNull(this) ?: illegalArgument("无法找到 Bot: $this")
protected fun String.findBotOrFail(): Bot =
Bot.getInstanceOrNull(this.parseToLongOrFail()) ?: illegalArgument("无法找到 Bot: $this")
protected fun Bot.findGroupOrFail(id: Long): Group = getGroup(id) ?: illegalArgument("无法找到群: $this")
protected fun Bot.findGroupOrFail(id: String): Group =
getGroup(id.parseToLongOrFail()) ?: illegalArgument("无法找到群: $this")
protected fun Bot.findFriendOrFail(id: String): Friend =
getFriend(id.parseToLongOrFail()) ?: illegalArgument("无法找到好友: $this")
protected fun Bot.findMemberOrFail(id: String): Friend =
getFriend(id.parseToLongOrFail()) ?: illegalArgument("无法找到群员: $this")
protected fun Group.findMemberOrFail(idOrCard: String): Member {
if (idOrCard == "\$") return members.randomOrNull() ?: illegalArgument("当前语境下无法推断随机群员")
idOrCard.toLongOrNull()?.let { get(it) }?.let { return it }
this.members.singleOrNull { it.nameCardOrNick.contains(idOrCard) }?.let { return it }
this.members.singleOrNull { it.nameCardOrNick.contains(idOrCard, ignoreCase = true) }?.let { return it }
val candidates = this.fuzzySearchMember(idOrCard)
candidates.singleOrNull()?.let {
if (it.second == 1.0) return it.first // single match
}
if (candidates.isEmpty()) {
illegalArgument("无法找到成员 $idOrCard")
} else {
var index = 1
illegalArgument(
"无法找到成员 $idOrCard。 多个成员满足搜索结果或匹配度不足: \n\n" +
candidates.joinToString("\n", limit = 6) {
val percentage = (it.second * 100).toDecimalPlace(0)
"#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4%
}
)
}
}
protected fun CommandSender.inferBotOrFail(): Bot =
(this as? UserCommandSender)?.bot
?: Bot.instancesSequence.singleOrNull()
?: illegalArgument("当前语境下无法推断目标 Bot, 因为目前有多个 Bot 在线.")
protected fun CommandSender.inferGroupOrFail(): Group =
inferGroup() ?: illegalArgument("当前语境下无法推断目标群")
protected fun CommandSender.inferGroup(): Group? = (this as? GroupAwareCommandSender)?.group
protected fun CommandSender.inferFriendOrFail(): Friend =
(this as? FriendCommandSender)?.user ?: illegalArgument("当前语境下无法推断目标好友")
}
internal fun Double.toDecimalPlace(n: Int): String = "%.${n}f".format(this)
internal fun String.truncate(lengthLimit: Int, replacement: String = "..."): String = buildString {
var lengthSum = 0
for (char in this@truncate) {
lengthSum += char.chineseLength()
if (lengthSum > lengthLimit) {
append(replacement)
return toString()
} else append(char)
}
return toString()
}
internal fun Char.chineseLength(): Int {
return when (this) {
in '\u0000'..'\u007F' -> 1
in '\u0080'..'\u07FF' -> 2
in '\u0800'..'\uFFFF' -> 2
else -> 2
}
}

View File

@ -0,0 +1,272 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createOptional
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createRequired
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.command.resolve.ResolvedCommandValueArgument
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
import net.mamoe.mirai.console.internal.data.typeOf0
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.message.data.content
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf
/**
* Inherited instances must be [CommandValueParameter] or [CommandReceiverParameter]
*/
@ExperimentalCommandDescriptors
public interface CommandParameter<T : Any?> {
public val name: String?
public val isOptional: Boolean
/**
* Reified type of [T]
*/
public val type: KType
}
@ExperimentalCommandDescriptors
public abstract class AbstractCommandParameter<T> : CommandParameter<T> {
override fun toString(): String = buildString {
append(name)
append(": ")
append(type.classifierAsKClass().simpleName)
append(if (type.isMarkedNullable) "?" else "")
}
}
/**
* Inherited instances must be [AbstractCommandValueParameter].
*
* ### Implementation details
*
* [CommandValueParameter] should:
* - implement [equals], [hashCode] since used in [ResolvedCommandValueArgument].
* - implement [toString] to produce user-friendly textual representation of this parameter with type info.
*
*/
@ExperimentalCommandDescriptors
public interface CommandValueParameter<T : Any?> : CommandParameter<T> {
public val isVararg: Boolean
/**
* Checks whether this [CommandValueParameter] accepts [argument].
*
* An [argument] can be accepted if:
* - [CommandValueArgument.type] is subtype of, or equals to [CommandValueParameter.type] (nullability considered), or
* - [CommandValueArgument.typeVariants] produces
*
* @return `true` if [argument] may be accepted through any approach mentioned above.
*
* @see accepting
*/
public fun accepts(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): Boolean =
accepting(argument, commandArgumentContext).isAcceptable
public fun accepting(
argument: CommandValueArgument,
commandArgumentContext: CommandArgumentContext?
): ArgumentAcceptance
}
@ExperimentalCommandDescriptors
public sealed class ArgumentAcceptance(
/**
* Higher means more acceptable
*/
@ConsoleExperimentalApi
public val acceptanceLevel: Int,
) {
public object Direct : ArgumentAcceptance(Int.MAX_VALUE)
public data class WithTypeConversion(
public val typeVariant: TypeVariant<*>,
) : ArgumentAcceptance(20)
public data class WithContextualConversion(
public val parser: CommandValueArgumentParser<*>,
) : ArgumentAcceptance(10)
public data class ResolutionAmbiguity(
public val candidates: List<TypeVariant<*>>,
) : ArgumentAcceptance(0)
public object Impossible : ArgumentAcceptance(-1)
public companion object {
@JvmStatic
public val ArgumentAcceptance.isAcceptable: Boolean
get() = acceptanceLevel > 0
@JvmStatic
public val ArgumentAcceptance.isNotAcceptable: Boolean
get() = acceptanceLevel <= 0
}
}
@ExperimentalCommandDescriptors
public data class CommandReceiverParameter<T : CommandSender>(
override val isOptional: Boolean,
override val type: KType,
) : CommandParameter<T>, AbstractCommandParameter<T>() {
override val name: String get() = NAME
init {
val classifier = type.classifier
require(classifier is KClass<*>) {
"CommandReceiverParameter.type.classifier must be KClass."
}
require(classifier.isSubclassOf(CommandSender::class)) {
"CommandReceiverParameter.type.classifier must be subclass of CommandSender."
}
}
public companion object {
public const val NAME: String = "<receiver>"
}
}
internal val ANY_TYPE = typeOf0<Any>()
internal val ARRAY_OUT_ANY_TYPE = typeOf0<Array<out Any?>>()
@ExperimentalCommandDescriptors
public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>, AbstractCommandParameter<T>() {
override fun toString(): String = buildString {
if (isVararg) append("vararg ")
append(super.toString())
if (isOptional) {
append(" = ...")
}
}
public override fun accepting(
argument: CommandValueArgument,
commandArgumentContext: CommandArgumentContext?
): ArgumentAcceptance {
if (isVararg) {
val arrayElementType = this.type.arguments.single() // Array<T>
return acceptingImpl(arrayElementType.type ?: ANY_TYPE, argument, commandArgumentContext)
}
return acceptingImpl(this.type, argument, commandArgumentContext)
}
protected open fun acceptingImpl(
expectingType: KType,
argument: CommandValueArgument,
commandArgumentContext: CommandArgumentContext?,
): ArgumentAcceptance {
if (argument.type.isSubtypeOf(expectingType)) return ArgumentAcceptance.Direct
argument.typeVariants.associateWith { typeVariant ->
if (typeVariant.outType.isSubtypeOf(expectingType)) {
// TODO: 2020/10/11 resolution ambiguity
return ArgumentAcceptance.WithTypeConversion(typeVariant)
}
}
expectingType.classifierAsKClassOrNull()?.let { commandArgumentContext?.get(it) }?.let { parser ->
return ArgumentAcceptance.WithContextualConversion(parser)
}
return ArgumentAcceptance.Impossible
}
@ConsoleExperimentalApi
public data class StringConstant(
@ConsoleExperimentalApi
public override val name: String?,
public val expectingValue: String,
public val ignoreCase: Boolean,
) : AbstractCommandValueParameter<String>() {
public override val type: KType get() = STRING_TYPE
public override val isOptional: Boolean get() = false
public override val isVararg: Boolean get() = false
init {
require(expectingValue.isNotBlank()) {
"expectingValue must not be blank"
}
require(expectingValue.none(Char::isWhitespace)) {
"expectingValue must not contain whitespace"
}
}
override fun toString(): String = "<$expectingValue>"
override fun acceptingImpl(
expectingType: KType,
argument: CommandValueArgument,
commandArgumentContext: CommandArgumentContext?
): ArgumentAcceptance {
return if (argument.value.content.equals(expectingValue, ignoreCase)) {
ArgumentAcceptance.Direct
} else ArgumentAcceptance.Impossible
}
private companion object {
@OptIn(ExperimentalStdlibApi::class)
val STRING_TYPE = typeOf<String>()
}
}
/**
* @see createOptional
* @see createRequired
*/
public data class UserDefinedType<T>(
public override val name: String?,
public override val isOptional: Boolean,
public override val isVararg: Boolean,
public override val type: KType,
) : AbstractCommandValueParameter<T>() {
override fun toString(): String = super.toString()
init {
requireNotNull(type.classifierAsKClassOrNull()) {
"type.classifier must be KClass."
}
if (isVararg)
check(type.isSubtypeOf(ARRAY_OUT_ANY_TYPE)) {
"type must be subtype of Array if vararg. Given $type."
}
}
public companion object {
@JvmStatic
public inline fun <reified T : Any> createOptional(name: String, isVararg: Boolean): UserDefinedType<T> {
@OptIn(ExperimentalStdlibApi::class)
return UserDefinedType(name, true, isVararg, typeOf<T>())
}
@JvmStatic
public inline fun <reified T : Any> createRequired(name: String, isVararg: Boolean): UserDefinedType<T> {
@OptIn(ExperimentalStdlibApi::class)
return UserDefinedType(name, false, isVararg, typeOf<T>())
}
}
}
/**
* Extended by [CommandValueArgumentParser]
*/
@ConsoleExperimentalApi
public abstract class Extended<T> : AbstractCommandValueParameter<T>() // For implementer: take care of toString()
}

View File

@ -0,0 +1,90 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.reflect.KFunction
/**
* 指令签名. 表示指令定义的需要的参数.
*
* @see AbstractCommandSignature
*/
@ExperimentalCommandDescriptors
public interface CommandSignature {
/**
* 接收者参数, [CommandSender] 子类
*/
@ConsoleExperimentalApi
public val receiverParameter: CommandReceiverParameter<out CommandSender>?
/**
* 形式 值参数.
*/
public val valueParameters: List<AbstractCommandValueParameter<*>>
/**
* 调用这个指令.
*/
public suspend fun call(resolvedCommandCall: ResolvedCommandCall)
}
/**
* 来自 [KFunction] 反射得到的 [CommandSignature]
*
* @see CommandSignatureFromKFunctionImpl
*/
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public interface CommandSignatureFromKFunction : CommandSignature {
public val originFunction: KFunction<*>
}
/**
* @see CommandSignatureImpl
* @see CommandSignatureFromKFunctionImpl
*/
@ExperimentalCommandDescriptors
public abstract class AbstractCommandSignature : CommandSignature {
override fun toString(): String {
val receiverParameter = receiverParameter
return if (receiverParameter == null) {
"CommandSignature(${valueParameters.joinToString()})"
} else {
"CommandSignature($receiverParameter, ${valueParameters.joinToString()})"
}
}
}
@ExperimentalCommandDescriptors
public open class CommandSignatureImpl(
override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
override val valueParameters: List<AbstractCommandValueParameter<*>>,
private val onCall: suspend CommandSignatureImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
) : CommandSignature, AbstractCommandSignature() {
override suspend fun call(resolvedCommandCall: ResolvedCommandCall) {
return onCall(resolvedCommandCall)
}
}
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public open class CommandSignatureFromKFunctionImpl(
override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
override val valueParameters: List<AbstractCommandValueParameter<*>>,
override val originFunction: KFunction<*>,
private val onCall: suspend CommandSignatureFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
) : CommandSignatureFromKFunction, AbstractCommandSignature() {
override suspend fun call(resolvedCommandCall: ResolvedCommandCall) {
return onCall(resolvedCommandCall)
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.SimpleCommand
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.map
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.parse
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermitteeId
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.data.*
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* 指令参数解析器. 用于解析字符串或 [SingleMessage] 到特定参数类型.
*
* ### 参数解析
*
* [SimpleCommand] 中的示例:
* ```
* suspend fun CommandSender.mute(target: Member, duration: Int)
* ```
* [CommandManager] 总是从 [SimpleCommand.context] 搜索一个 [T] [Member] [CommandValueArgumentParser], 并调用其 [CommandValueArgumentParser.parse]
*
* ### 内建指令解析器
* - 基础类型: [ByteValueArgumentParser], [ShortValueArgumentParser], [IntValueArgumentParser], [LongValueArgumentParser]
* [FloatValueArgumentParser], [DoubleValueArgumentParser],
* [BooleanValueArgumentParser], [StringValueArgumentParser]
*
* - [Bot]: [ExistingBotValueArgumentParser]
* - [Friend]: [ExistingFriendValueArgumentParser]
* - [Group]: [ExistingGroupValueArgumentParser]
* - [Member]: [ExistingMemberValueArgumentParser]
* - [User]: [ExistingUserValueArgumentParser]
* - [Contact]: [ExistingContactValueArgumentParser]
*
* - [PermitteeId]: [PermitteeIdValueArgumentParser]
* - [PermissionId]: [PermissionIdValueArgumentParser]
*
*
* @see SimpleCommand 简单指令
* @see CompositeCommand 复合指令
*
* @see buildCommandArgumentContext 指令参数环境, [CommandValueArgumentParser] 的集合
*/
public interface CommandValueArgumentParser<out T : Any> {
/**
* 解析一个字符串为 [T] 类型参数
*
* **实现提示**: 在解析时遇到意料之中的问题, 如无法找到目标群员, 可抛出 [CommandArgumentParserException].
* 此异常将会被特殊处理, 不会引发一个错误, 而是作为指令调用成功的情况, 将错误信息发送给用户.
*
* @throws CommandArgumentParserException 当解析时遇到*意料之中*的问题时抛出.
*
* @see CommandArgumentParserException
*/
@Throws(CommandArgumentParserException::class)
public fun parse(raw: String, sender: CommandSender): T
/**
* 解析一个消息内容元素为 [T] 类型参数
*
* **实现提示**: 在解析时遇到意料之中的问题, 如无法找到目标群员, 可抛出 [CommandArgumentParserException].
* 此异常将会被特殊处理, 不会引发一个错误, 而是作为指令调用成功的情况, 将错误信息发送给用户.
*
* @throws CommandArgumentParserException 当解析时遇到*意料之中*的问题时抛出.
*
* @see CommandArgumentParserException
*/
@Throws(CommandArgumentParserException::class)
public fun parse(raw: MessageContent, sender: CommandSender): T = parse(raw.content, sender)
public companion object {
/**
* 解析一个字符串或 [SingleMessage] [T] 类型参数
*
* @throws IllegalArgumentException [raw] 既不是 [SingleMessage], 也不是 [String] 时抛出.
*
* @see CommandValueArgumentParser.parse
*/
@JvmStatic
@Throws(IllegalArgumentException::class)
public fun <T : Any> CommandValueArgumentParser<T>.parse(raw: Message, sender: CommandSender): T {
return when (raw) {
is PlainText -> parse(raw.content, sender)
is MessageContent -> parse(raw, sender)
else -> throw IllegalArgumentException("Illegal raw argument type: ${raw::class.qualifiedName}")
}
}
/**
* 使用原 [this] 解析, 成功后使用 [mapper] 映射为另一个类型.
*/
@JvmStatic
public fun <Original : Any, Result : Any> CommandValueArgumentParser<Original>.map(
mapper: MappingCommandValueArgumentParser<Original, Result>.(Original) -> Result,
): CommandValueArgumentParser<Result> = MappingCommandValueArgumentParser(this, mapper)
}
}
/**
* @see CommandValueArgumentParser 的基础实现.
*/
public abstract class AbstractCommandValueArgumentParser<T : Any> : CommandValueArgumentParser<T> {
public companion object {
/**
* 抛出一个 [CommandArgumentParserException] 的捷径
*
* @throws CommandArgumentParserException
*/
@JvmStatic
@JvmSynthetic
@Throws(CommandArgumentParserException::class)
protected inline fun CommandValueArgumentParser<*>.illegalArgument(
message: String,
cause: Throwable? = null
): Nothing =
throw CommandArgumentParserException(message, cause)
/**
* 检查参数 [condition]. 当它为 `false` 时调用 [message] 并以其返回值作为消息, 抛出异常 [CommandArgumentParserException]
*
* @throws CommandArgumentParserException
*/
@JvmStatic
@Throws(CommandArgumentParserException::class)
@JvmSynthetic
protected inline fun CommandValueArgumentParser<*>.checkArgument(
condition: Boolean,
crossinline message: () -> String = { "Check failed." },
) {
contract {
returns() implies condition
callsInPlace(message, InvocationKind.AT_MOST_ONCE)
}
if (!condition) illegalArgument(message())
}
}
}
/**
* @see CommandValueArgumentParser.map
*/
public class MappingCommandValueArgumentParser<T : Any, R : Any>(
private val original: CommandValueArgumentParser<T>,
private val mapper: MappingCommandValueArgumentParser<T, R>.(T) -> R,
) : AbstractCommandValueArgumentParser<R>() {
override fun parse(raw: String, sender: CommandSender): R = mapper(original.parse(raw, sender))
override fun parse(raw: MessageContent, sender: CommandSender): R = mapper(original.parse(raw, sender))
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.IllegalCommandArgumentException
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueArgumentParser.Companion.illegalArgument
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
import kotlin.reflect.KType
internal val KType.qualifiedName: String
get() = this.classifierAsKClassOrNull()?.qualifiedNameOrTip ?: classifier.toString()
@ExperimentalCommandDescriptors
public open class NoValueArgumentMappingException(
public val argument: CommandValueArgument,
public val forType: KType,
) : CommandResolutionException("Cannot find a CommandArgument mapping for ${forType.qualifiedName}")
public open class CommandResolutionException : RuntimeException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}
@ExperimentalCommandDescriptors
public open class CommandDeclarationClashException(
public val command: Command,
public val signatures: List<CommandSignature>,
) : CommandDeclarationException("Declaration clash for command '${command.primaryName}': \n${signatures.joinToString("\n")}")
public open class CommandDeclarationException : RuntimeException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}
/**
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等.
*
* [message] 将会发送给指令调用方.
*
* @see IllegalCommandArgumentException
* @see CommandValueArgumentParser
* @see AbstractCommandValueArgumentParser.illegalArgument
*/
public class CommandArgumentParserException @JvmOverloads constructor(
message: String,
cause: Throwable? = null,
) : IllegalCommandArgumentException(message, cause)

View File

@ -0,0 +1,31 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.annotation.AnnotationTarget.*
/**
* 标记一个实验性的指令解释器 API.
*
* 这些 API 不具有稳定性, 且可能会在任意时刻更改.
* 不建议在发行版本中使用这些 API.
*
* @since 1.0-RC
*/
@Retention(AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
@MustBeDocumented
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public annotation class ExperimentalCommandDescriptors(
val message: String = "Command descriptors are an experimental API.",
)

View File

@ -0,0 +1,83 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.internal.data.castOrNull
import net.mamoe.mirai.console.internal.data.kClassQualifiedName
import net.mamoe.mirai.message.data.*
import kotlin.reflect.KType
import kotlin.reflect.typeOf
/**
* Intrinsic variant of an [CommandValueArgument].
*
* The *intrinsic* reveals the independent conversion property of this type.
* Conversion with [TypeVariant] is out of any contextual resource,
* except the [output type][TypeVariant.outType] declared by the [TypeVariant] itself.
*
*
* [TypeVariant] is not necessary for all [CommandValueArgument]s.
*
* @param OutType the type this [TypeVariant] can map a argument [Message] to .
*
* @see CommandValueArgument.typeVariants
*/
@ExperimentalCommandDescriptors
public interface TypeVariant<out OutType> {
/**
* The reified type of [OutType]
*/
public val outType: KType
/**
* Maps an [valueArgument] to [outType]
*
* @see CommandValueArgument.value
*/
public fun mapValue(valueArgument: Message): OutType
public companion object {
/**
* Creates a [TypeVariant] with reified [OutType].
*/
@OptIn(ExperimentalStdlibApi::class)
@JvmSynthetic
public inline operator fun <reified OutType> invoke(crossinline block: (valueParameter: Message) -> OutType): TypeVariant<OutType> {
return object : TypeVariant<OutType> {
override val outType: KType = typeOf<OutType>()
override fun mapValue(valueArgument: Message): OutType = block(valueArgument)
}
}
}
}
@ExperimentalCommandDescriptors
public object MessageContentTypeVariant : TypeVariant<MessageContent> {
@OptIn(ExperimentalStdlibApi::class)
override val outType: KType = typeOf<MessageContent>()
override fun mapValue(valueArgument: Message): MessageContent =
valueArgument.castOrNull<MessageContent>()
?: error("Accepts MessageContent only but given ${valueArgument.kClassQualifiedName}")
}
@ExperimentalCommandDescriptors
public object MessageChainTypeVariant : TypeVariant<MessageChain> {
@OptIn(ExperimentalStdlibApi::class)
override val outType: KType = typeOf<MessageChain>()
override fun mapValue(valueArgument: Message): MessageChain = valueArgument.toMessageChain()
}
@ExperimentalCommandDescriptors
public object ContentStringTypeVariant : TypeVariant<String> {
@OptIn(ExperimentalStdlibApi::class)
override val outType: KType = typeOf<String>()
override fun mapValue(valueArgument: Message): String = valueArgument.content
}

View File

@ -0,0 +1,94 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command.java
import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
import net.mamoe.mirai.console.permission.Permission
/**
* 复合指令. 指令注册时候会通过反射构造指令解析器.
*
* 示例:
* ```
* public final class MyCompositeCommand extends CompositeCommand {
* public static final MyCompositeCommand INSTANCE = new MyCompositeCommand();
*
* private MyCompositeCommand() {
* super(MyPluginMain.INSTANCE, "manage") // "manage" 是主指令名
* }
*
* // [参数智能解析]
* //
* //
* // 在控制台执行 "/manage <群号>.<群员> <持续时间>",
* // 或在聊天群内发送 "/manage <@一个群员> <持续时间>",
* // 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>",
* // 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>"
* // 时调用这个函数
* @SubCommand
* public void mute(CommandSender sender, Member target, int duration) { // 通过 /manage mute <target> <duration> 调用.
* sender.sendMessage("/manage mute 被调用了, 参数为: " + target + ", " + duration);
*
*
* String result;
* try {
* result = target.mute(duration).toString();
* } catch(Exception e) {
* result = ExceptionsKt.stackTraceToString(e);
* }
*
* sender.sendMessage("结果: " + result)
* }
*
* @SubCommand
* public void list(CommandSender sender) { // 执行 "/manage list" 时调用这个方法
* sender.sendMessage("/manage list 被调用了")
* }
*
* // 支持 Image 类型, 需在聊天中执行此指令.
* @SubCommand
* public void test(CommandSender sender, Image image) { // 执行 "/manage test <一张图片>" 时调用这个方法
* sender.sendMessage("/manage image 被调用了, 图片是 " + image.imageId)
* }
* }
* ```
*
* Kotlin 示例查看 [CompositeCommand]
*
* @see buildCommandArgumentContext
*/
public abstract class JCompositeCommand
@JvmOverloads constructor(
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
@ResolveContext(COMMAND_NAME) primaryName: String,
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
parentPermission: Permission = owner.parentPermission,
) : CompositeCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = parentPermission) {
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
public final override var description: String = super.description
protected set
public final override var permission: Permission = super.permission
protected set
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
@ExperimentalCommandDescriptors
public final override var prefixOptional: Boolean = false
protected set
}

View File

@ -0,0 +1,108 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command.java
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
import net.mamoe.mirai.console.internal.command.findOrCreateCommandPermission
import net.mamoe.mirai.console.internal.data.typeOf0
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.buildMessageChain
import net.mamoe.mirai.utils.runBIO
/**
* Java 用户继承
*
* 请在构造时设置相关属性.
*
* ```java
* public final class MyCommand extends JRawCommand {
* public static final MyCommand INSTANCE = new MyCommand();
* private MyCommand() {
* super(MyPluginMain.INSTANCE, "test")
* // 可选设置如下属性
* setUsage("/test")
* setDescription("这是一个测试指令")
* setPermission(CommandPermission.Operator.INSTANCE)
* setPrefixOptional(true)
* }
*
* @Override
* public void onCommand(@NotNull CommandSender sender, @NotNull args: Object[]) {
* // 处理指令
* }
* }
* ```
*
* @see JRawCommand
*/
public abstract class JRawCommand
@JvmOverloads constructor(
/**
* 指令拥有者.
* @see CommandOwner
*/
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER)
public override val owner: CommandOwner,
@ResolveContext(COMMAND_NAME)
public override val primaryName: String,
@ResolveContext(COMMAND_NAME)
public override vararg val secondaryNames: String,
parentPermission: Permission = owner.parentPermission,
) : Command {
/** 用法说明, 用于发送给用户 */
public override var usage: String = "<no usages given>"
protected set
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
public final override var description: String = "<no descriptions given>"
protected set
/** 指令权限 */
public final override var permission: Permission = findOrCreateCommandPermission(parentPermission)
protected set
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
@ExperimentalCommandDescriptors
public final override var prefixOptional: Boolean = false
protected set
@ExperimentalCommandDescriptors
override val overloads: List<@JvmWildcard CommandSignature> = listOf(
CommandSignatureImpl(
receiverParameter = CommandReceiverParameter(false, typeOf0<CommandSender>()),
valueParameters = listOf(
AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>(
"args",
true
)
)
) { call ->
val sender = call.caller
val arguments = call.rawValueArguments
runBIO { onCommand(sender, buildMessageChain { arguments.forEach { +it.value } }) }
}
)
/**
* 在指令被执行时调用.
*
* @param args 指令参数.
*
* @see CommandManager.executeCommand 查看更多信息
* @since 2.8
*/
public open fun onCommand(sender: CommandSender, args: MessageChain) {}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command.java
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.command.SimpleCommand
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
import net.mamoe.mirai.console.permission.Permission
/**
* Java 实现:
* ```java
* public final class MySimpleCommand extends JSimpleCommand {
* public static final MySimpleCommand INSTANCE = new MySimpleCommand();
* private MySimpleCommand() {
* super(MyPlugin.INSTANCE, "tell")
* // 可选设置如下属性
* setDescription("这是一个测试指令")
* setUsage("/tell <target> <message>") // 如不设置则自动根据带有 @Handler 的方法生成
* setPermission(CommandPermission.Operator.INSTANCE)
* setPrefixOptional(true)
* }
*
* @Handler
* public void onCommand(CommandSender sender, User target, String message) {
* target.sendMessage(message)
* }
* }
* ```
*
* @see SimpleCommand
* @see [CommandManager.executeCommand]
*/
public abstract class JSimpleCommand @JvmOverloads constructor(
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
@ResolveContext(COMMAND_NAME) primaryName: String,
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
basePermission: Permission = owner.parentPermission,
) : SimpleCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = basePermission) {
public override var description: String = super.description
protected set
public override var permission: Permission = super.permission
protected set
@ExperimentalCommandDescriptors
public override var prefixOptional: Boolean = super.prefixOptional
protected set
public override var context: CommandArgumentContext = super.context
protected set
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:OptIn(ExperimentalStdlibApi::class)
package net.mamoe.mirai.console.command.parse
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
import net.mamoe.mirai.message.data.MessageChain
/**
* Unresolved [CommandCall].
*
* ### Implementation details
* [CommandCall] should be _immutable_,
* meaning all of its properties must be *pure* and should be implemented as an immutable property, or delegated by a lazy initializer.
*
* @see CommandCallParser
* @see CommandCallResolver
*
* @see ResolvedCommandCall
*/
@ExperimentalCommandDescriptors
public interface CommandCall {
/**
* The [CommandSender] responsible to this call.
*/
public val caller: CommandSender
/**
* One of callee [Command]'s [Command.allNames].
*
* Generally [CommandCallResolver] use [calleeName] to find target [Command] registered in [CommandManager]
*/
public val calleeName: String
/**
* Explicit value arguments parsed from raw [MessageChain] or implicit ones deduced by the [CommandCallResolver].
*/
public val valueArguments: List<CommandValueArgument>
// maybe add contextual arguments, i.e. from MessageMetadata
}
@ExperimentalCommandDescriptors
public class CommandCallImpl(
override val caller: CommandSender,
override val calleeName: String,
override val valueArguments: List<CommandValueArgument>,
) : CommandCall

View File

@ -0,0 +1,55 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command.parse
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
import net.mamoe.mirai.console.extensions.CommandCallParserProvider
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.message.data.MessageChain
/**
* Lexical and syntactical parser for transforming a [MessageChain] into [CommandCall]
*
* @see CommandCallResolver The call resolver for [CommandCall] to become [ResolvedCommandCall]
* @see CommandCallParserProvider The extension point
*
* @see SpaceSeparatedCommandCallParser
*/
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public interface CommandCallParser {
/**
* Lexically and syntactically parse a [message] into [CommandCall], but performs nothing about resolving a call.
*
* @return `null` if unable to parse (i.e. due to syntax errors).
*/
public fun parse(caller: CommandSender, message: MessageChain): CommandCall?
public companion object {
/**
* Calls [CommandCallParser]s provided by [CommandCallParserProvider] in [GlobalComponentStorage] sequentially,
* returning the first non-null result, `null` otherwise.
*/
@JvmStatic
public fun MessageChain.parseCommandCall(sender: CommandSender): CommandCall? {
GlobalComponentStorage.run {
CommandCallParserProvider.useExtensions { provider ->
provider.instance.parse(sender, this@parseCommandCall)?.let { return it }
}
}
return null
}
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.command.parse
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.internal.data.castOrInternalError
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageContent
import net.mamoe.mirai.message.data.SingleMessage
import kotlin.reflect.KType
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf
/**
* @see CommandValueArgument
*/
@ExperimentalCommandDescriptors
public interface CommandArgument
/**
* @see DefaultCommandValueArgument
*/
@ExperimentalCommandDescriptors
public interface CommandValueArgument : CommandArgument {
public val type: KType
/**
* [MessageContent] if single argument
* [MessageChain] is vararg
*/
public val value: Message
/**
* Intrinsic variants of this argument.
*
* @see TypeVariant
*/
public val typeVariants: List<TypeVariant<*>>
}
/**
* The [CommandValueArgument] that doesn't vary in type (remaining [MessageContent]).
*/
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public data class DefaultCommandValueArgument(
public override val value: Message,
) : CommandValueArgument {
@OptIn(ExperimentalStdlibApi::class)
override val type: KType = typeOf<MessageContent>()
override val typeVariants: List<TypeVariant<*>> = listOf(
MessageContentTypeVariant,
MessageChainTypeVariant,
ContentStringTypeVariant,
)
}
@ExperimentalCommandDescriptors
public fun <T> CommandValueArgument.mapValue(typeVariant: TypeVariant<T>): T = typeVariant.mapValue(this.value)
@OptIn(ExperimentalStdlibApi::class)
@ExperimentalCommandDescriptors
public inline fun <reified T> CommandValueArgument.mapToType(): T =
mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>())
@OptIn(ExperimentalStdlibApi::class)
@ExperimentalCommandDescriptors
public fun <T> CommandValueArgument.mapToType(type: KType): T =
mapToTypeOrNull(type) ?: throw NoValueArgumentMappingException(this, type)
@ExperimentalCommandDescriptors
public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType): T? {
if (expectingType.isSubtypeOf(ARRAY_OUT_ANY_TYPE)) {
val arrayElementType = expectingType.arguments.single().type ?: ANY_TYPE
val result = ArrayList<Any?>()
when (val value = value) {
is MessageChain -> {
for (message in value) {
result.add(mapToTypeOrNullImpl(arrayElementType, message))
}
}
else -> { // single
value.castOrInternalError<SingleMessage>()
result.add(mapToTypeOrNullImpl(arrayElementType, value))
}
}
@Suppress("UNCHECKED_CAST")
return result.toArray(arrayElementType.createArray(result.size)) as T
}
@Suppress("UNCHECKED_CAST")
return mapToTypeOrNullImpl(expectingType, value) as T
}
private fun KType.createArray(size: Int): Array<Any?> {
return java.lang.reflect.Array.newInstance(this.classifierAsKClass().javaObjectType, size).castOrInternalError()
}
@OptIn(ExperimentalCommandDescriptors::class)
private fun CommandValueArgument.mapToTypeOrNullImpl(expectingType: KType, value: Message): Any? {
@OptIn(ExperimentalStdlibApi::class)
val result = typeVariants
.filter { it.outType.isSubtypeOf(expectingType) }
.ifEmpty {
return null
}
.reduce { acc, typeVariant ->
if (acc.outType.isSubtypeOf(typeVariant.outType))
acc
else typeVariant
}
@Suppress("UNCHECKED_CAST")
return result.mapValue(value)
}
@ExperimentalCommandDescriptors
public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
@OptIn(ExperimentalStdlibApi::class)
return mapToTypeOrNull(typeOf<T>())
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command.parse
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.extensions.CommandCallParserProvider
import net.mamoe.mirai.console.extensions.CommandCallParserProviderImpl
import net.mamoe.mirai.console.internal.command.flattenCommandComponents
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageContent
import net.mamoe.mirai.message.data.content
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public object SpaceSeparatedCommandCallParser : CommandCallParser {
override fun parse(caller: CommandSender, message: MessageChain): CommandCall? {
val flatten = message.flattenCommandComponents().filterIsInstance<MessageContent>()
if (flatten.isEmpty()) return null
return CommandCallImpl(
caller = caller,
calleeName = flatten.first().content,
valueArguments = flatten.drop(1).map(::DefaultCommandValueArgument)
)
}
public object Provider : CommandCallParserProvider by CommandCallParserProviderImpl(SpaceSeparatedCommandCallParser)
}

View File

@ -0,0 +1,256 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command.resolve
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isNotAcceptable
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.command.parse.DefaultCommandValueArgument
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.safeCast
import net.mamoe.mirai.message.data.EmptyMessageChain
import net.mamoe.mirai.message.data.toMessageChain
/**
* Builtin implementation of [CommandCallResolver]
*/
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public object BuiltInCommandCallResolver : CommandCallResolver {
override fun resolve(call: CommandCall): CommandResolveResult {
val callee = CommandManager.matchCommand(call.calleeName)
?: return CommandResolveResult(CommandExecuteResult.UnresolvedCommand(call))
val valueArguments = call.valueArguments
val context = callee.safeCast<CommandArgumentContextAware>()?.context
val errorSink = ErrorSink()
val signature = resolveImpl(call.caller, callee, valueArguments, context, errorSink) ?: kotlin.run {
return CommandResolveResult(errorSink.createFailure(call, callee))
}
return CommandResolveResult(
ResolvedCommandCallImpl(
call.caller,
callee,
signature.signature,
signature.zippedArguments.map { it.second },
context ?: EmptyCommandArgumentContext
)
)
}
private data class ResolveData(
val signature: CommandSignature,
val zippedArguments: List<Pair<AbstractCommandValueParameter<*>, CommandValueArgument>>,
val argumentAcceptances: List<ArgumentAcceptanceWithIndex>,
val remainingParameters: List<AbstractCommandValueParameter<*>>,
) {
val remainingOptionalCount: Int = remainingParameters.count { it.isOptional }
}
private data class ArgumentAcceptanceWithIndex(
val index: Int,
val acceptance: ArgumentAcceptance,
)
private class ErrorSink {
private val unmatchedCommandSignatures = mutableListOf<UnmatchedCommandSignature>()
private val resolutionAmbiguities = mutableListOf<CommandSignature>()
fun reportUnmatched(failure: UnmatchedCommandSignature) {
unmatchedCommandSignatures.add(failure)
}
fun reportAmbiguity(resolutionAmbiguity: CommandSignature) {
resolutionAmbiguities.add(resolutionAmbiguity)
}
fun createFailure(call: CommandCall, command: Command): CommandExecuteResult.Failure {
val failureReasons = unmatchedCommandSignatures.toMutableList()
val rA = FailureReason.ResolutionAmbiguity(resolutionAmbiguities)
failureReasons.addAll(resolutionAmbiguities.map { UnmatchedCommandSignature(it, rA) })
return CommandExecuteResult.UnmatchedSignature(command, call, unmatchedCommandSignatures)
}
}
private
fun CommandSignature.toResolveData(
caller: CommandSender,
valueArguments: List<CommandValueArgument>,
context: CommandArgumentContext?,
errorSink: ErrorSink,
): ResolveData? {
val signature = this
val receiverParameter = signature.receiverParameter
if (receiverParameter?.type?.classifierAsKClass()?.isInstance(caller) == false) {
errorSink.reportUnmatched(
UnmatchedCommandSignature(
signature,
FailureReason.InapplicableReceiverArgument(receiverParameter, caller)
)
)// not compatible receiver
return null
}
val valueParameters = signature.valueParameters
val zipped = valueParameters.zip(valueArguments).toMutableList()
val remainingParameters = valueParameters.drop(zipped.size).toMutableList()
if (remainingParameters.any { !it.isOptional && !it.isVararg }) {
errorSink.reportUnmatched(
UnmatchedCommandSignature(
signature,
FailureReason.NotEnoughArguments
)
)// not enough args. // vararg can be empty.
return null
}
return if (zipped.isEmpty()) {
ResolveData(
signature = signature,
zippedArguments = emptyList(),
argumentAcceptances = emptyList(),
remainingParameters = remainingParameters,
)
} else {
if (valueArguments.size > valueParameters.size && zipped.last().first.isVararg) {
// merge vararg arguments
val (varargParameter, _)
= zipped.removeLast()
zipped.add(
varargParameter to DefaultCommandValueArgument(
valueArguments.drop(zipped.size).map { it.value }.toMessageChain()
)
)
} else {
// add default empty vararg argument
val remainingVararg = remainingParameters.find { it.isVararg }
if (remainingVararg != null) {
zipped.add(remainingVararg to DefaultCommandValueArgument(EmptyMessageChain))
remainingParameters.remove(remainingVararg)
}
}
ResolveData(
signature = signature,
zippedArguments = zipped,
argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) ->
val accepting = parameter.accepting(argument, context)
if (accepting.isNotAcceptable) {
errorSink.reportUnmatched(
UnmatchedCommandSignature(
signature,
FailureReason.InapplicableValueArgument(parameter, argument)
)
)// argument type not assignable
return null
}
ArgumentAcceptanceWithIndex(index, accepting)
},
remainingParameters = remainingParameters
)
}
}
private fun resolveImpl(
caller: CommandSender,
callee: Command,
valueArguments: List<CommandValueArgument>,
context: CommandArgumentContext?,
errorSink: ErrorSink,
): ResolveData? {
callee.overloads
.mapNotNull l@{ signature ->
signature.toResolveData(caller, valueArguments, context, errorSink)
}
.also { result -> result.takeSingleResolveData()?.let { return it } }
.takeLongestMatches()
.ifEmpty { return null }
.also { result -> result.takeSingleResolveData()?.let { return it } }
// take single ArgumentAcceptance.Direct
.also { list ->
val candidates = list
.asSequence().filterIsInstance<ResolveData>()
.flatMap { phase ->
phase.argumentAcceptances.filter { it.acceptance is ArgumentAcceptance.Direct }
.map { phase to it }
}.toList()
candidates.singleOrNull()?.let { return it.first } // single Direct
if (candidates.distinctBy { it.second.index }.size != candidates.size) {
// Resolution ambiguity
/*
open class A
open class AA: A()
open class C
open class CC: C()
fun foo(a: A, c: CC) = 1
fun foo(a: AA, c: C) = 1
*/
// The call is foo(AA(), C()) or foo(A(), CC())
candidates.forEach { candidate -> errorSink.reportAmbiguity(candidate.first.signature) }
}
}
return null
}
private fun Collection<Any>.takeSingleResolveData() = asSequence().filterIsInstance<ResolveData>().singleOrNull()
/*
open class A
open class B : A()
open class C : A()
open class D : C()
open class BB : B()
fun foo(a: A, c: C) = 1
//fun foo(a: A, c: A) = 1
//fun foo(a: A, c: C, def: Int = 0) = 1
fun foo(a: B, c: C, d: D) = ""
fun foo(b: BB, a: A, d: C) = 1.0
fun main() {
val a = foo(D(), D()) // int
val b = foo(A(), C()) // int
val d = foo(BB(), c = C(), D()) // string
}
*/
private fun List<ResolveData>.takeLongestMatches(): Collection<ResolveData> {
if (isEmpty()) return emptyList()
return associateWith {
it.signature.valueParameters.size - it.remainingOptionalCount * 1.001 // slightly lower priority with optional defaults.
}.let { m ->
val maxMatch = m.values.maxByOrNull { it }
m.filter { it.value == maxMatch }.keys
}
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused", "NOTHING_TO_INLINE")
package net.mamoe.mirai.console.command.resolve
import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.command.parse.CommandCallParser
import net.mamoe.mirai.console.extensions.CommandCallInterceptorProvider
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.internal.util.UNREACHABLE_CLAUSE
import net.mamoe.mirai.console.util.safeCast
import net.mamoe.mirai.message.data.Message
import org.jetbrains.annotations.Contract
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* 指令解析和调用拦截器. 用于在指令各解析阶段拦截或转换调用.
*/
@ExperimentalCommandDescriptors
public interface CommandCallInterceptor {
/**
* 在指令[语法解析][CommandCallParser]前调用.
*
* @return `null` 表示未处理
*/
public fun interceptBeforeCall(
message: Message,
caller: CommandSender,
): InterceptResult<Message>? = null
/**
* 在指令[语法解析][CommandCallParser]后调用.
*
* @return `null` 表示未处理
*/
public fun interceptCall(
call: CommandCall,
): InterceptResult<CommandCall>? = null
/**
* 在指令[调用解析][CommandCallResolver]后调用.
*
* @return `null` 表示未处理
*/
public fun interceptResolvedCall(
call: ResolvedCommandCall,
): InterceptResult<ResolvedCommandCall>? = null
public companion object {
/**
* 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall].
* 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [Message]
*/
@JvmStatic
public fun Message.intercepted(caller: CommandSender): InterceptResult<Message> {
GlobalComponentStorage.run {
return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext ->
val intercepted = ext.instance.interceptBeforeCall(acc, caller)
intercepted?.fold(
onIntercepted = { return intercepted },
otherwise = { it }
) ?: acc
}.let { InterceptResult(it) }
}
}
/**
* 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall].
* 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [CommandCall]
*/
@JvmStatic
public fun CommandCall.intercepted(): InterceptResult<CommandCall> {
GlobalComponentStorage.run {
return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext ->
val intercepted = ext.instance.interceptCall(acc)
intercepted?.fold(
onIntercepted = { return intercepted },
otherwise = { it }
) ?: acc
}.let { InterceptResult(it) }
}
}
/**
* 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall].
* 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [ResolvedCommandCall]
*/
@JvmStatic
public fun ResolvedCommandCall.intercepted(): InterceptResult<ResolvedCommandCall> {
GlobalComponentStorage.run {
return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext ->
val intercepted = ext.instance.interceptResolvedCall(acc)
intercepted?.fold(
onIntercepted = { return intercepted },
otherwise = { it }
) ?: acc
}.let { InterceptResult(it) }
}
}
}
}
/**
* [CommandCallInterceptor] 拦截结果
*/
@ExperimentalCommandDescriptors
public class InterceptResult<T> internal constructor(
private val _value: Any?,
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?,
) {
/**
* 构造一个 [InterceptResult], [value] 继续处理后续指令执行.
*/
public constructor(value: T) : this(value as Any?, null)
/**
* 构造一个 [InterceptResult], [原因][reason] 中断指令执行.
*/
public constructor(reason: InterceptedReason) : this(reason as Any?, null)
@get:Contract(pure = true)
public val value: T?
@Suppress("UNCHECKED_CAST")
get() {
val value = this._value
return if (value is InterceptedReason) null else value as T
}
@get:Contract(pure = true)
public val reason: InterceptedReason?
get() = this._value.safeCast()
}
@ExperimentalCommandDescriptors
public inline fun <T, R> InterceptResult<T>.fold(
onIntercepted: (reason: InterceptedReason) -> R,
otherwise: (call: T) -> R,
): R {
contract {
callsInPlace(onIntercepted, InvocationKind.AT_MOST_ONCE)
callsInPlace(otherwise, InvocationKind.AT_MOST_ONCE)
}
value?.let(otherwise)
reason?.let(onIntercepted)
UNREACHABLE_CLAUSE
}
@ExperimentalCommandDescriptors
public inline fun <T : R, R> InterceptResult<T>.getOrElse(onIntercepted: (reason: InterceptedReason) -> R): R {
contract { callsInPlace(onIntercepted, InvocationKind.AT_MOST_ONCE) }
reason?.let(onIntercepted)
return value!!
}
/**
* 创建一个 [InterceptedReason]
*
* @see InterceptedReason.create
*/
@ExperimentalCommandDescriptors
@JvmSynthetic
public inline fun InterceptedReason(message: String): InterceptedReason = InterceptedReason.create(message)
/**
* 拦截原因
*/
@ExperimentalCommandDescriptors
public interface InterceptedReason {
public val message: String
public companion object {
/**
* 创建一个 [InterceptedReason]
*/
public fun create(message: String): InterceptedReason = InterceptedReasonData(message)
}
}
@OptIn(ExperimentalCommandDescriptors::class)
@Serializable
private data class InterceptedReasonData(override val message: String) : InterceptedReason

View File

@ -0,0 +1,86 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command.resolve
import net.mamoe.mirai.console.command.CommandExecuteResult
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.safeCast
import org.jetbrains.annotations.Contract
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@ExperimentalCommandDescriptors
public class CommandResolveResult private constructor(
internal val value: Any?,
) {
@get:Contract(pure = true)
public val call: ResolvedCommandCall?
get() = value.safeCast()
@get:Contract(pure = true)
public val failure: CommandExecuteResult.Failure?
get() = value.safeCast()
public constructor(call: ResolvedCommandCall) : this(call as Any?)
public constructor(failure: CommandExecuteResult.Failure) : this(failure as Any)
}
@ExperimentalCommandDescriptors
public inline fun <R> CommandResolveResult.fold(
onSuccess: (ResolvedCommandCall?) -> R,
onFailure: (CommandExecuteResult.Failure) -> R,
): R {
contract {
callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
}
failure?.let(onFailure)?.let { return it }
return call.let(onSuccess)
}
@ExperimentalCommandDescriptors
public inline fun CommandResolveResult.getOrElse(
onFailure: (CommandExecuteResult.Failure) -> ResolvedCommandCall?,
): ResolvedCommandCall {
contract {
callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
}
failure?.let(onFailure)?.let { return it }
return call!!
}
/**
* The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered []
*
* @see CommandCallResolverProvider The provider to instances of this class
* @see BuiltInCommandCallResolver The builtin implementation
*/
@ExperimentalCommandDescriptors
public interface CommandCallResolver {
public fun resolve(call: CommandCall): CommandResolveResult
public companion object {
@JvmName("resolveCall")
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public fun CommandCall.resolve(): CommandResolveResult {
GlobalComponentStorage.run {
val instance =
CommandCallResolverProvider.findSingletonInstance(CommandCallResolverProvider.builtinImplementation)
return instance.resolve(this@resolve)
}
}
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command.resolve
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.parse
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.command.parse.mapToTypeOrNull
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.cast
/**
* The resolved [CommandCall].
*
* ### Implementation details
* [ResolvedCommandCall] should be _immutable_,
* meaning all of its properties must be *pure* and should be implemented as an immutable property, or delegated by a lazy initializer.
*
* @see ResolvedCommandCallImpl
*/
@ExperimentalCommandDescriptors
public interface ResolvedCommandCall {
/**
* The [CommandSender] responsible to this call.
*/
public val caller: CommandSender
/**
* The callee [Command]
*/
public val callee: Command
/**
* The callee [CommandSignature], specifically a sub command from [CompositeCommand]
*/
public val calleeSignature: CommandSignature
/**
* Original arguments
*/
public val rawValueArguments: List<CommandValueArgument>
/**
* Resolved value arguments arranged mapping the [CommandSignature.valueParameters] by index.
*
* **Default implementation details**: Lazy calculation.
*/
@ConsoleExperimentalApi
public val resolvedValueArguments: List<ResolvedCommandValueArgument<*>>
public companion object
}
/**
* Resolved [CommandValueParameter] for [ResolvedCommandCall.resolvedValueArguments]
*/
@ExperimentalCommandDescriptors
public data class ResolvedCommandValueArgument<T>(
val parameter: CommandValueParameter<T>,
/**
* Argument value expected by the [parameter]
*/
val value: T,
)
// Don't move into companion, compilation error
/**
* Invoke this resolved call.
*/
@ExperimentalCommandDescriptors
public suspend inline fun ResolvedCommandCall.call() {
return this@call.calleeSignature.call(this@call)
}
/**
* Default implementation.
*/
@ExperimentalCommandDescriptors
public class ResolvedCommandCallImpl(
override val caller: CommandSender,
override val callee: Command,
override val calleeSignature: CommandSignature,
override val rawValueArguments: List<CommandValueArgument>,
private val context: CommandArgumentContext,
) : ResolvedCommandCall {
override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy {
calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) ->
val value = argument.mapToTypeOrNull(parameter.type) ?: context[parameter.type.classifierAsKClass()]?.parse(
argument.value,
caller
)
?: throw NoValueArgumentMappingException(argument, parameter.type)
// TODO: 2020/10/17 consider vararg and optional
ResolvedCommandValueArgument(parameter.cast(), value)
}
}
}

View File

@ -0,0 +1,186 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE", "unused")
package net.mamoe.mirai.console.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
import net.mamoe.mirai.console.internal.data.PluginDataImpl
import net.mamoe.mirai.console.internal.data.getAnnotationListForValueSerialization
import net.mamoe.mirai.console.internal.data.valueName
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.reflect.KProperty
/**
* [PluginData] 的默认实现. 支持使用 `by value()` 等委托方法创建 [Value] 并跟踪其改动.
*
* ### 实现注意
* 此类型处于实验性阶段. 使用其中定义的属性和函数是安全的, 但将来可能会新增成员抽象函数.
*
* @see PluginData
*/
public abstract class AbstractPluginData : PluginData, PluginDataImpl() {
/**
* 这个 [PluginData] 保存时使用的名称.
*/
public abstract override val saveName: String
/**
* 添加了追踪的 [ValueNode] 列表, 即通过 `by value` 初始化的属性列表.
*
* 它们的修改会被跟踪, 并触发 [onValueChanged].
*
* @see provideDelegate
*/
@ConsoleExperimentalApi
public val valueNodes: MutableList<ValueNode<*>> = mutableListOf()
/**
* 供手动实现时值跟踪使用 ( Java 用户). 一般 Kotlin 用户需使用 [provideDelegate]
*/
@ConsoleExperimentalApi
public open fun <T : SerializerAwareValue<*>> track(
value: T,
/**
* 值名称.
*
* 如果属性带有 [ValueName], 则使用 [ValueName.value],
* 否则使用 [属性名称][KProperty.name]
*
* @see [ValueNode.value]
*/
valueName: String,
annotations: List<Annotation>,
): T =
value.apply { this@AbstractPluginData.valueNodes.add(ValueNode(valueName, this, annotations, this.serializer)) }
/**
* 使用 `by value()` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪, 并创建 [ValueNode] 加入 [valueNodes]
*/
public operator fun <T : SerializerAwareValue<*>> T.provideDelegate(
thisRef: Any?,
property: KProperty<*>,
): T = track(this, property.valueName, property.getAnnotationListForValueSerialization())
/**
* 所有 [valueNodes] 更新和保存序列化器.
*/
@ConsoleExperimentalApi
public final override val updaterSerializer: KSerializer<Unit>
get() = super.updaterSerializer
@ConsoleExperimentalApi
public override val serializersModule: SerializersModule = EmptySerializersModule
/**
* 当所属于这个 [PluginData] [Value] [][Value.value] 被修改时被调用.
*/
@ConsoleExperimentalApi
public override fun onValueChanged(value: Value<*>) {
// no-op by default
}
/**
* 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用
*/
@ConsoleExperimentalApi
public override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) {
// no-op by default
}
/**
* [track] 创建, 来自一个通过 `by value` 初始化的属性节点.
*/
@ConsoleExperimentalApi
public data class ValueNode<T>(
/**
* 节点名称.
*
* 如果属性带有 [ValueName], 则使用 [ValueName.value],
* 否则使用 [属性名称][KProperty.name]
*/
val valueName: String,
/**
* 属性值代理
*/
val value: Value<out T>,
/**
* 注解列表
*/
val annotations: List<Annotation>,
/**
* 属性值更新器
*/
val updaterSerializer: KSerializer<Unit>,
)
}
/**
* 获取这个 [KProperty] 委托的 [Value]
*
* 示例:
* ```
* object MyData : AutoSavePluginData(PluginMain) {
* val list: List<String> by value()
* }
*
* val value: Value<List<String>> = MyData.findBackingFieldValue(MyData::list)
* ```
*
* @see PluginData
*/
@ConsoleExperimentalApi
public fun <T> AbstractPluginData.findBackingFieldValue(property: KProperty<T>): Value<out T>? =
findBackingFieldValue(property.valueName)
/**
* 获取这个 [KProperty] 委托的 [Value]
*
* 示例:
* ```
* object MyData : AutoSavePluginData(PluginMain) {
* @ValueName("theList")
* val list: List<String> by value()
* val int: Int by value()
* }
*
* val value: Value<List<String>> = MyData.findBackingFieldValue("theList") // 需使用 @ValueName 标注的名称
* val intValue: Value<Int> = MyData.findBackingFieldValue("int")
* ```
*
* @see PluginData
*/
@ConsoleExperimentalApi
public fun <T> AbstractPluginData.findBackingFieldValue(propertyValueName: String): Value<out T>? {
@Suppress("UNCHECKED_CAST")
return this.valueNodes.find { it.valueName == propertyValueName }?.value as Value<out T>?
}
/**
* 获取这个 [KProperty] 委托的 [Value]
*
* 示例:
* ```
* object MyData : AutoSavePluginData(PluginMain) {
* val list: List<String> by value()
* }
*
* val value: PluginData.ValueNode<List<String>> = MyData.findBackingFieldValueNode(MyData::list)
* ```
*
* @see PluginData
*/
@ConsoleExperimentalApi
public fun <T> AbstractPluginData.findBackingFieldValueNode(property: KProperty<T>): AbstractPluginData.ValueNode<out T>? {
@Suppress("UNCHECKED_CAST")
return this.valueNodes.find { it == property } as AbstractPluginData.ValueNode<out T>?
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.data
import kotlinx.coroutines.Job
/**
* 链接自动保存的 [PluginConfig].
*
* 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存
*
* [AutoSavePluginDataHolder.coroutineContext] 含有 [Job], [AutoSavePluginData] 会通过 [Job.invokeOnCompletion] Job 完结时触发自动保存.
*
* @see PluginConfig
* @see AutoSavePluginData
*/
public open class AutoSavePluginConfig public constructor(saveName: String) : AutoSavePluginData(saveName), PluginConfig

View File

@ -0,0 +1,147 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused", "PropertyName", "PrivatePropertyName")
package net.mamoe.mirai.console.data
import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
import net.mamoe.mirai.console.internal.util.runIgnoreException
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.TimedTask
import net.mamoe.mirai.console.util.launchTimedTask
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.error
import net.mamoe.mirai.utils.withSwitch
/**
* 链接自动保存的 [PluginData].
*
* 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存
*
* [AutoSavePluginDataHolder.coroutineContext] 含有 [Job], [AutoSavePluginData] 会通过 [Job.invokeOnCompletion] Job 完结时触发自动保存.
*
* @see PluginData
*/
public open class AutoSavePluginData private constructor(
// KEEP THIS PRIMARY CONSTRUCTOR FOR FUTURE USE: WE'LL SUPPORT SERIALIZERS_MODULE FOR POLYMORPHISM
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?,
) : AbstractPluginData() {
private lateinit var owner_: AutoSavePluginDataHolder
private val autoSaveIntervalMillis_: LongRange get() = owner_.autoSaveIntervalMillis
private lateinit var storage_: PluginDataStorage
public final override val saveName: String
get() = _saveName
@Suppress("JoinDeclarationAndAssignment") // bug
private lateinit var _saveName: String
public constructor(saveName: String) : this(null) {
_saveName = saveName
}
private fun logException(e: Throwable) {
owner_.coroutineContext[CoroutineExceptionHandler]?.handleException(owner_.coroutineContext, e)
?.let { return }
MiraiConsole.mainLogger.error(
"An exception occurred when saving config ${this@AutoSavePluginData::class.qualifiedNameOrTip} " +
"but CoroutineExceptionHandler not found in PluginDataHolder.coroutineContext for ${owner_::class.qualifiedNameOrTip}",
e
)
}
@ConsoleExperimentalApi
override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) {
check(owner is AutoSavePluginDataHolder) { "owner must be AutoSavePluginDataHolder for AutoSavePluginData" }
if (this::storage_.isInitialized) {
check(storage == this.storage_) { "AutoSavePluginData is already initialized with one storage and cannot be reinitialized with another." }
}
this.storage_ = storage
this.owner_ = owner
owner_.coroutineContext[Job]?.invokeOnCompletion { save() }
saverTask = owner_.launchTimedTask(
intervalMillis = autoSaveIntervalMillis_.first,
coroutineContext = CoroutineName("AutoSavePluginData.saver: ${this::class.qualifiedNameOrTip}")
) { save() }
if (shouldPerformAutoSaveWheneverChanged()) {
// 定时自动保存, 用于 kts 序列化的对象
owner_.launch(CoroutineName("AutoSavePluginData.timedAutoSave: ${this::class.qualifiedNameOrTip}")) {
while (isActive) {
runIgnoreException<CancellationException> { delay(autoSaveIntervalMillis_.last) } ?: return@launch
doSave()
}
}
}
}
private var saverTask: TimedTask? = null
/**
* @return `true` , 一段时间后, 即使无属性改变, 也会进行保存.
*/
@ConsoleExperimentalApi
protected open fun shouldPerformAutoSaveWheneverChanged(): Boolean {
return true
}
@ConsoleExperimentalApi
public final override fun onValueChanged(value: Value<*>) {
debuggingLogger1.error { "onValueChanged: $value" }
saverTask?.setChanged()
}
private fun save() {
kotlin.runCatching {
doSave()
}.onFailure { e ->
logException(e)
}
}
private fun doSave() {
debuggingLogger1.error { "doSave: ${this::class.qualifiedName}" }
storage_.store(owner_, this)
}
}
internal val debuggingLogger1 by lazy {
MiraiLogger.Factory.create(AutoSavePluginData::class, "console.debug").withSwitch(false)
}
@Suppress("RESULT_CLASS_IN_RETURN_TYPE")
internal inline fun <R> MiraiLogger.runCatchingLog(message: String? = null, block: () -> R): Result<R> {
return kotlin.runCatching {
block()
}.onFailure {
if (message != null) {
error(message, it)
} else error(it)
}
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@kotlin.internal.LowPriorityInOverloadResolution
internal inline fun <R> MiraiLogger.runCatchingLog(message: (Throwable) -> String, block: () -> R): R? {
return kotlin.runCatching {
block()
}.onFailure {
error(message(it), it)
}.getOrNull()
}
@Suppress("SpellCheckingInspection")
private const val MAGIC_NUMBER_CFST_INIT: Long = Long.MAX_VALUE

View File

@ -0,0 +1,41 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.data
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/**
* 可以持有相关 [AutoSavePluginData] 的对象.
*
* ### 实现 [AutoSavePluginDataHolder]
* [CoroutineScope.coroutineContext] 中应用 [CoroutineExceptionHandler]
*
* @see net.mamoe.mirai.console.plugin.jvm.JvmPlugin
*/
@ConsoleExperimentalApi
public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope {
/**
* [AutoSavePluginData] 每次自动保存时间间隔
*
* - 区间的左端点为最小间隔, 一个 [Value] 被修改后, 若此时间段后无其他修改, 将触发自动保存; 若有, 将重新开始计时.
* - 区间的右端点为最大间隔, 一个 [Value] 被修改后, 最多不超过这个时间段后就会被保存.
*
* [AutoSavePluginDataHolder.coroutineContext] 含有 [Job],
* [AutoSavePluginData] 会通过 [Job.invokeOnCompletion] Job 完结时触发自动保存.
*
* @see LongRange Java 用户使用 [LongRange] 的构造器创建
* @see Long.rangeTo Kotlin 用户使用 [Long.rangeTo] 创建, `3000..50000`
*/
@ConsoleExperimentalApi
public val autoSaveIntervalMillis: LongRange
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.data
import net.mamoe.mirai.console.data.java.JAutoSavePluginConfig
/**
* 一个插件的配置数据, 用于和用户交互.
*
* 用户可通过 mirai-console 前端 (如在 Android 中可视化实现) 修改这些配置, 修改会自动写入这个对象中.
*
* **提示**:
* 插件内部的数据应用 [PluginData] 存储, 而不能使用 [PluginConfig].
*
* ## 实现
* 对使用者来说, [PluginConfig] [PluginData] 实现几乎相同. 目前仅需在 [PluginData] 使用的基础上添加接口实现即可.
*
* ### Kotlin
* [PluginData] 的示例基础上, 修改对象定义
* ```
* // 原
* object MyPluginData : AutoSavePluginData()
* // 修改为
* object MyPluginConfig : AutoSavePluginConfig()
* ```
* 即可将一个 [PluginData] 变更为 [PluginConfig].
*
* ### Java
* [JAutoSavePluginConfig]
*
* @see PluginData
*/
public interface PluginConfig : PluginData {
/**
* 警告: [PluginConfig] 的实现处于实验性阶段.
*
* 自主实现 [PluginConfig] 将得不到兼容性保障. 请仅考虑使用 [AutoSavePluginConfig]
*/
}

View File

@ -0,0 +1,247 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress(
"INVISIBLE_REFERENCE",
"INVISIBLE_MEMBER",
"EXPOSED_SUPER_CLASS",
"NOTHING_TO_INLINE", "unused", "UNCHECKED_CAST"
)
@file:JvmName("PluginDataKt")
package net.mamoe.mirai.console.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.serializersModuleOf
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_NO_ARG_CONSTRUCTOR
import net.mamoe.mirai.console.data.java.JAutoSavePluginData
import net.mamoe.mirai.console.internal.data.createInstanceSmart
import net.mamoe.mirai.console.internal.data.typeOf0
import net.mamoe.mirai.console.internal.data.valueFromKTypeImpl
import net.mamoe.mirai.console.internal.data.valueImpl
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.reloadPluginData
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass
import kotlin.reflect.KType
/**
* 一个插件内部的, 对用户隐藏的数据对象. 可包含对多个 [Value] 的值变更的跟踪. 典型的实现为 [AbstractPluginData].
*
* [AbstractPluginData] 不涉及有关数据的存储, 而是只维护数据结构: [属性节点列表][AbstractPluginData.valueNodes].
*
* 有关存储方案, 请查看 [PluginDataStorage].
*
* **注意**: [PluginData] 总应该是单例的.
*
* ## [JvmPlugin] 的实现方案
*
* 要修改保存时的名称, 请参考 [ValueName]
*
* ### 使用 Kotlin
*
* [JvmPlugin] 的典型实现方式:
* ```
* object PluginMain : KotlinPlugin()
*
* object MyPluginData : AutoSavePluginData() {
* var list: MutableList<String> by value(mutableListOf("a", "b")) // mutableListOf("a", "b") 是初始值, 可以省略
* val custom: Map<Long, CustomData> by value() // 使用 kotlinx-serialization 序列化的类型.
* var long: Long by value(0) // 允许 var
* var int by value(0) // 可以使用类型推断, 但更推荐使用 `var long: Long by value(0)` 这种定义方式.
*
* // 将 MutableMap<Long, Long> 映射到 MutableMap<Bot, Long>.
* val botToLongMap: MutableMap<Bot, Long> by value<MutableMap<Long, Long>>().mapKeys(Bot::getInstance, Bot::id)
* }
*
* @Serializable // kotlinx.serialization: https://github.com/Kotlin/kotlinx.serialization
* data class CustomData(
* // ...
* )
* ```
*
* 使用时, 可以方便地直接调用, :
* ```
* val theList: MutableList<String> = AccountPluginData.list
* ```
*
* 但也注意, 不要存储 `AccountPluginData.list`. 它可能受不到值跟踪. 若必要存储, 请使用 [AbstractPluginData.findBackingFieldValue]
*
* ### 使用 Java
*
* 参考 [JAutoSavePluginData]
*
* ## 非引用赋值
*
* 由于实现特殊, 赋值时不会写其引用. :
* ```
* val list = ArrayList<String>("A")
* MyPluginData.list = list // 赋值给 PluginData 的委托属性是非引用的
* println(MyPluginData.list) // "[A]"
*
* list.add("B")
* println(list) // "[A, B]"
* println(MyPluginData.list) // "[A]" // !! 由于 `list` 的引用并未赋值给 `MyPluginData.list`.
* ```
*
* 另一个更容易出错的示例:
* ```
* // MyPluginData.nestedMap: MutableMap<Long, List<Long>> by value()
* val newList = MyPluginData.map.getOrPut(1, ::mutableListOf)
* newList.add(1) // 不会添加到 MyPluginData.nestedMap 中, 因为 `mutableListOf` 创建的 MutableList 被非引用 (浅拷贝) 地添加进了 MyPluginData.nestedMap
* ```
*
* 一个解决方案是对 [SerializerAwareValue] 做映射或相关修改. [PluginDataExtensions].
*
* 要查看详细的解释请查看 [docs/PluginData.md](https://github.com/mamoe/mirai-console/blob/master/docs/PluginData.md)
*
* ## 实现注意
* 此类型处于实验性阶段. 使用其中定义的属性和函数是安全的, 但将来可能会新增成员抽象函数.
*
* @see AbstractJvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
* @see PluginDataStorage [PluginData] 存储仓库
* @see PluginDataExtensions 相关 [SerializerAwareValue] 映射函数
*/
public interface PluginData {
/**
* 这个 [PluginData] 保存时使用的名称.
*/
@ConsoleExperimentalApi
public val saveName: String
@ConsoleExperimentalApi
public val updaterSerializer: KSerializer<Unit>
/**
* 当所属于这个 [PluginData] [Value] [][Value.value] 被修改时被调用.
* 调用者为 [Value] 的实现.
*/
@ConsoleExperimentalApi
public fun onValueChanged(value: Value<*>)
/**
* 用于支持多态序列化.
*
* @see SerializersModule
* @see serializersModuleOf
*/
@ConsoleExperimentalApi
public val serializersModule: SerializersModule
/**
* 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用
*/
@ConsoleExperimentalApi
public fun onInit(owner: PluginDataHolder, storage: PluginDataStorage)
}
// don't default = 0, cause ambiguity
//// region PluginData_value_primitives CODEGEN ////
/**
* 创建一个 [Byte] 类型的 [Value], 并设置初始值为 [default]
*/
public fun PluginData.value(default: Byte): SerializerAwareValue<Byte> = valueImpl(default)
/**
* 创建一个 [Short] 类型的 [Value], 并设置初始值为 [default]
*/
public fun PluginData.value(default: Short): SerializerAwareValue<Short> = valueImpl(default)
/**
* 创建一个 [Int] 类型的 [Value], 并设置初始值为 [default]
*/
public fun PluginData.value(default: Int): SerializerAwareValue<Int> = valueImpl(default)
/**
* 创建一个 [Long] 类型的 [Value], 并设置初始值为 [default]
*/
public fun PluginData.value(default: Long): SerializerAwareValue<Long> = valueImpl(default)
/**
* 创建一个 [Float] 类型的 [Value], 并设置初始值为 [default]
*/
public fun PluginData.value(default: Float): SerializerAwareValue<Float> = valueImpl(default)
/**
* 创建一个 [Double] 类型的 [Value], 并设置初始值为 [default]
*/
public fun PluginData.value(default: Double): SerializerAwareValue<Double> = valueImpl(default)
/**
* 创建一个 [Char] 类型的 [Value], 并设置初始值为 [default]
*/
public fun PluginData.value(default: Char): SerializerAwareValue<Char> = valueImpl(default)
/**
* 创建一个 [Boolean] 类型的 [Value], 并设置初始值为 [default]
*/
public fun PluginData.value(default: Boolean): SerializerAwareValue<Boolean> = valueImpl(default)
/**
* 创建一个 [String] 类型的 [Value], 并设置初始值为 [default]
*/
public fun PluginData.value(default: String): SerializerAwareValue<String> = valueImpl(default)
//// endregion PluginData_value_primitives CODEGEN ////
/**
* 通过具体化类型创建一个 [SerializerAwareValue], 并设置初始值.
*
* @param T 具体化参数类型 T. 仅支持:
* - 基础数据类型
* - 标准库集合类型 ([List], [Map], [Set])
* - 标准库数据类型 ([Map.Entry], [Pair], [Triple])
* - 和使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的
*/
@Suppress("UNCHECKED_CAST")
@LowPriorityInOverloadResolution
public inline fun <reified T> PluginData.value(
default: T,
crossinline apply: T.() -> Unit = {},
): SerializerAwareValue<T> =
valueFromKType(typeOf0<T>(), default).also { it.value.apply() }
/**
* 通过具体化类型创建一个 [SerializerAwareValue].
* @see valueFromKType 查看更多实现信息
*/
@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR)
@LowPriorityInOverloadResolution
public inline fun <@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR) reified T>
PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue<T> =
valueImpl<T>(typeOf0<T>(), T::class).also { it.value.apply() }
@Suppress("UNCHECKED_CAST")
@PublishedApi
internal fun <T> PluginData.valueImpl(type: KType, classifier: KClass<*>): SerializerAwareValue<T> =
valueFromKType(type, classifier.run { objectInstance ?: createInstanceSmart() } as T)
/**
* 通过一个特定的 [KType] 创建 [Value], 并设置初始值.
*
* 对于 [Map], [Set], [List] 等标准库类型, 这个函数会尝试构造 [LinkedHashMap], [LinkedHashSet], [ArrayList] 等相关类型.
* 而对于自定义数据类型, 本函数只会反射获取 [objectInstance][KClass.objectInstance] 或使用*无参构造器*构造实例.
*
* @param T 具体化参数类型 T. 仅支持:
* - 基础数据类型, [String]
* - 标准库集合类型 ([List], [Map], [Set])
* - 标准库数据类型 ([Map.Entry], [Pair], [Triple])
* - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的类
*/
@Suppress("UNCHECKED_CAST")
@ConsoleExperimentalApi
public fun <T> PluginData.valueFromKType(type: KType, default: T): SerializerAwareValue<T> =
(valueFromKTypeImpl(type) as SerializerAwareValue<Any?>).apply { this.value = default } as SerializerAwareValue<T>

View File

@ -0,0 +1,366 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused", "INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package net.mamoe.mirai.console.data
import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault
import net.mamoe.mirai.console.internal.data.ShadowMap
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.internal.LowPriorityInOverloadResolution
/**
* [PluginData] 相关一些扩展
*/
public object PluginDataExtensions {
@ConsoleExperimentalApi
public open class NotNullMap<K, V> internal constructor(
private val delegate: Map<K, V>
) : Map<K, V> by delegate {
override fun get(key: K): V =
delegate[key] ?: error("Internal error: delegate[key] returned null for NotNullMap.get")
@Deprecated(
"getOrDefault on NotNullMap always returns the value in the map, and defaultValue will never be returned.",
level = DeprecationLevel.WARNING,
replaceWith = ReplaceWith("this.get(key)")
)
override fun getOrDefault(key: K, defaultValue: V): V {
return super.getOrDefault(key, defaultValue)
}
}
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") // as designed
public class NotNullMutableMap<K, V> internal constructor(
private val delegate: MutableMap<K, V>
) : MutableMap<K, V> by delegate, NotNullMap<K, V>(delegate) {
override fun get(key: K): V =
delegate[key] ?: error("Internal error: delegate[key] returned null for NotNullMutableMap.get")
@Deprecated(
"getOrDefault on NotNullMutableMap always returns the value in the map, and defaultValue will never be returned.",
level = DeprecationLevel.WARNING,
replaceWith = ReplaceWith("this.get(key)")
)
override fun getOrDefault(key: K, defaultValue: V): V {
return super<MutableMap>.getOrDefault(key, defaultValue)
}
@Deprecated(
"putIfAbsent on NotNullMutableMap always does nothing.",
level = DeprecationLevel.WARNING,
replaceWith = ReplaceWith("")
)
override fun putIfAbsent(key: K, value: V): Nothing? = null
}
/**
* 创建一个代理对象, [Map.get] 返回 `null` 时先放入一个 [LinkedHashMap], 再从 [this] 中取出链接自动保存的 [LinkedHashMap]. ([MutableMap.getOrPut] 的替代)
*
* @see withDefault
*/
@JvmName("withEmptyDefaultMapImmutable")
@JvmStatic
public fun <K, InnerE, InnerV> SerializerAwareValue<MutableMap<K, Map<InnerE, InnerV>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, Map<InnerE, InnerV>>> {
return this.withDefault { LinkedHashMap() }
}
/**
* 创建一个代理对象, [Map.get] 返回 `null` 时先放入一个 [LinkedHashMap], 再从 [this] 中取出链接自动保存的 [LinkedHashMap]. ([MutableMap.getOrPut] 的替代)
* @see withDefault
*/
@JvmName("withEmptyDefaultMap")
@JvmStatic
public fun <K, InnerE, InnerV> SerializerAwareValue<MutableMap<K, MutableMap<InnerE, InnerV>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, MutableMap<InnerE, InnerV>>> {
return this.withDefault { LinkedHashMap() }
}
/**
* 创建一个代理对象, [Map.get] 返回 `null` 时先放入一个 [ArrayList], 再从 [this] 中取出链接自动保存的 [ArrayList].
* @see withDefault
*/
@JvmName("withEmptyDefaultListImmutable")
@JvmStatic
public fun <K, E> SerializerAwareValue<MutableMap<K, List<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, List<E>>> {
return this.withDefault { ArrayList() }
}
/**
* 创建一个代理对象, [Map.get] 返回 `null` 时先放入一个 [ArrayList], 再从 [this] 中取出链接自动保存的 [ArrayList].
* @see withDefault
*/
@JvmName("withEmptyDefaultList")
@JvmStatic
public fun <K, E> SerializerAwareValue<MutableMap<K, MutableList<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, MutableList<E>>> {
return this.withDefault { ArrayList() }
}
/**
* 创建一个代理对象, [Map.get] 返回 `null` 时先放入一个 [LinkedHashSet], 再从 [this] 中取出链接自动保存的 [LinkedHashSet].
* @see withDefault
*/
@JvmName("withEmptyDefaultSetImmutable")
@JvmStatic
public fun <K, E> SerializerAwareValue<MutableMap<K, Set<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, Set<E>>> {
return this.withDefault { LinkedHashSet() }
}
/**
* 创建一个代理对象, [Map.get] 返回 `null` 时先放入一个 [LinkedHashSet], 再从 [this] 中取出链接自动保存的 [LinkedHashSet].
* @see withDefault
*/
@JvmName("withEmptyDefaultSet")
@JvmStatic
public fun <K, E> SerializerAwareValue<MutableMap<K, MutableSet<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, MutableSet<E>>> {
return this.withDefault { LinkedHashSet() }
}
/**
* 创建一个代理对象, [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值
*/
@JvmStatic
@JvmName("withDefaultMapImmutableNotNull")
public fun <K, V : Any> SerializerAwareValue<Map<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<NotNullMap<K, V>> {
@Suppress("UNCHECKED_CAST") // magic
return (this as SerializerAwareValue<MutableMap<K, V>>).withDefault(defaultValueComputer) as SerializerAwareValue<NotNullMap<K, V>>
}
/**
* 创建一个代理对象, [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值
*/
@JvmStatic
@LowPriorityInOverloadResolution
@JvmName("withDefaultMapImmutable")
public fun <K, V> SerializerAwareValue<Map<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<Map<K, V>> {
@Suppress("UNCHECKED_CAST") // magic
return (this as SerializerAwareValue<MutableMap<K, V>>).withDefault(defaultValueComputer) as SerializerAwareValue<Map<K, V>>
}
@JvmStatic
@JvmName("withDefaultMapNotNull")
public fun <K, V : Any> SerializerAwareValue<MutableMap<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<NotNullMutableMap<K, V>> {
val origin = this
@Suppress("UNCHECKED_CAST")
return SerializableValue(
object : CompositeMapValue<K, V> {
private val instance = NotNullMutableMap(createDelegateInstance(origin, defaultValueComputer))
override var value: Map<K, V>
get() = instance
set(value) {
origin.value = value as MutableMap<K, V> // erased cast
}
} as Value<NotNullMutableMap<K, V>>, // erased cast
this.serializer
)
}
/**
* 创建一个代理对象, [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值
*/
@LowPriorityInOverloadResolution
@JvmStatic
@JvmName("withDefaultMap")
public fun <K, V> SerializerAwareValue<MutableMap<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<MutableMap<K, V>> {
val origin = this
@Suppress("UNCHECKED_CAST")
return SerializableValue(
object : CompositeMapValue<K, V> {
private val instance = createDelegateInstance(origin, defaultValueComputer)
override var value: Map<K, V>
get() = instance
set(value) {
origin.value = value as MutableMap<K, V> // erased cast
}
} as Value<MutableMap<K, V>>, // erased cast
this.serializer
)
}
private fun <K, V> createDelegateInstance(
origin: SerializerAwareValue<MutableMap<K, V>>,
defaultValueComputer: (K) -> V,
): MutableMap<K, V> {
return object : MutableMap<K, V>, AbstractMap<K, V>() {
override val entries: MutableSet<MutableMap.MutableEntry<K, V>> get() = origin.value.entries
override val keys: MutableSet<K> get() = origin.value.keys
override val values: MutableCollection<V> get() = origin.value.values
override fun clear() = origin.value.clear()
override fun putAll(from: Map<out K, V>) = origin.value.putAll(from)
override fun remove(key: K): V? = origin.value.remove(key)
override fun put(key: K, value: V): V? = origin.value.put(key, value)
override fun get(key: K): V? {
// the only difference
val result = origin.value[key]
if (result != null) return result
put(key, defaultValueComputer(key))
return origin.value[key]
}
}
}
/**
* 替换 [MutableMap] key
*/
@JvmName("mapKeysNotNull")
@JvmStatic
public fun <OldK, NewK, V : Any> SerializerAwareValue<NotNullMutableMap<OldK, V>>.mapKeys(
oldToNew: (OldK) -> NewK,
newToOld: (NewK) -> OldK,
): SerializerAwareValue<NotNullMutableMap<NewK, V>> {
val origin = this
@Suppress("UNCHECKED_CAST")
return SerializableValue(
object : CompositeMapValue<NewK, V> {
private val instance =
NotNullMutableMap(ShadowMap({ origin.value }, oldToNew, newToOld, { it }, { it }))
override var value: Map<NewK, V>
get() = instance
set(value) {
origin.value =
value.mapKeysTo(NotNullMutableMap(LinkedHashMap())) { it.key.let(newToOld) } // erased cast
}
} as Value<NotNullMutableMap<NewK, V>>, // erased cast
this.serializer
)
}
/**
* 替换 [MutableMap] key
*/
@JvmName("mapKeys")
@JvmStatic
public fun <OldK, NewK, V> SerializerAwareValue<MutableMap<OldK, V>>.mapKeys(
oldToNew: (OldK) -> NewK,
newToOld: (NewK) -> OldK,
): SerializerAwareValue<MutableMap<NewK, V>> {
val origin = this
@Suppress("UNCHECKED_CAST")
return SerializableValue(
object : CompositeMapValue<NewK, V> {
private val instance = ShadowMap({ origin.value }, oldToNew, newToOld, { it }, { it })
override var value: Map<NewK, V>
get() = instance
set(value) {
origin.value = value.mapKeysTo(LinkedHashMap()) { it.key.let(newToOld) } // erased cast
}
} as Value<MutableMap<NewK, V>>, // erased cast
this.serializer
)
}
/**
* 替换 [Map] key
*/
@JvmName("mapKeysImmutable")
@JvmStatic
public fun <OldK, NewK, V> SerializerAwareValue<Map<OldK, V>>.mapKeys(
oldToNew: (OldK) -> NewK,
newToOld: (NewK) -> OldK,
): SerializerAwareValue<Map<NewK, V>> {
val origin = this
@Suppress("UNCHECKED_CAST")
return SerializableValue(
object : CompositeMapValue<NewK, V> {
// casting Map to MutableMap is OK here, as we don't call mutable functions
private val instance =
ShadowMap({ origin.value as MutableMap<OldK, V> }, oldToNew, newToOld, { it }, { it })
override var value: Map<NewK, V>
get() = instance
set(value) {
origin.value = value.mapKeysTo(LinkedHashMap()) { it.key.let(newToOld) } // erased cast
}
} as Value<Map<NewK, V>>, // erased cast
this.serializer
)
}
/**
* 替换 [Map] key
*/
@JvmName("mapKeysImmutableNotNull")
@JvmStatic
public fun <OldK, NewK, V : Any> SerializerAwareValue<NotNullMap<OldK, V>>.mapKeys(
oldToNew: (OldK) -> NewK,
newToOld: (NewK) -> OldK,
): SerializerAwareValue<NotNullMap<NewK, V>> {
val origin = this
@Suppress("UNCHECKED_CAST")
return SerializableValue(
object : CompositeMapValue<NewK, V> {
// casting Map to MutableMap is OK here, as we don't call mutable functions
private val instance =
NotNullMap(ShadowMap({ origin.value as MutableMap<OldK, V> }, oldToNew, newToOld, { it }, { it }))
override var value: Map<NewK, V>
get() = instance
set(value) {
origin.value =
value.mapKeysTo(NotNullMutableMap(LinkedHashMap())) { it.key.let(newToOld) } // erased cast
}
} as Value<NotNullMap<NewK, V>>, // erased cast
this.serializer
)
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST", "unused")
package net.mamoe.mirai.console.data
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/**
* 可以持有相关 [PluginData] 实例的对象, 作为 [PluginData] 实例的拥有者.
*
* @see PluginDataStorage.load
* @see PluginDataStorage.store
*
* @see AutoSavePluginDataHolder 支持自动保存
*/
@ConsoleExperimentalApi
public interface PluginDataHolder {
/**
* 保存时使用的分类名
*/
@ConsoleExperimentalApi
public val dataHolderName: String
}
/*
public interface PluginDataHolder {
/**
* 创建一个 [PluginData] 实例.
*
* 注意, 此时的 [PluginData] 并没有绑定 [PluginDataStorage], 因此无法进行保存等操作.
*
* @see Companion.newPluginDataInstance
* @see KClass.createType
*/
public fun <T : PluginData> newPluginDataInstance(type: KType): T =
newPluginDataInstanceUsingReflection<PluginData>(type) as T
public companion object {
/**
* 创建一个 [PluginData] 实例.
*
* @see PluginDataHolder.newPluginDataInstance
*/
@JvmSynthetic
public inline fun <reified T : PluginData> PluginDataHolder.newPluginDataInstance(): T {
return this.newPluginDataInstance(typeOf0<T>())
}
}
*/
/*
public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope {
/**
* 仅支持确切的 [PluginData] 类型
*/
public override fun <T : PluginData> newPluginDataInstance(type: KType): T {
val classifier = type.classifier?.cast<KClass<PluginData>>()
require(classifier != null && classifier.java == PluginData::class.java) {
"Cannot create PluginData instance. AutoSavePluginDataHolder supports only PluginData type."
}
return AutoSavePluginData(this, classifier) as T // T is always PluginData
}
*/

View File

@ -0,0 +1,141 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST", "unused")
package net.mamoe.mirai.console.data
import net.mamoe.mirai.console.internal.data.MemoryPluginDataStorageImpl
import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import java.io.File
import java.nio.file.Path
/**
* [数据对象][PluginData] 存储仓库.
*
* ## 职责
* [PluginDataStorage] 类似于一个数据库, 它只承担将序列化之后的数据保存到数据库中, 和从数据库取出这个对象的任务.
*
*
* 此为较低层的 API, 一般插件开发者不会接触.
*
* [JvmPluginLoader] 实现一个 [PluginDataStorage], 用于管理所有 [JvmPlugin] [PluginData] 实例.
*
* ### 实现 [PluginDataStorage]
* 无特殊需求. 实现两个成员函数即可. 可参考内建 [MultiFilePluginDataStorageImpl]
*
* @see PluginDataHolder
* @see JvmPluginLoader.dataStorage
*/
@ConsoleExperimentalApi
public interface PluginDataStorage {
/**
* 读取一个实例. 并为 [instance] [设置 [PluginDataStorage]][PluginData.onInit]
*/
@ConsoleExperimentalApi
public fun load(holder: PluginDataHolder, instance: PluginData)
/**
* 保存一个实例.
*
* **实现细节**: 调用 [PluginData.updaterSerializer]
*/
@ConsoleExperimentalApi
public fun store(holder: PluginDataHolder, instance: PluginData)
/*
public companion object {
/**
* 通过反射
* 读取一个实例.
*
* [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage]
*/
@ConsoleExperimentalAPI
@JvmStatic
public fun <T : PluginData> PluginDataStorage.load(holder: PluginDataHolder, dataClass: KClass<T>): T {
@Suppress("UNCHECKED_CAST")
val instance = with(dataClass){
objectInstance
?: createInstanceOrNull()
?: throw IllegalArgumentException(
"Cannot create PluginData instance. Make sure dataClass is PluginData::class.java or a Kotlin's object, " +
"or has a constructor which either has no parameters or all parameters of which are optional"
)
}
load(holder, instance)
return instance
}
/**
* 读取一个实例. [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage]
*/
@JvmStatic
public fun <T : PluginData> PluginDataStorage.load(holder: PluginDataHolder, dataClass: Class<T>): T =
this.load(holder, dataClass.java)
/**
* 读取一个实例. [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage]
*/
@JvmSynthetic
public inline fun <reified T : PluginData> PluginDataStorage.load(holder: PluginDataHolder): T =
this.load(holder, T::class)
}
*/
}
/**
* 在内存存储所有 [PluginData] 实例的 [PluginDataStorage]. 在内存数据丢失后相关 [PluginData] 实例也会丢失.
* @see PluginDataStorage
*/
@ConsoleExperimentalApi
public interface MemoryPluginDataStorage : PluginDataStorage {
public companion object {
/**
* 创建一个 [MemoryPluginDataStorage] 实例.
*/
@JvmStatic
@JvmName("create")
// @JvmOverloads
public operator fun invoke(): MemoryPluginDataStorage = MemoryPluginDataStorageImpl()
}
}
/**
* 用多个文件存储 [PluginData] 实例的 [PluginDataStorage].
*/
@ConsoleExperimentalApi
public interface MultiFilePluginDataStorage : PluginDataStorage {
/**
* 存放 [PluginData] 的目录.
*/
public val directoryPath: Path
public companion object {
/**
* 创建一个 [MultiFilePluginDataStorage] 实例.
*
* @see directory 存放 [PluginData] 的目录.
*/
@JvmStatic
@JvmName("create")
public operator fun invoke(directory: Path): MultiFilePluginDataStorage =
MultiFilePluginDataStorageImpl(directory)
}
}
@ConsoleExperimentalApi
@get:JvmSynthetic
public inline val MultiFilePluginDataStorage.directory: File
get() = this.directoryPath.toFile()

View File

@ -0,0 +1,21 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.data
/**
* 只读的 [PluginConfig]. 插件只能读取其值, 但值可能在后台被前端 (用户) 修改.
*
* @see PluginConfig
* @see AutoSavePluginData
* @since 1.1
*/
public open class ReadOnlyPluginConfig public constructor(saveName: String) : ReadOnlyPluginData(saveName), PluginConfig

View File

@ -0,0 +1,45 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.data
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.error
/**
* 只读的 [PluginData]. 插件只能读取其值, 但值可能在后台被前端 (用户) 修改.
*
* @see PluginData
* @see AutoSavePluginData
* @since 1.1
*/
public open class ReadOnlyPluginData private constructor(
// KEEP THIS PRIMARY CONSTRUCTOR FOR FUTURE USE: WE'LL SUPPORT SERIALIZERS_MODULE FOR POLYMORPHISM
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?,
) : AbstractPluginData() {
public final override val saveName: String
get() = _saveName
private lateinit var _saveName: String
public constructor(saveName: String) : this(null) {
_saveName = saveName
}
@ConsoleExperimentalApi
override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) {
}
@ConsoleExperimentalApi
public final override fun onValueChanged(value: Value<*>) {
debuggingLogger1.error { "onValueChanged: $value" }
}
}

View File

@ -0,0 +1,325 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused", "NOTHING_TO_INLINE", "INAPPLICABLE_JVM_NAME")
package net.mamoe.mirai.console.data
import kotlinx.serialization.BinaryFormat
import kotlinx.serialization.KSerializer
import kotlinx.serialization.StringFormat
import net.mamoe.mirai.console.internal.data.map
import net.mamoe.mirai.console.internal.data.setValueBySerializer
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/**
* 表示一个值代理.
*
* [Value.value] 可以像 Kotlin `var` 一样被修改, 然而它也可能被用户修改, 如通过 UI 前端, 或通过自动重载.
*
* 一些常用的基础类型实现由代码生成创建特性的优化.
*
* @see PluginData 容纳 [Value] 的数据对象
*
* @see PrimitiveValue 基础数据类型实现
* @see CompositeValue 复合数据类型实现
*/
public interface Value<T> : ReadWriteProperty<Any?, T> {
@get:JvmName("get")
@set:JvmName("set")
public var value: T
@JvmSynthetic // avoid ambiguity with property `value`
public override operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
@JvmSynthetic
public override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
/**
* 可被序列化的 [Value].
*/
public class SerializableValue<T>(
@JvmField internal val delegate: Value<T>,
/**
* 用于更新和保存 [delegate] 的序列化器
*/
public override val serializer: KSerializer<Unit>
) : Value<T> by delegate, SerializerAwareValue<T> {
public override fun toString(): String = delegate.toString()
public override fun equals(other: Any?): Boolean {
if (other === this) return true
if (other?.javaClass != this.javaClass) return false
@Suppress("UNCHECKED_CAST")
other as SerializableValue<T>
if (other.delegate != this.delegate) return false
// if (other.serializer != this.serializer) return false
// TODO: 2020/9/9 serializers should be checked here, but it will cause incomparable issue when putting a SerializableValue as a Key
return true
}
override fun hashCode(): Int {
@Suppress("UnnecessaryVariable", "CanBeVal")
var result = delegate.hashCode()
// result = 31 * result + serializer.hashCode()
// TODO: 2020/9/9 serializers should be checked here, but it will cause incomparable issue when putting a SerializableValue as a Key
return result
}
public companion object {
@JvmStatic
@JvmName("create")
public fun <T> Value<T>.serializableValueWith(
serializer: KSerializer<T>
): SerializableValue<T> {
return SerializableValue(
this,
serializer.map(serializer = {
this.value
}, deserializer = { this.setValueBySerializer(it) })
)
}
}
}
/**
* 带有显式 [序列化器][serializer] [Value].
*
* @see SerializableValue 简单实现
* @see PluginData.value 创建一个这样的 [SerializerAwareValue]
*/
public interface SerializerAwareValue<T> : Value<T> {
/**
* 用于更新 [value] 的序列化器. 在反序列化时不会创建新的 [T] 对象实例.
*
* - 序列化: `val text: String = Yaml.default.encodeToString(serializer, Unit)`
* - 反序列化 (本质上是更新 [value], 不创建新的 [T] 实例): `Yaml.default.decodeFromString(serializer, text)`
*/
public val serializer: KSerializer<Unit>
public companion object {
/**
* 使用 [指定格式格式][format] 序列化一个 [SerializerAwareValue]
*/
@JvmStatic
public fun <T> SerializerAwareValue<T>.serialize(format: StringFormat): String {
return format.encodeToString(this.serializer, Unit)
}
/**
* 使用 [指定格式格式][format] 序列化一个 [SerializerAwareValue]
*/
@JvmStatic
public fun <T> SerializerAwareValue<T>.serialize(format: BinaryFormat): ByteArray {
return format.encodeToByteArray(this.serializer, Unit)
}
/**
* 使用 [指定格式格式][format] 反序列化 (更新) 一个 [SerializerAwareValue]
*/
@JvmStatic
public fun <T> SerializerAwareValue<T>.deserialize(format: StringFormat, string: String) {
format.decodeFromString(this.serializer, string)
}
/**
* 使用 [指定格式格式][format] 反序列化 (更新) 一个 [SerializerAwareValue]
*/
@JvmStatic
public fun <T> SerializerAwareValue<T>.deserialize(format: BinaryFormat, bytes: ByteArray) {
format.decodeFromByteArray(this.serializer, bytes)
}
}
}
/**
* 基础数据类型 [Value]
*
* 9 个被认为是 *基础类型* 的类型:
* - 整数: [Byte], [Short], [Int], [Long]
* - 浮点: [Float], [Double]
* - [Boolean]
* - [Char], [String]
*
* 注意: 目前这些类型都会被装箱, 由于泛型 T. 在将来可能会有优化处理.
* *Primitive* 仅表示一个类型是上面 9 种类型之一.
*/
@ConsoleExperimentalApi
public interface PrimitiveValue<T> : Value<T>
//// region PrimitiveValues CODEGEN ////
/**
* 表示一个不可空 [Byte] [Value].
*/
@ConsoleExperimentalApi
public interface ByteValue : PrimitiveValue<Byte>
/**
* 表示一个不可空 [Short] [Value].
*/
@ConsoleExperimentalApi
public interface ShortValue : PrimitiveValue<Short>
/**
* 表示一个不可空 [Int] [Value].
*/
@ConsoleExperimentalApi
public interface IntValue : PrimitiveValue<Int>
/**
* 表示一个不可空 [Long] [Value].
*/
@ConsoleExperimentalApi
public interface LongValue : PrimitiveValue<Long>
/**
* 表示一个不可空 [Float] [Value].
*/
@ConsoleExperimentalApi
public interface FloatValue : PrimitiveValue<Float>
/**
* 表示一个不可空 [Double] [Value].
*/
@ConsoleExperimentalApi
public interface DoubleValue : PrimitiveValue<Double>
/**
* 表示一个不可空 [Char] [Value].
*/
@ConsoleExperimentalApi
public interface CharValue : PrimitiveValue<Char>
/**
* 表示一个不可空 [Boolean] [Value].
*/
@ConsoleExperimentalApi
public interface BooleanValue : PrimitiveValue<Boolean>
/**
* 表示一个不可空 [String] [Value].
*/
@ConsoleExperimentalApi
public interface StringValue : PrimitiveValue<String>
//// endregion PrimitiveValues CODEGEN ////
/**
* 复合数据类型实现
*/
@ConsoleExperimentalApi
public interface CompositeValue<T> : Value<T>
/**
* @see [CompositeListValue]
* @see [PrimitiveListValue]
*/
public interface ListValue<E> : CompositeValue<List<E>>
/**
* 复合数据类型的 [List]
*
* @param E 不是基础数据类型
*/
@ConsoleExperimentalApi
public interface CompositeListValue<E> : ListValue<E>
/**
* 针对基础类型优化的 [List]
*
* @param E 是基础类型
*/
@ConsoleExperimentalApi
public interface PrimitiveListValue<E> : ListValue<E>
//// region PrimitiveListValue CODEGEN ////
@ConsoleExperimentalApi
public interface PrimitiveIntListValue : PrimitiveListValue<Int>
@ConsoleExperimentalApi
public interface PrimitiveLongListValue : PrimitiveListValue<Long>
// TODO + codegen
//// endregion PrimitiveListValue CODEGEN ////
/**
* @see [CompositeSetValue]
* @see [PrimitiveSetValue]
*/
@ConsoleExperimentalApi
public interface SetValue<E> : CompositeValue<Set<E>>
/**
* 复合数据类型 [Set]
* @param E 是基础数据类型
*/
@ConsoleExperimentalApi
public interface CompositeSetValue<E> : SetValue<E>
/**
* 基础数据类型 [Set]
* @param E 是基础数据类型
*/
@ConsoleExperimentalApi
public interface PrimitiveSetValue<E> : SetValue<E>
//// region PrimitiveSetValue CODEGEN ////
@ConsoleExperimentalApi
public interface PrimitiveIntSetValue : PrimitiveSetValue<Int>
@ConsoleExperimentalApi
public interface PrimitiveLongSetValue : PrimitiveSetValue<Long>
// TODO + codegen
//// endregion PrimitiveSetValue CODEGEN ////
/**
* @see [CompositeMapValue]
* @see [PrimitiveMapValue]
*/
@ConsoleExperimentalApi
public interface MapValue<K, V> : CompositeValue<Map<K, V>>
@ConsoleExperimentalApi
public interface CompositeMapValue<K, V> : MapValue<K, V>
@ConsoleExperimentalApi
public interface PrimitiveMapValue<K, V> : MapValue<K, V>
//// region PrimitiveMapValue CODEGEN ////
@ConsoleExperimentalApi
public interface PrimitiveIntIntMapValue : PrimitiveMapValue<Int, Int>
@ConsoleExperimentalApi
public interface PrimitiveIntLongMapValue : PrimitiveMapValue<Int, Long>
// TODO + codegen
//// endregion PrimitiveSetValue CODEGEN ////
@ConsoleExperimentalApi
public interface ReferenceValue<T> : Value<T>

View File

@ -0,0 +1,45 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.data
import kotlinx.serialization.SerialInfo
/**
* 序列化之后的注释.
*
* :
* ```
* object AccountPluginData : PluginData by ... {
* @ValueDescription("""
* 一个 map
* """)
* val map: Map<String, String> by value("a" to "b")
* }
* ```
*
* 将被保存为配置 (YAML 作为示例):
* ```yaml
* AccountPluginData:
* # 一个 map
* map:
* a: b
* ```
*
* @see net.mamoe.yamlkt.Comment
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
public annotation class ValueDescription(
/**
* 将会被 [String.trimIndent] 处理
*/
val value: String,
)

View File

@ -0,0 +1,35 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.data
/**
* 序列化之后的名称.
*
* :
* ```
* object AccountPluginData : AutoSavePluginData() {
* @ValueName("info")
* val map: Map<String, String> by value("a" to "b")
* }
* ```
*
* 将被保存为配置 (YAML 作为示例):
* ```yaml
* AccountPluginData:
* info:
* a: b
* ```
*
* @see PluginData
* @see Value
*/
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
public annotation class ValueName(val value: String)

View File

@ -0,0 +1,41 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused", "EXPOSED_SUPER_CLASS")
package net.mamoe.mirai.console.data.java
import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.PluginConfig
import net.mamoe.mirai.console.data.PluginData
/**
* 一个插件的配置数据, 用于和用户交互.
*
* 用户可通过 mirai-console 前端 (如在 Android 中可视化实现) 修改这些配置, 修改会自动写入这个对象中.
*
* **提示**:
* 插件内部的数据应用 [PluginData] 存储, 而不能使用 [PluginConfig].
*
* ### 实现
*
* [JAutoSavePluginData] 的示例基础上, 修改类定义
* ```java
* // 原
* public class AccountPluginData extends JAutoSavePluginData {
* // 修改为
* public class AccountPluginConfig extends JAutoSavePluginConfig {
* ```
* 即可将一个 [PluginData] 变更为 [PluginConfig].
*
* @see JAutoSavePluginData
* @see PluginConfig
*/
public abstract class JAutoSavePluginConfig public constructor(saveName: String) : AutoSavePluginConfig(saveName),
PluginConfig

View File

@ -0,0 +1,171 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused", "EXPOSED_SUPER_CLASS")
package net.mamoe.mirai.console.data.java
import net.mamoe.mirai.console.data.*
import net.mamoe.mirai.console.internal.data.cast
import net.mamoe.mirai.console.internal.data.setValueBySerializer
import net.mamoe.mirai.console.internal.data.valueImpl
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.reflect.full.createType
/**
* Java 用户使用的 [PluginData]. 参考 [PluginData] 以获取更多信息.
*
* [JvmPlugin] 的典型实现方式:
* ```
* // PluginMain.java
* public final class PluginMain extends JavaPlugin {
* public static PluginMain INSTANCE;
* public PluginMain() {
* INSTANCE = this;
* }
* @Override
* public onLoad() {
* this.reloadPluginData(MyPluginData.INSTANCE); // 读取文件等
* }
* }
*
* // MyPluginData.java
* public class MyPluginData extends JAutoSavePluginData {
* public static final MyPluginData INSTANCE = new MyPluginData();
*
* public final Value<String> string = value("test"); // 默认值 "test"
*
* public final Value<List<String>> list = typedValue(createKType(List.class, createKType(String.class))); // 无默认值, 自动创建空 List
*
* public final Value<Map<Long, Object>> custom = typedValue(
* createKType(Map.class, createKType(Long.class), createKType(Object.class)),
* new HashMap<Long, Object>() {{ // 带默认值
* put(123L, "ok");
* }}
* );
* }
* ```
*
* 使用时, 需要使用 `.get()`, :
* ```
* Value<List<String>> theList = MyPluginData.INSTANCE.list; // 获取 Value 实例. Value 代表一个追踪自动保存的值.
* List<String> actualList = theList.get();
* theList.set();
* ```
*
* **注意**: 由于实现特殊, 请不要在初始化 Value 时就使用 `.get()`. 这可能会导致自动保存追踪失效. 必须在使用时才调用 `.get()` 获取真实数据对象.
*
* @see PluginData
*/
public abstract class JAutoSavePluginData public constructor(saveName: String) : AutoSavePluginData(saveName),
PluginConfig {
//// region JPluginData_value_primitives CODEGEN ////
/**
* 创建一个 [Byte] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Byte): SerializerAwareValue<Byte> = valueImpl(default)
/**
* 创建一个 [Short] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Short): SerializerAwareValue<Short> = valueImpl(default)
/**
* 创建一个 [Int] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Int): SerializerAwareValue<Int> = valueImpl(default)
/**
* 创建一个 [Long] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Long): SerializerAwareValue<Long> = valueImpl(default)
/**
* 创建一个 [Float] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Float): SerializerAwareValue<Float> = valueImpl(default)
/**
* 创建一个 [Double] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Double): SerializerAwareValue<Double> = valueImpl(default)
/**
* 创建一个 [Char] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Char): SerializerAwareValue<Char> = valueImpl(default)
/**
* 创建一个 [Boolean] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: Boolean): SerializerAwareValue<Boolean> = valueImpl(default)
/**
* 创建一个 [String] 类型的 [Value], 并设置初始值为 [default]
*/
public fun value(default: String): SerializerAwareValue<String> = valueImpl(default)
//// endregion JPluginData_value_primitives CODEGEN ////
/**
* 构造一个支持泛型的 [Value].
*
* 对于 [Map], [Set], [List], [ConcurrentMap] 等标准库类型, 这个函数会尝试构造 [LinkedHashMap], [LinkedHashSet], [ArrayList], [ConcurrentHashMap] 等相关类型.
* 而对于自定义数据类型, 本函数只会反射获取 [objectInstance][KClass.objectInstance] 或使用*无参构造器*构造实例.
*
* @param type Kotlin 类型. 可通过 [createKType] 获得
*
* @param T 类型 T. 仅支持:
* - 基础数据类型, [String]
* - 标准库集合类型 ([List], [Map], [Set], [ConcurrentMap])
* - 标准库数据类型 ([Map.Entry], [Pair], [Triple])
* - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的 **Kotlin** 类
*/
@JvmOverloads
public fun <T : Any> typedValue(type: KType, default: T? = null): SerializerAwareValue<T> {
val value = valueImpl<T>(type, type.classifier!!.cast())
if (default != null) value.setValueBySerializer(default)
return value
}
public companion object {
/**
* 根据 [Class] 及泛型参数获得一个类型
*
* 如要获得一个 `Map<String, Long>` 的类型,
* ```java
* KType type = JPluginDataHelper.createKType(Map.java, createKType(String.java), createKType(Long.java))
* ```
*
* @param genericArguments 带有顺序的泛型参数
*/
@JvmStatic
public fun <T : Any> createKType(clazz: Class<T>, nullable: Boolean, vararg genericArguments: KType): KType {
return clazz.kotlin.createType(genericArguments.map { KTypeProjection(KVariance.INVARIANT, it) }, nullable)
}
/**
* 根据 [Class] 及泛型参数获得一个不可为 `null` 的类型
*
* @see createKType
*/
@JvmStatic
public fun <T : Any> createKType(clazz: Class<T>, vararg genericArguments: KType): KType {
return createKType(clazz, false, *genericArguments)
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.events
/*
data class CommandExecutionEvent( // TODO: 2020/6/26 impl CommandExecutionEvent
val sender: CommandSender,
val command: Command,
val rawArgs: Array<Any>
) : CancellableEvent, ConsoleEvent, AbstractEvent() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CommandExecutionEvent
if (sender != other.sender) return false
if (command != other.command) return false
if (!rawArgs.contentEquals(other.rawArgs)) return false
return true
}
override fun hashCode(): Int {
var result = sender.hashCode()
result = 31 * result + command.hashCode()
result = 31 * result + rawArgs.contentHashCode()
return result
}
}
*/

View File

@ -0,0 +1,17 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.events
import net.mamoe.mirai.event.Event
/**
* 表示来自 mirai-console 的事件
*/
public interface ConsoleEvent : Event

View File

@ -0,0 +1,35 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.extension
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad
/**
* 组件容器, 容纳 [Plugin] 注册的 [Extension].
*
* @see Extension
* @see JvmPlugin.onLoad
*/
public interface ComponentStorage {
public fun <T : Extension> contribute(
extensionPoint: ExtensionPoint<T>,
plugin: Plugin,
extensionInstance: T,
)
public fun <T : Extension> contribute(
extensionPoint: ExtensionPoint<T>,
plugin: Plugin,
lazyInstance: () -> T,
)
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.extension
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector.ExtensionPoint.selectSingleton
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad
/**
* 表示一个扩展.
*
* ### 获取扩展
* Console 不允许插件获取自己或其他插件注册的扩展
*
* ### 注册扩展
* 插件仅能在 [JvmPlugin.onLoad] 阶段注册扩展
*
* ```kotlin
* object MyPlugin : KotlinPlugin( /* ... */ ) {
* fun PluginComponentStorage.onLoad() {
* contributePermissionService { /* ... */ }
* contributePluginLoader { /* ... */ }
* contribute(ExtensionPoint) { /* ... */ }
* }
* }
* ```
*
* @see ComponentStorage
*/
public interface Extension
/**
* 增加一些函数 (方法)的扩展
*/
public interface FunctionExtension : Extension
/**
* 为某单例服务注册的 [Extension].
*
* 若同时有多个实例可用, 将会使用 [SingletonExtensionSelector.selectSingleton] 选择
*
* @see PermissionServiceProvider
*/
public interface SingletonExtension<T> : Extension {
public val instance: T
}
/**
* 为一些实例注册的 [Extension].
*
* @see PluginLoaderProvider
*/
public interface InstanceExtension<T> : Extension {
public val instance: T
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.extension
/**
* 在调用一个 extension 时遇到的异常.
*/
public open class ExtensionException : RuntimeException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.extension
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.reflect.KClass
/**
* [Extension] 的伴生对象实现.
*
* @see AbstractExtensionPoint
*/
public interface ExtensionPoint<T : Extension> {
/**
* 扩展实例 [T] 的类型
*/
public val extensionType: KClass<T>
}
public abstract class AbstractExtensionPoint<T : Extension>(
public override val extensionType: KClass<T>,
) : ExtensionPoint<T>
/**
* 表示一个 [SingletonExtension] [ExtensionPoint]
*/
public interface SingletonExtensionPoint<T : SingletonExtension<*>> : ExtensionPoint<T>
/**
* 表示一个 [InstanceExtension] [ExtensionPoint]
*/
public interface InstanceExtensionPoint<T : InstanceExtension<*>> : ExtensionPoint<T>
/**
* 表示一个 [FunctionExtension] [ExtensionPoint]
*/
public interface FunctionExtensionPoint<T : FunctionExtension> : ExtensionPoint<T>
public abstract class AbstractInstanceExtensionPoint<E : InstanceExtension<T>, T>
@ConsoleExperimentalApi constructor(
extensionType: KClass<E>,
/**
* 内建的实现列表.
*/
@ConsoleExperimentalApi
public vararg val builtinImplementations: E,
) : AbstractExtensionPoint<E>(extensionType)
public abstract class AbstractSingletonExtensionPoint<E : SingletonExtension<T>, T>
@ConsoleExperimentalApi constructor(
extensionType: KClass<E>,
/**
* 内建的实现.
*/
@ConsoleExperimentalApi
public val builtinImplementation: T,
) : AbstractExtensionPoint<E>(extensionType), SingletonExtensionPoint<E> {
/**
* [SingletonExtensionSelector] 选择后的实例.
*/
@ConsoleExperimentalApi
public open val selectedInstance: T by lazy {
GlobalComponentStorage.run {
this@AbstractSingletonExtensionPoint.findSingletonInstance(
extensionType,
builtinImplementation
)
}
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.extension
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCallParser
import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
import net.mamoe.mirai.console.extensions.*
import net.mamoe.mirai.console.internal.extension.AbstractConcurrentComponentStorage
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import kotlin.reflect.full.companionObjectInstance
/**
* 添加一些扩展给 [Plugin] [ComponentStorage].
*
* 所有扩展都会以 'lazy' 形式注册, Console 在不同的启动阶段分别初始化各类扩展.
*/
@Suppress("EXPOSED_SUPER_CLASS", "unused", "MemberVisibilityCanBePrivate")
public class PluginComponentStorage(
@JvmField
internal val plugin: Plugin,
) : AbstractConcurrentComponentStorage() {
/**
* 注册一个扩展
*/
public fun <E : Extension> contribute(
extensionPoint: ExtensionPoint<E>,
lazyInstance: () -> E,
): Unit = contribute(extensionPoint, plugin, lazyInstance)
/**
* 注册一个扩展. [E] 必须拥有伴生对象为 [ExtensionPoint].
*/
public inline fun <reified E : Extension> contribute(
noinline lazyInstance: () -> E,
) {
@Suppress("UNCHECKED_CAST")
(contribute(
(E::class.companionObjectInstance as? ExtensionPoint<E>
?: error("Companion object of ${E::class.qualifiedName} is not an ExtensionPoint")),
lazyInstance
))
}
///////////////////////////////////////////////////////////////////////////
// FunctionExtension
///////////////////////////////////////////////////////////////////////////
/** 注册一个 [SingletonExtensionSelector] */
public fun contributeSingletonExtensionSelector(lazyInstance: () -> SingletonExtensionSelector): Unit =
contribute(SingletonExtensionSelector, plugin, lazyInstance)
@Suppress("SpellCheckingInspection") // alterer
/** 注册一个 [BotConfigurationAlterer] */
public fun contributeBotConfigurationAlterer(instance: BotConfigurationAlterer): Unit =
contribute(BotConfigurationAlterer, plugin, lazyInstance = { instance })
/** 注册一个 [PostStartupExtension] */
public fun contributePostStartupExtension(instance: PostStartupExtension): Unit =
contribute(PostStartupExtension, plugin, lazyInstance = { instance })
/** 注册一个 [PostStartupExtension] */
public fun runAfterStartup(block: () -> Unit): Unit = contributePostStartupExtension(block)
///////////////////////////////////////////////////////////////////////////
// InstanceExtensions & SingletonExtensions
///////////////////////////////////////////////////////////////////////////
/** 注册一个 [PermissionServiceProvider] */
@OverloadResolutionByLambdaReturnType
public fun contributePermissionService(lazyInstance: () -> PermissionService<*>): Unit =
contribute(PermissionServiceProvider, plugin, PermissionServiceProviderImplLazy(lazyInstance))
/** 注册一个 [PermissionServiceProvider] */
@JvmName("contributePermissionServiceProvider")
@OverloadResolutionByLambdaReturnType
public fun contributePermissionService(lazyProvider: () -> PermissionServiceProvider): Unit =
contribute(PermissionServiceProvider, plugin, lazyProvider)
/////////////////////////////////////
/** 注册一个 [PluginLoaderProvider] */
@OverloadResolutionByLambdaReturnType
public fun contributePluginLoader(lazyInstance: () -> PluginLoader<*, *>): Unit =
contribute(PluginLoaderProvider, plugin, PluginLoaderProviderImplLazy(lazyInstance))
/** 注册一个 [PluginLoaderProvider] */
@JvmName("contributePluginLoaderProvider")
@OverloadResolutionByLambdaReturnType
public fun contributePluginLoader(lazyProvider: () -> PluginLoaderProvider): Unit =
contribute(PluginLoaderProvider, plugin, lazyProvider) // lazy for safety
/////////////////////////////////////
/** 注册一个 [CommandCallParserProvider] */
@ExperimentalCommandDescriptors
@OverloadResolutionByLambdaReturnType
public fun contributeCommandCallParser(lazyInstance: () -> CommandCallParser): Unit =
contribute(CommandCallParserProvider, plugin, CommandCallParserProviderImplLazy(lazyInstance))
/** 注册一个 [CommandCallParserProvider] */
@ExperimentalCommandDescriptors
@JvmName("contributeCommandCallParserProvider")
@OverloadResolutionByLambdaReturnType
public fun contributeCommandCallParser(provider: CommandCallParserProvider): Unit =
contribute(CommandCallParserProvider, plugin, provider)
/////////////////////////////////////
/** 注册一个 [CommandCallResolverProvider] */
@ExperimentalCommandDescriptors
@OverloadResolutionByLambdaReturnType
public fun contributeCommandCallResolver(lazyInstance: () -> CommandCallResolver): Unit =
contribute(CommandCallResolverProvider, plugin, CommandCallResolverProviderImplLazy(lazyInstance))
/** 注册一个 [CommandCallResolverProvider] */
@ExperimentalCommandDescriptors
@JvmName("contributeCommandCallResolverProvider")
@OverloadResolutionByLambdaReturnType
public fun contributeCommandCallParser(provider: CommandCallResolverProvider): Unit =
contribute(CommandCallResolverProvider, plugin, provider)
/////////////////////////////////////
/** 注册一个 [CommandCallInterceptorProvider] */
@ExperimentalCommandDescriptors
@OverloadResolutionByLambdaReturnType
public fun contributeCommandCallInterceptor(lazyInstance: () -> CommandCallInterceptor): Unit =
contribute(CommandCallInterceptorProvider, plugin, CommandCallInterceptorProviderImplLazy(lazyInstance))
/** 注册一个 [CommandCallInterceptorProvider] */
@ExperimentalCommandDescriptors
@JvmName("contributeCommandCallInterceptorProvider")
@OverloadResolutionByLambdaReturnType
public fun contributeCommandCallParser(provider: CommandCallInterceptorProvider): Unit =
contribute(CommandCallInterceptorProvider, plugin, provider)
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
import net.mamoe.mirai.console.extension.FunctionExtension
import net.mamoe.mirai.utils.BotConfiguration
/**
* [MiraiConsole.addBot] 时的 [BotConfiguration] 修改扩展
*
* @see MiraiConsole.addBot
*/
@Suppress("SpellCheckingInspection") // alterer
public fun interface BotConfigurationAlterer : FunctionExtension {
/**
* 修改 [configuration], 返回修改完成的 [BotConfiguration]
*/
public fun alterConfiguration(
botId: Long,
configuration: BotConfiguration,
): BotConfiguration
public companion object ExtensionPoint :
AbstractExtensionPoint<BotConfigurationAlterer>(BotConfigurationAlterer::class)
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor
import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint
import net.mamoe.mirai.console.extension.InstanceExtension
@ExperimentalCommandDescriptors
public interface CommandCallInterceptorProvider : InstanceExtension<CommandCallInterceptor> {
public companion object ExtensionPoint :
AbstractInstanceExtensionPoint<CommandCallInterceptorProvider, CommandCallInterceptor>(
CommandCallInterceptorProvider::class
)
}
@ExperimentalCommandDescriptors
public class CommandCallInterceptorProviderImpl(override val instance: CommandCallInterceptor) :
CommandCallInterceptorProvider
@ExperimentalCommandDescriptors
public class CommandCallInterceptorProviderImplLazy(initializer: () -> CommandCallInterceptor) :
CommandCallInterceptorProvider {
override val instance: CommandCallInterceptor by lazy(initializer)
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCallParser
import net.mamoe.mirai.console.command.parse.SpaceSeparatedCommandCallParser
import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint
import net.mamoe.mirai.console.extension.InstanceExtension
/**
* The provider of [CommandCallParser]
*/
@ExperimentalCommandDescriptors
public interface CommandCallParserProvider : InstanceExtension<CommandCallParser> {
public companion object ExtensionPoint :
AbstractInstanceExtensionPoint<CommandCallParserProvider, CommandCallParser>(
CommandCallParserProvider::class,
SpaceSeparatedCommandCallParser.Provider
)
}
@ExperimentalCommandDescriptors
public class CommandCallParserProviderImpl(override val instance: CommandCallParser) : CommandCallParserProvider
@ExperimentalCommandDescriptors
public class CommandCallParserProviderImplLazy(initializer: () -> CommandCallParser) : CommandCallParserProvider {
override val instance: CommandCallParser by lazy(initializer)
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.resolve.BuiltInCommandCallResolver
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint
import net.mamoe.mirai.console.extension.SingletonExtension
@ExperimentalCommandDescriptors
public interface CommandCallResolverProvider : SingletonExtension<CommandCallResolver> {
public companion object ExtensionPoint :
AbstractSingletonExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(
CommandCallResolverProvider::class,
BuiltInCommandCallResolver
)
}
@ExperimentalCommandDescriptors
public class CommandCallResolverProviderImpl(override val instance: CommandCallResolver) : CommandCallResolverProvider
@ExperimentalCommandDescriptors
public class CommandCallResolverProviderImplLazy(initializer: () -> CommandCallResolver) : CommandCallResolverProvider {
override val instance: CommandCallResolver by lazy(initializer)
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint
import net.mamoe.mirai.console.extension.SingletonExtension
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/**
* [权限服务][PermissionService] 提供器.
*
* 当插件注册 [PermissionService] , 默认会使用插件的 [PermissionService].
*/
public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> {
public companion object ExtensionPoint :
AbstractSingletonExtensionPoint<PermissionServiceProvider, PermissionService<*>>(
PermissionServiceProvider::class,
BuiltInPermissionService
) {
internal var permissionServiceOk = false
@ConsoleExperimentalApi
public val providerPlugin: Plugin? by lazy {
GlobalComponentStorage.run {
val instance = PermissionService.INSTANCE
if (instance is BuiltInPermissionService) return@lazy null
PermissionServiceProvider.getExtensions().find { it.extension.instance === instance }?.plugin
}
}
@ConsoleExperimentalApi
override val selectedInstance: PermissionService<*>
get() {
if (!permissionServiceOk) {
error("PermissionService not yet loaded")
}
return super.selectedInstance
}
}
}
/**
* @see PermissionServiceProvider
*/
public class PermissionServiceProviderImpl(override val instance: PermissionService<*>) : PermissionServiceProvider
/**
* @see PermissionServiceProvider
*/
public class PermissionServiceProviderImplLazy(initializer: () -> PermissionService<*>) : PermissionServiceProvider {
override val instance: PermissionService<*> by lazy(initializer)
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
import net.mamoe.mirai.console.extension.Extension
import net.mamoe.mirai.console.extension.InstanceExtension
import net.mamoe.mirai.console.extension.PluginComponentStorage
import net.mamoe.mirai.console.plugin.loader.PluginLoader
/**
* 提供扩展 [PluginLoader]
*
* @see PluginComponentStorage.contributePluginLoader
*
*
* @see Extension
* @see PluginLoader
*
* @see PluginLoaderProviderImplLazy
*/
public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> {
public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class)
}
public class PluginLoaderProviderImpl(override val instance: PluginLoader<*, *>) : PluginLoaderProvider
public class PluginLoaderProviderImplLazy(initializer: () -> PluginLoader<*, *>) : PluginLoaderProvider {
override val instance: PluginLoader<*, *> by lazy(initializer)
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
import net.mamoe.mirai.console.extension.ExtensionException
import net.mamoe.mirai.console.extension.FunctionExtension
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
/**
* Console 启动完成后立即在主线程调用的扩展. 用于进行一些必要的延迟初始化.
*
* 这些扩展只会, 且一定会被调用正好一次.
*/
public fun interface PostStartupExtension : FunctionExtension {
/**
* 将在 Console 主线程执行.
*
* #### 内部实现细节
* [MiraiConsoleImplementationBridge.doStart] 所有 [MiraiConsoleImplementationBridge.phase] 执行完成后顺序调用.
*
* @throws Exception 所有抛出的 [Exception] 都会被捕获并包装为 [ExtensionException] 抛出, 并停止 [MiraiConsole]
*/
@Throws(Exception::class)
public operator fun invoke()
public companion object ExtensionPoint : AbstractExtensionPoint<PostStartupExtension>(PostStartupExtension::class)
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
import net.mamoe.mirai.console.extension.Extension
import net.mamoe.mirai.console.extension.FunctionExtension
import net.mamoe.mirai.console.extension.SingletonExtension
import net.mamoe.mirai.console.internal.extension.BuiltInSingletonExtensionSelector
import net.mamoe.mirai.console.internal.extension.ExtensionRegistry
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.utils.info
import kotlin.reflect.KClass
/**
* 用于同时拥有多个 [SingletonExtension] 时选择一个实例.
*
* 如有多个 [SingletonExtensionSelector] 注册, 将会停止服务器.
*/
public interface SingletonExtensionSelector : FunctionExtension {
/**
* 表示一个插件注册的 [Extension]
*/
public data class Registry<T : Extension>(
val plugin: Plugin?,
val extension: T,
)
/**
* @return `null` 表示使用 Console 内置的 [SingletonExtensionSelector]
*/
public fun <T : Extension> selectSingleton(
extensionType: KClass<T>,
candidates: Collection<Registry<T>>,
): T?
public companion object ExtensionPoint :
AbstractExtensionPoint<SingletonExtensionSelector>(SingletonExtensionSelector::class) {
private var instanceField: SingletonExtensionSelector? = null
internal val instance: SingletonExtensionSelector get() = instanceField ?: error("")
internal fun init() {
check(instanceField == null) { "Internal error: reinitialize SingletonExtensionSelector" }
val instances = GlobalComponentStorage.run { SingletonExtensionSelector.getExtensions() }
instanceField = when {
instances.isEmpty() -> BuiltInSingletonExtensionSelector
instances.size == 1 -> {
instances.single().also { (plugin, ext) ->
MiraiConsole.mainLogger.info { "Loaded SingletonExtensionSelector: $ext from ${plugin?.name ?: "<builtin>"}" }
}.extension
}
else -> {
error("Found too many SingletonExtensionSelectors: ${instances.joinToString { (p, i) -> "'$i' from '${p?.name ?: "<builtin>"}'" }}. Check your plugins and ensure there is only one external SingletonExtensionSelectors")
}
}
}
internal fun <T : Extension> selectSingleton(
extensionType: KClass<T>,
candidates: Collection<ExtensionRegistry<T>>,
): T? =
instance.selectSingleton(extensionType, candidates.map { Registry(it.plugin, it.extension) })
internal fun <T : Extension> SingletonExtensionSelector.selectSingleton(
extensionType: KClass<T>,
candidates: Collection<ExtensionRegistry<T>>,
): T? = selectSingleton(extensionType, candidates.map { Registry(it.plugin, it.extension) })
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.internal
import net.mamoe.mirai.console.util.SemVersion
import java.time.Instant
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
@JvmStatic
val buildDate: Instant = Instant.ofEpochSecond(1628666895)
const val versionConst: String = "2.7.0"
@JvmStatic
val version: SemVersion = SemVersion(versionConst)
}

View File

@ -0,0 +1,312 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:OptIn(ConsoleExperimentalApi::class)
package net.mamoe.mirai.console.internal
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MalformedMiraiConsoleImplementationError
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
import net.mamoe.mirai.console.MiraiConsoleImplementation
import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
import net.mamoe.mirai.console.extensions.PostStartupExtension
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
import net.mamoe.mirai.console.internal.command.CommandConfig
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.ConfigurationKey
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.MD5
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope
import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig
import net.mamoe.mirai.console.internal.extension.BuiltInSingletonExtensionSelector
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.internal.util.autoHexToBytes
import net.mamoe.mirai.console.internal.util.runIgnoreException
import net.mamoe.mirai.console.logging.LoggerController
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
import net.mamoe.mirai.console.permission.RootPermission
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.*
import java.nio.file.Path
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
/**
* [MiraiConsole] 公开 API 与前端实现的连接桥.
*/
@Suppress("SpellCheckingInspection")
internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleImplementation,
MiraiConsole {
override val pluginCenter: PluginCenter get() = throw UnsupportedOperationException("PluginCenter is not supported yet")
private val instance: MiraiConsoleImplementation get() = MiraiConsoleImplementation.getInstance()
override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate
override val version: SemVersion by MiraiConsoleBuildConstants::version
override val rootPath: Path by instance::rootPath
override val frontEndDescription: MiraiConsoleFrontEndDescription by instance::frontEndDescription
override val mainLogger: MiraiLogger by lazy {
createLogger("main")
}
override val coroutineContext: CoroutineContext by instance::coroutineContext
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> by instance::builtInPluginLoaders
override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl by instance::consoleCommandSender
override val dataStorageForJvmPluginLoader: PluginDataStorage by instance::dataStorageForJvmPluginLoader
override val configStorageForJvmPluginLoader: PluginDataStorage by instance::configStorageForJvmPluginLoader
override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns
override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns
override val consoleInput: ConsoleInput by instance::consoleInput
override val isAnsiSupported: Boolean by instance::isAnsiSupported
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver =
instance.createLoginSolver(requesterBot, configuration)
override val loggerController: LoggerController by instance::loggerController
init {
// TODO: Replace to standard api
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
DefaultFactoryOverrides.override { requester, identity ->
return@override createLogger(
identity ?: requester.kotlin.simpleName ?: requester.simpleName
)
}
}
override fun createLogger(identity: String?): MiraiLogger {
val controller = loggerController
return MiraiConsoleLogger(controller, instance.createLogger(identity))
}
@Suppress("RemoveRedundantBackticks")
internal fun doStart() {
instance.preStart()
phase("setup logger controller") {
if (loggerController === LoggerControllerImpl) {
// Reload LoggerConfig.
ConsoleDataScope.addAndReloadConfig(LoggerConfig)
LoggerControllerImpl.initialized = true
}
}
phase("greeting") {
val buildDateFormatted =
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
mainLogger.info { "Starting mirai-console..." }
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
mainLogger.info { frontEndDescription.render() }
mainLogger.info { "Welcome to visit https://mirai.mamoe.net/" }
}
phase("check coroutineContext") {
if (coroutineContext[Job] == null) {
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
}
if (coroutineContext[CoroutineExceptionHandler] == null) {
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a CoroutineExceptionHandler in it.")
}
MiraiConsole.job.invokeOnCompletion {
Bot.instances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
}
}
ConsoleInput
// start
phase("load configurations") {
mainLogger.verbose { "Loading configurations..." }
ConsoleDataScope.addAndReloadConfig(CommandConfig)
ConsoleDataScope.reloadAll()
}
phase("initialize all plugins") {
PluginManager // init
mainLogger.verbose { "Loading JVM plugins..." }
PluginManagerImpl.loadAllPluginsUsingBuiltInLoaders()
PluginManagerImpl.initExternalPluginLoaders().let { count ->
mainLogger.verbose { "$count external PluginLoader(s) found. " }
if (count != 0) {
mainLogger.verbose { "Loading external plugins..." }
}
}
}
phase("load all plugins") {
PluginManagerImpl.loadPlugins(PluginManagerImpl.scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider())
mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." }
}
phase("load SingletonExtensionSelector") {
SingletonExtensionSelector.init()
val instance = SingletonExtensionSelector.instance
if (instance is BuiltInSingletonExtensionSelector) {
ConsoleDataScope.addAndReloadConfig(instance.config)
}
}
phase("load PermissionService") {
mainLogger.verbose { "Loading PermissionService..." }
PermissionServiceProvider.permissionServiceOk = true
PermissionService.INSTANCE.let { ps ->
if (ps is BuiltInPermissionService) {
ConsoleDataScope.addAndReloadConfig(ps.config)
mainLogger.verbose { "Reloaded PermissionService settings." }
} else {
mainLogger.info { "Loaded PermissionService from plugin ${PermissionServiceProvider.providerPlugin?.name}" }
}
}
runIgnoreException<UnsupportedOperationException> { ConsoleCommandSender.permit(RootPermission) }
}
phase("prepare commands") {
mainLogger.verbose { "Loading built-in commands..." }
BuiltInCommands.registerAll()
mainLogger.info { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
CommandManager
// CommandManagerImpl.commandListener // start
}
phase("enable plugins") {
mainLogger.verbose { "Enabling plugins..." }
PluginManagerImpl.enableAllLoadedPlugins()
for (registeredCommand in CommandManager.allRegisteredCommands) {
registeredCommand.permission // init
}
mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) enabled." }
}
phase("auto-login bots") {
runBlocking {
val accounts = AutoLoginConfig.accounts.toList()
for (account in accounts.filter {
it.configuration[ConfigurationKey.enable]?.toString()?.equals("true", true) ?: true
}) {
val id = kotlin.runCatching {
account.account.toLong()
}.getOrElse {
error("Bad auto-login account: '${account.account}'")
}
if (id == 123456L) continue
fun BotConfiguration.configBot() {
mainLogger.info { "Auto-login ${account.account}" }
account.configuration[ConfigurationKey.protocol]?.let { protocol ->
this.protocol = runCatching {
BotConfiguration.MiraiProtocol.valueOf(protocol.toString())
}.getOrElse {
throw IllegalArgumentException(
"Bad auto-login config value for `protocol` for account $id",
it
)
}
}
account.configuration[ConfigurationKey.device]?.let { device ->
fileBasedDeviceInfo(device.toString())
}
}
val bot = when (account.password.kind) {
PLAIN -> {
MiraiConsole.addBot(id, account.password.value, BotConfiguration::configBot)
}
MD5 -> {
val md5 = kotlin.runCatching {
account.password.value.autoHexToBytes()
}.getOrElse {
error("Bad auto-login md5: '${account.password.value}' for account $id")
}
MiraiConsole.addBot(id, md5, BotConfiguration::configBot)
}
}
runCatching { bot.login() }.getOrElse {
mainLogger.error(it)
bot.close()
}
}
}
}
phase("finally post") {
GlobalComponentStorage.run {
PostStartupExtension.useExtensions { it() } // exceptions thrown will be caught by caller of `doStart`.
}
}
instance.postStart()
mainLogger.info { "mirai-console started successfully." }
}
@Suppress("SpellCheckingInspection")
@Retention(AnnotationRetention.BINARY)
@DslMarker
internal annotation class ILoveOmaeKumikoForever
/**
* 表示一个初始化阶段, 无实际作用.
*/
@ILoveOmaeKumikoForever
private inline fun phase(phase: String, block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
prePhase(phase)
block()
postPhase(phase)
}
override fun prePhase(phase: String) {
instance.prePhase(phase)
}
override fun postPhase(phase: String) {
instance.postPhase(phase)
}
}

View File

@ -0,0 +1,171 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.internal.command
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.Command.Companion.allNames
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.findDuplicate
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall
import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor.Companion.intercepted
import net.mamoe.mirai.console.command.resolve.CommandCallResolver.Companion.resolve
import net.mamoe.mirai.console.command.resolve.getOrElse
import net.mamoe.mirai.console.internal.util.ifNull
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.toMessageChain
import net.mamoe.mirai.utils.MiraiLogger
import java.util.concurrent.locks.ReentrantLock
@OptIn(ExperimentalCommandDescriptors::class)
internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiConsole.childScope("CommandManagerImpl") {
private val logger: MiraiLogger by lazy {
MiraiConsole.createLogger("command")
}
@Suppress("ObjectPropertyName")
@JvmField
internal val _registeredCommands: MutableList<Command> = mutableListOf()
@JvmField
internal val requiredPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
@JvmField
internal val optionalPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
@JvmField
internal val modifyLock = ReentrantLock()
/**
* 从原始的 command 中解析出 Command 对象
*/
override fun matchCommand(commandName: String): Command? {
if (commandName.startsWith(commandPrefix)) {
return requiredPrefixCommandMap[commandName.substringAfter(commandPrefix).lowercase()]
}
return optionalPrefixCommandMap[commandName.lowercase()]
}
///// IMPL
override fun getRegisteredCommands(owner: CommandOwner): List<Command> =
_registeredCommands.filter { it.owner == owner }
override val allRegisteredCommands: List<Command> get() = _registeredCommands.toList() // copy
override val commandPrefix: String get() = CommandConfig.commandPrefix
override fun unregisterAllCommands(owner: CommandOwner) {
for (registeredCommand in getRegisteredCommands(owner)) {
unregisterCommand(registeredCommand)
}
}
override fun registerCommand(command: Command, override: Boolean): Boolean {
if (command is CompositeCommand) {
command.overloads // init lazy
}
kotlin.runCatching {
command.permission // init lazy
command.secondaryNames // init lazy
command.description // init lazy
command.usage // init lazy
}.onFailure {
throw IllegalStateException("Failed to init command ${command}.", it)
}
modifyLock.withLock {
if (!override) {
if (command.findDuplicate() != null) return false
}
_registeredCommands.add(command)
if (command.prefixOptional) {
for (name in command.allNames) {
val lowerCaseName = name.lowercase()
optionalPrefixCommandMap[lowerCaseName] = command
requiredPrefixCommandMap[lowerCaseName] = command
}
} else {
for (name in command.allNames) {
val lowerCaseName = name.lowercase()
optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency
requiredPrefixCommandMap[lowerCaseName] = command
}
}
return true
}
}
override fun findDuplicateCommand(command: Command): Command? =
_registeredCommands.firstOrNull { it.allNames intersectsIgnoringCase command.allNames }
override fun unregisterCommand(command: Command): Boolean = modifyLock.withLock {
if (command.prefixOptional) {
command.allNames.forEach {
optionalPrefixCommandMap.remove(it.lowercase())
}
}
command.allNames.forEach {
requiredPrefixCommandMap.remove(it.lowercase())
}
_registeredCommands.remove(command)
}
override fun isCommandRegistered(command: Command): Boolean = command in _registeredCommands
}
// Don't move into CommandManager, compilation error / VerifyError
@OptIn(ExperimentalCommandDescriptors::class)
internal suspend fun executeCommandImpl(
message0: Message,
caller: CommandSender,
checkPermission: Boolean,
): CommandExecuteResult {
val message = message0
.intercepted(caller)
.getOrElse { return CommandExecuteResult.Intercepted(null, null, null, it) }
val call = message.toMessageChain()
.parseCommandCall(caller)
.ifNull { return CommandExecuteResult.UnresolvedCommand(null) }
.let { raw ->
raw.intercepted()
.getOrElse { return CommandExecuteResult.Intercepted(raw, null, null, it) }
}
val resolved = call
.resolve()
.getOrElse { return it }
.let { raw ->
raw.intercepted()
.getOrElse { return CommandExecuteResult.Intercepted(call, raw, null, it) }
}
val command = resolved.callee
if (checkPermission && !command.permission.testPermission(caller)) {
return CommandExecuteResult.PermissionDenied(command, call, resolved)
}
return try {
resolved.calleeSignature.call(resolved)
CommandExecuteResult.Success(resolved.callee, call, resolved)
} catch (e: CommandArgumentParserException) {
CommandExecuteResult.IllegalArgument(e, resolved.callee, call, resolved)
} catch (e: Throwable) {
CommandExecuteResult.ExecutionFailed(e, resolved.callee, call, resolved)
}
}

View File

@ -0,0 +1,345 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.buildMessageChain
import net.mamoe.mirai.utils.runBIO
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.KVisibility
import kotlin.reflect.full.*
internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray()
internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain {
when (this@flattenCommandComponents) {
is PlainText -> this@flattenCommandComponents.content.splitToSequence(' ').filterNot { it.isBlank() }
.forEach { +PlainText(it) }
is CharSequence -> this@flattenCommandComponents.splitToSequence(' ').filterNot { it.isBlank() }
.forEach { +PlainText(it) }
is SingleMessage -> add(this@flattenCommandComponents)
is Array<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
is Iterable<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
else -> add(this@flattenCommandComponents.toString())
}
}
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
internal object CompositeCommandSubCommandAnnotationResolver :
SubCommandAnnotationResolver {
override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
function.hasAnnotation<CompositeCommand.SubCommand>()
override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> {
val annotated = function.findAnnotation<CompositeCommand.SubCommand>()!!.value
return if (annotated.isEmpty()) arrayOf(function.name)
else annotated
}
override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? =
parameter.findAnnotation<CompositeCommand.Name>()?.value
override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? =
function.findAnnotation<CompositeCommand.Description>()?.value
}
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
internal object SimpleCommandSubCommandAnnotationResolver :
SubCommandAnnotationResolver {
override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
function.hasAnnotation<SimpleCommand.Handler>()
override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> =
emptyArray()
override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? =
parameter.findAnnotation<SimpleCommand.Name>()?.value
override fun getDescription(ownerCommand: Command, function: KFunction<*>): String =
ownerCommand.description
}
internal interface SubCommandAnnotationResolver {
fun hasAnnotation(ownerCommand: Command, function: KFunction<*>): Boolean
fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String>
fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String?
fun getDescription(ownerCommand: Command, function: KFunction<*>): String?
}
@ConsoleExperimentalApi
public class IllegalCommandDeclarationException : Exception {
public override val message: String?
public constructor(
ownerCommand: Command,
correspondingFunction: KFunction<*>,
message: String?,
) : super("Illegal command declaration: ${correspondingFunction.name} declared in ${ownerCommand::class.qualifiedName}") {
this.message = message
}
public constructor(
ownerCommand: Command,
message: String?,
) : super("Illegal command declaration: ${ownerCommand::class.qualifiedName}") {
this.message = message
}
}
@OptIn(ExperimentalCommandDescriptors::class)
internal class CommandReflector(
val command: Command,
val annotationResolver: SubCommandAnnotationResolver,
) {
@Suppress("NOTHING_TO_INLINE")
private inline fun KFunction<*>.illegalDeclaration(
message: String,
): Nothing {
throw IllegalCommandDeclarationException(command, this, message)
}
private fun KFunction<*>.isSubCommandFunction(): Boolean = annotationResolver.hasAnnotation(command, this)
private fun KFunction<*>.checkExtensionReceiver() {
this.extensionReceiverParameter?.let { receiver ->
if (receiver.type.classifierAsKClassOrNull()?.isSubclassOf(CommandSender::class) != true) {
illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender.")
}
}
}
private fun KFunction<*>.checkNames() {
val names = annotationResolver.getSubCommandNames(command, this)
for (name in names) {
ILLEGAL_SUB_NAME_CHARS.find { it in name }?.let {
illegalDeclaration("'$it' is forbidden in command name.")
}
}
}
private fun KFunction<*>.checkModifiers() {
if (isInline) illegalDeclaration("Command function cannot be inline")
if (visibility == KVisibility.PRIVATE) illegalDeclaration("Command function must be accessible from Mirai Console, that is, effectively public.")
if (this.hasAnnotation<JvmStatic>()) illegalDeclaration("Command function must not be static.")
// should we allow abstract?
// if (isAbstract) illegalDeclaration("Command function cannot be abstract")
}
fun generateUsage(overloads: Iterable<CommandSignatureFromKFunction>): String {
return generateUsage(command, annotationResolver, overloads)
}
companion object {
fun generateUsage(
command: Command,
annotationResolver: SubCommandAnnotationResolver?,
overloads: Iterable<CommandSignature>
): String {
return overloads.joinToString("\n") { subcommand ->
buildString {
if (command.prefixOptional) {
append("(")
append(CommandManager.commandPrefix)
append(")")
} else {
append(CommandManager.commandPrefix)
}
//if (command is CompositeCommand) {
append(command.primaryName)
append(" ")
//}
append(subcommand.valueParameters.joinToString(" ") { it.render() })
if (annotationResolver != null && subcommand is CommandSignatureFromKFunction) {
annotationResolver.getDescription(command, subcommand.originFunction)?.let { description ->
append(" # ")
append(description)
}
}
}
}
}
private fun <T> AbstractCommandValueParameter<T>.render(): String {
return when (this) {
is AbstractCommandValueParameter.Extended,
is AbstractCommandValueParameter.UserDefinedType<*>,
-> {
val nameToRender = this.name ?: this.type.classifierAsKClass().simpleName
if (isOptional) "[$nameToRender]" else "<$nameToRender>"
}
is AbstractCommandValueParameter.StringConstant -> {
this.expectingValue
}
}
}
}
fun validate(signatures: List<CommandSignatureFromKFunctionImpl>) {
data class ErasedParameterInfo(
val index: Int,
val name: String?,
val type: KType, // ignore nullability
val additional: String?,
)
data class ErasedVariantInfo(
val receiver: ErasedParameterInfo?,
val valueParameters: List<ErasedParameterInfo>,
)
fun CommandParameter<*>.toErasedParameterInfo(index: Int): ErasedParameterInfo {
return ErasedParameterInfo(
index,
this.name,
this.type.withNullability(false),
if (this is AbstractCommandValueParameter.StringConstant) this.expectingValue else null
)
}
val candidates = signatures.map { variant ->
variant to ErasedVariantInfo(
variant.receiverParameter?.toErasedParameterInfo(0),
variant.valueParameters.mapIndexed { index, parameter -> parameter.toErasedParameterInfo(index) }
)
}
val groups = candidates.groupBy { it.second }
val clashes = groups.entries.find { (_, value) ->
value.size > 1
} ?: return
throw CommandDeclarationClashException(command, clashes.value.map { it.first })
}
@Throws(IllegalCommandDeclarationException::class)
fun findSubCommands(): List<CommandSignatureFromKFunctionImpl> {
return command::class.functions // exclude static later
.asSequence()
.filter { it.isSubCommandFunction() }
.onEach { it.checkExtensionReceiver() }
.onEach { it.checkModifiers() }
.onEach { it.checkNames() }
.flatMap { function ->
val names = annotationResolver.getSubCommandNames(command, function)
if (names.isEmpty()) sequenceOf(createMapEntry(null, function))
else names.associateWith { function }.asSequence()
}
.map { (name, function) ->
val functionNameAsValueParameter =
name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameterForName(index, s) }
.orEmpty()
val valueParameters = function.valueParameters.toMutableList()
var receiverParameter = function.extensionReceiverParameter
if (receiverParameter == null && valueParameters.isNotEmpty()) {
val valueFirstParameter = valueParameters[0]
if (valueFirstParameter.type.classifierAsKClassOrNull()
?.isSubclassOf(CommandSender::class) == true
) {
receiverParameter = valueFirstParameter
valueParameters.removeAt(0)
}
}
val functionValueParameters =
valueParameters.associateBy { it.toUserDefinedCommandParameter() }
CommandSignatureFromKFunctionImpl(
receiverParameter = receiverParameter?.toCommandReceiverParameter(),
valueParameters = functionNameAsValueParameter + functionValueParameters.keys,
originFunction = function
) { call ->
val args = LinkedHashMap<KParameter, Any?>()
for ((commandParameter, value) in call.resolvedValueArguments) {
if (commandParameter is AbstractCommandValueParameter.StringConstant) {
continue
}
val functionParameter =
functionValueParameters[commandParameter]
?: error("Could not find a corresponding function parameter '${commandParameter.name}'")
args[functionParameter] = value
}
val instanceParameter = function.instanceParameter
if (instanceParameter != null) {
check(instanceParameter.type.classifierAsKClass().isInstance(command)) {
"Bad command call resolved. " +
"Function expects instance parameter ${instanceParameter.type} whereas actual instance is ${command::class}."
}
args[instanceParameter] = command
}
if (receiverParameter != null) {
check(receiverParameter.type.classifierAsKClass().isInstance(call.caller)) {
"Bad command call resolved. " +
"Function expects receiver parameter ${receiverParameter.type} whereas actual is ${call.caller::class}."
}
args[receiverParameter] = call.caller
}
// #341
if (function.isSuspend) {
function.callSuspendBy(args)
} else {
runBIO { function.callBy(args) }
}
}
}.toList()
}
private fun <K, V> createMapEntry(key: K, value: V) = object : Map.Entry<K, V> {
override val key: K get() = key
override val value: V get() = value
}
private fun KParameter.toCommandReceiverParameter(): CommandReceiverParameter<out CommandSender> {
check(!this.isVararg) { "Receiver cannot be vararg." }
check(
this.type.classifierAsKClass().isSubclassOf(CommandSender::class)
) { "Receiver must be subclass of CommandSender" }
return CommandReceiverParameter(this.type.isMarkedNullable, this.type)
}
private fun createStringConstantParameterForName(
index: Int,
expectingValue: String
): AbstractCommandValueParameter.StringConstant {
return AbstractCommandValueParameter.StringConstant("#$index", expectingValue, true)
}
private fun KParameter.toUserDefinedCommandParameter(): AbstractCommandValueParameter.UserDefinedType<*> {
return AbstractCommandValueParameter.UserDefinedType<Any?>(
nameForCommandParameter(),
this.isOptional,
this.isVararg,
this.type
) // Any? is erased
}
private fun KParameter.nameForCommandParameter(): String? =
annotationResolver.getAnnotatedName(command, this) ?: this.name
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.data.ReadOnlyPluginConfig
import net.mamoe.mirai.console.data.ValueDescription
import net.mamoe.mirai.console.data.ValueName
import net.mamoe.mirai.console.data.value
@ValueDescription(
"""
内置指令系统配置
"""
)
internal object CommandConfig : ReadOnlyPluginConfig("Command") {
@ValueDescription(
"""
指令前缀, 默认 "/"
"""
)
@ValueName("commandPrefix")
val commandPrefix: String by value("/")
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import kotlin.math.max
import kotlin.math.min
internal infix fun Array<out String>.intersectsIgnoringCase(other: Array<out String>): Boolean {
val max = this.size.coerceAtMost(other.size)
for (i in 0 until max) {
if (this[i].equals(other[i], ignoreCase = true)) return true
}
return false
}
internal fun String.fuzzyMatchWith(target: String): Double {
if (this == target) {
return 1.0
}
var match = 0
for (i in 0..(max(this.lastIndex, target.lastIndex))) {
val t = target.getOrNull(match) ?: break
if (t == this.getOrNull(i)) {
match++
}
}
val longerLength = max(this.length, target.length)
val shorterLength = min(this.length, target.length)
return match.toDouble() / (longerLength + (shorterLength - match))
}
/**
* @return candidates
*/
internal fun Group.fuzzySearchMember(
nameCardTarget: String,
minRate: Double = 0.2, // 参与判断, 用于提示可能的解
matchRate: Double = 0.6,// 最终选择的最少需要的匹配率, 减少歧义
/**
* 如果有多个值超过 [matchRate], 并相互差距小于等于 [disambiguationRate], 则认为有较大歧义风险, 返回可能的解的列表.
*/
disambiguationRate: Double = 0.1,
): List<Pair<Member, Double>> {
val candidates = (this.members + botAsMember)
.asSequence()
.associateWith { it.nameCard.fuzzyMatchWith(nameCardTarget) }
.filter { it.value >= minRate }
.toList()
.sortedByDescending { it.second }
val bestMatches = candidates.filter { it.second >= matchRate }
return when {
bestMatches.isEmpty() -> candidates
bestMatches.size == 1 -> listOf(bestMatches.single().first to 1.0)
else -> {
if (bestMatches.first().second - bestMatches.last().second <= disambiguationRate) {
// resolution ambiguity
candidates
} else {
listOf(bestMatches.first().first to 1.0)
}
}
}
}
internal fun Command.findOrCreateCommandPermission(parent: Permission): Permission {
val id = owner.permissionId("command.$primaryName")
return PermissionService.INSTANCE[id] ?: PermissionService.INSTANCE.register(id, description, parent)
}
//// internal

View File

@ -0,0 +1,213 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.internal.data
import net.mamoe.mirai.console.data.*
// type inference bug
internal fun <T> PluginData.createCompositeSetValueImpl(tToValue: (T) -> Value<T>): CompositeSetValueImpl<T> {
return object : CompositeSetValueImpl<T>(tToValue) {
override fun onChanged() {
this@createCompositeSetValueImpl.onValueChanged(this)
}
}
}
internal abstract class CompositeSetValueImpl<T>(
tToValue: (T) -> Value<T> // should override onChanged
) : CompositeSetValue<T>, AbstractValueImpl<Set<T>>() {
private val internalSet: MutableSet<Value<T>> = mutableSetOf()
private var _value: Set<T> = internalSet.shadowMap({ it.value }, tToValue).observable { onChanged() }
override var value: Set<T>
get() = _value
set(v) {
if (_value != v) {
@Suppress("LocalVariableName")
val _value = _value as MutableSet<T>
_value.clear()
_value.addAll(v)
onChanged()
}
}
override fun setValueBySerializer(value: Set<T>) {
val thisValue = this.value
if (!thisValue.tryPatch(value)) {
this.value = value // deep set
}
}
protected abstract fun onChanged()
override fun toString(): String = _value.toString()
override fun equals(other: Any?): Boolean =
other is CompositeSetValueImpl<*> && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return value.hashCode() * 31 + super.hashCode()
}
}
// type inference bug
internal fun <T> PluginData.createCompositeListValueImpl(tToValue: (T) -> Value<T>): CompositeListValueImpl<T> {
return object : CompositeListValueImpl<T>(tToValue) {
override fun onChanged() {
this@createCompositeListValueImpl.onValueChanged(this)
}
}
}
internal abstract class CompositeListValueImpl<T>(
tToValue: (T) -> Value<T> // should override onChanged
) : CompositeListValue<T>, AbstractValueImpl<List<T>>() {
private val internalList: MutableList<Value<T>> = mutableListOf()
private val _value: List<T> = internalList.shadowMap({ it.value }, tToValue).observable { onChanged() }
override var value: List<T>
get() = _value
set(v) {
if (_value != v) {
@Suppress("LocalVariableName")
val _value = _value as MutableList<T>
_value.clear()
_value.addAll(v)
onChanged()
}
}
override fun setValueBySerializer(value: List<T>) {
val thisValue = this.value
if (!thisValue.tryPatch(value)) {
this.value = value // deep set
}
}
protected abstract fun onChanged()
override fun toString(): String = _value.toString()
override fun equals(other: Any?): Boolean =
other is CompositeListValueImpl<*> && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return value.hashCode() * 31 + super.hashCode()
}
}
// workaround to a type inference bug
internal fun <K, V> PluginData.createCompositeMapValueImpl(
mapInitializer: (() -> MutableMap<Value<K>, Value<V>>?)? = null,
kToValue: (K) -> Value<K>,
vToValue: (V) -> Value<V>,
valueToK: (Value<K>) -> K = Value<K>::value,
valueToV: (Value<V>) -> V = Value<V>::value,
applyToShadowedMap: ((MutableMap<K, V>) -> (MutableMap<K, V>))? = null
): CompositeMapValueImpl<K, V> {
return object :
CompositeMapValueImpl<K, V>(mapInitializer, kToValue, vToValue, valueToK, valueToV, applyToShadowedMap) {
override fun onChanged() = this@createCompositeMapValueImpl.onValueChanged(this)
}
}
// TODO: 2020/6/24 在一个 Value 被删除后停止追踪其更新.
internal abstract class CompositeMapValueImpl<K, V>(
mapInitializer: (() -> MutableMap<Value<K>, Value<V>>?)? = null,
@JvmField internal val kToValue: (K) -> Value<K>, // should override onChanged
@JvmField internal val vToValue: (V) -> Value<V>, // should override onChanged
@JvmField internal val valueToK: (Value<K>) -> K = Value<K>::value,
@JvmField internal val valueToV: (Value<V>) -> V = Value<V>::value,
applyToShadowedMap: ((MutableMap<K, V>) -> (MutableMap<K, V>))? = null
) : CompositeMapValue<K, V>, AbstractValueImpl<Map<K, V>>() {
@JvmField
internal val internalList: MutableMap<Value<K>, Value<V>> = mapInitializer?.invoke() ?: mutableMapOf()
private var _value: MutableMap<K, V> =
internalList.shadowMap(valueToK, kToValue, valueToV, vToValue).let {
applyToShadowedMap?.invoke(it) ?: it
}.observable { onChanged() }
override var value: Map<K, V>
get() = _value
set(v) {
if (_value != v) {
@Suppress("LocalVariableName")
val _value = _value
_value.clear()
_value.putAll(v)
onChanged()
}
}
override fun setValueBySerializer(value: Map<K, V>) {
val thisValue = this.value as MutableMap<K, V>
if (!thisValue.tryPatch(value)) {
this.value = value // deep set
}
}
protected abstract fun onChanged()
override fun toString(): String = _value.toString()
override fun equals(other: Any?): Boolean =
other is CompositeMapValueImpl<*, *> && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return value.hashCode() * 31 + super.hashCode()
}
}
internal fun <K, V> MutableMap<K, V>.patchImpl(_new: Map<K, V>) {
val new = _new.toMutableMap()
val iterator = this.iterator()
for (entry in iterator) {
val newValue = new.remove(entry.key)
if (newValue != null) {
// has replacer
if (entry.value?.tryPatch(newValue) != true) {
// patch not supported, or old value is null
entry.setValue(newValue)
} // else: patched, no remove
} else {
// no replacer
iterator.remove()
}
}
putAll(new)
}
internal fun <C : MutableCollection<E>, E> C.patchImpl(_new: Collection<E>) {
this.clear()
this.addAll(_new)
}
/**
* True if successfully patched
*/
@Suppress("UNCHECKED_CAST")
internal fun Any.tryPatch(any: Any): Boolean = when {
this is MutableCollection<*> && any is Collection<*> -> {
(this as MutableCollection<Any?>).patchImpl(any as Collection<Any?>)
true
}
this is MutableMap<*, *> && any is Map<*, *> -> {
(this as MutableMap<Any?, Any?>).patchImpl(any as Map<Any?, Any?>)
true
}
this is Value<*> && any is Value<*> -> any.value?.let { otherValue -> this.value?.tryPatch(otherValue) } == true
else -> false
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.data
import net.mamoe.mirai.console.data.MemoryPluginDataStorage
import net.mamoe.mirai.console.data.PluginData
import net.mamoe.mirai.console.data.PluginDataHolder
import net.mamoe.mirai.console.data.PluginDataStorage
internal class MemoryPluginDataStorageImpl : PluginDataStorage, MemoryPluginDataStorage,
MutableMap<Class<out PluginData>, PluginData> by mutableMapOf() {
@Suppress("UNCHECKED_CAST")
override fun load(holder: PluginDataHolder, instance: PluginData) {
instance.onInit(holder, this)
}
override fun store(holder: PluginDataHolder, instance: PluginData) {
// no-op
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.data
import kotlinx.serialization.json.Json
import net.mamoe.mirai.console.data.*
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.SilentLogger
import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.warning
import net.mamoe.yamlkt.Yaml
import java.io.File
import java.nio.file.Path
@Suppress("RedundantVisibilityModifier") // might be public in the future
internal open class MultiFilePluginDataStorageImpl(
public final override val directoryPath: Path,
private val logger: MiraiLogger = SilentLogger,
) : PluginDataStorage, MultiFilePluginDataStorage {
init {
directoryPath.mkdir()
}
public override fun load(holder: PluginDataHolder, instance: PluginData) {
instance.onInit(holder, this)
// 0xFEFF is BOM, handle UTF8-BOM
val text = getPluginDataFile(holder, instance).readText().removePrefix("\uFEFF")
if (text.isNotBlank()) {
logger.warning { "Deserializing $text" }
Yaml.decodeFromString(instance.updaterSerializer, text)
} else {
this.store(holder, instance) // save an initial copy
}
logger.debug { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
}
protected open fun getPluginDataFile(holder: PluginDataHolder, instance: PluginData): File {
val name = instance.saveName
val dir = directoryPath.resolve(holder.dataHolderName)
if (dir.isFile) {
error("Target directory $dir for holder $holder is occupied by a file therefore data ${instance::class.qualifiedNameOrTip} can't be saved.")
}
dir.mkdir()
val file = dir.resolve("$name.yml")
if (file.isDirectory) {
error("Target File $file is occupied by a directory therefore data ${instance::class.qualifiedNameOrTip} can't be saved.")
}
logger.debug { "File allocated for ${instance.saveName}: $file" }
return file.toFile().also { it.createNewFile() }
}
private val json = Json {
prettyPrint = true
ignoreUnknownKeys = true
isLenient = true
allowStructuredMapKeys = true
encodeDefaults = true
}
private val yaml = Yaml
@ConsoleExperimentalApi
public override fun store(holder: PluginDataHolder, instance: PluginData) {
getPluginDataFile(holder, instance).writeText(
kotlin.runCatching {
yaml.encodeToString(instance.updaterSerializer, Unit).also {
yaml.decodeAnyFromString(it) // test yaml
}
}.recoverCatching {
logger.warning(
"Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " +
"Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new",
it
)
json.encodeToString(instance.updaterSerializer, Unit)
}.getOrElse {
throw IllegalStateException("Exception while saving $instance, saveName=${instance.saveName}", it)
}
)
logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
}
}
internal fun Path.mkdir(): Boolean = this.toFile().mkdir()
internal val Path.isFile: Boolean get() = this.toFile().isFile
internal val Path.isDirectory: Boolean get() = this.toFile().isDirectory

View File

@ -0,0 +1,143 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS")
package net.mamoe.mirai.console.internal.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import net.mamoe.mirai.console.data.AbstractPluginData
import net.mamoe.mirai.console.data.AbstractPluginData.ValueNode
import net.mamoe.mirai.console.data.PluginData
import net.mamoe.mirai.console.data.ValueDescription
import net.mamoe.mirai.console.data.ValueName
import net.mamoe.yamlkt.Comment
import net.mamoe.yamlkt.YamlNullableDynamicSerializer
import java.lang.reflect.Constructor
import kotlin.reflect.KAnnotatedElement
/**
* Internal implementation for [PluginData] including:
* - Reflection on Kotlin properties and Java fields
* - Auto-saving
*/
internal abstract class PluginDataImpl {
init {
@Suppress("LeakingThis")
check(this is AbstractPluginData)
}
private fun findNodeInstance(name: String): ValueNode<*>? {
check(this is AbstractPluginData)
return valueNodes.firstOrNull { it.valueName == name }
}
internal open val updaterSerializer: KSerializer<Unit> = object : KSerializer<Unit> {
override val descriptor: SerialDescriptor by lazy {
check(this@PluginDataImpl is AbstractPluginData)
kotlinx.serialization.descriptors.buildClassSerialDescriptor((this@PluginDataImpl as PluginData).saveName) {
for (valueNode in valueNodes) valueNode.run {
element(valueName, updaterSerializer.descriptor, annotations = annotations, isOptional = true)
}
}
}
@Suppress("UNCHECKED_CAST")
override fun deserialize(decoder: Decoder) {
val descriptor = descriptor
with(decoder.beginStructure(descriptor)) {
if (decodeSequentially()) {
var index = 0
repeat(decodeCollectionSize(descriptor)) {
val valueName = decodeSerializableElement(descriptor, index++, String.serializer())
val node = findNodeInstance(valueName)
if (node == null) {
decodeSerializableElement(descriptor, index++, YamlNullableDynamicSerializer)
} else {
decodeSerializableElement(descriptor, index++, node.updaterSerializer)
}
}
} else {
outerLoop@ while (true) {
innerLoop@ while (true) {
val index = decodeElementIndex(descriptor)
if (index == CompositeDecoder.DECODE_DONE) {
//check(valueName == null) { "name must be null at this moment." }
break@outerLoop
}
val node = findNodeInstance(descriptor.getElementName(index))
if (node == null) {
decodeSerializableElement(descriptor, index, YamlNullableDynamicSerializer)
} else {
decodeSerializableElement(descriptor, index, node.updaterSerializer)
}
break@innerLoop
}
}
}
endStructure(descriptor)
}
}
@Suppress("UNCHECKED_CAST")
override fun serialize(encoder: Encoder, value: Unit) {
check(this@PluginDataImpl is AbstractPluginData)
val descriptor = descriptor
with(encoder.beginStructure(descriptor)) {
repeat(descriptor.elementsCount) { index ->
encodeSerializableElement(
descriptor,
index,
valueNodes.find { it.valueName == descriptor.getElementName(index) }?.updaterSerializer
?: error("Cannot find a serializer for ${descriptor.getElementName(index)}"),
Unit
)
}
endStructure(descriptor)
}
}
}
}
internal fun KAnnotatedElement.getAnnotationListForValueSerialization(): List<Annotation> {
return this.annotations.mapNotNull {
when (it) {
is SerialName -> error("@SerialName is not supported on Value. Please use @ValueName instead")
is ValueName -> null
is ValueDescription -> COMMENT_CONSTRUCTOR(it.value)
else -> it
}
}
}
private val COMMENT_CONSTRUCTOR = findAnnotationImplementationClassConstructor<Comment>()!!
@Suppress("NOTHING_TO_INLINE")
internal inline operator fun <T : Any?> Constructor<T>.invoke(vararg args: Any?): T = this.newInstance(*args)
internal inline fun <reified T : Any> findAnnotationImplementationClassConstructor(): Constructor<out T>? {
@Suppress("UNCHECKED_CAST")
return T::class.nestedClasses
.find { it.simpleName?.endsWith("Impl") == true }?.java?.run {
constructors.singleOrNull()
} as Constructor<out T>?
}

View File

@ -0,0 +1,216 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.data
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.data.PluginData
import net.mamoe.mirai.console.data.ReferenceValue
import net.mamoe.mirai.console.data.SerializerAwareValue
import kotlin.reflect.KClass
internal object BuiltInSerializerConstants {
//// region BuiltInSerializerConstantsPrimitives CODEGEN ////
@JvmStatic
internal val ByteSerializerDescriptor = Byte.serializer().descriptor
@JvmStatic
internal val ShortSerializerDescriptor = Short.serializer().descriptor
@JvmStatic
internal val IntSerializerDescriptor = Int.serializer().descriptor
@JvmStatic
internal val LongSerializerDescriptor = Long.serializer().descriptor
@JvmStatic
internal val FloatSerializerDescriptor = Float.serializer().descriptor
@JvmStatic
internal val DoubleSerializerDescriptor = Double.serializer().descriptor
@JvmStatic
internal val CharSerializerDescriptor = Char.serializer().descriptor
@JvmStatic
internal val BooleanSerializerDescriptor = Boolean.serializer().descriptor
@JvmStatic
internal val StringSerializerDescriptor = String.serializer().descriptor
//// endregion BuiltInSerializerConstantsPrimitives CODEGEN ////
}
@Suppress("UNCHECKED_CAST")
internal fun <T : Any> PluginData.valueImplPrimitive(kClass: KClass<T>): SerializerAwareValue<T>? {
return when (kClass) {
//// region PluginData_valueImplPrimitive CODEGEN ////
Byte::class -> byteValueImpl()
Short::class -> shortValueImpl()
Int::class -> intValueImpl()
Long::class -> longValueImpl()
Float::class -> floatValueImpl()
Double::class -> doubleValueImpl()
Char::class -> charValueImpl()
Boolean::class -> booleanValueImpl()
String::class -> stringValueImpl()
//// endregion PluginData_valueImplPrimitive CODEGEN ////
else -> error("Internal error: unexpected type passed: ${kClass.qualifiedName}")
} as SerializerAwareValue<T>?
}
//// region PluginData_value_PrimitivesImpl CODEGEN ////
internal fun PluginData.valueImpl(default: Byte): SerializerAwareValue<Byte> {
return object : ByteValueImpl(default) {
override fun onChanged() = this@valueImpl.onValueChanged(this)
}
}
internal fun PluginData.byteValueImpl(): SerializerAwareValue<Byte> {
return object : ByteValueImpl() {
override fun onChanged() = this@byteValueImpl.onValueChanged(this)
}
}
internal fun PluginData.valueImpl(default: Short): SerializerAwareValue<Short> {
return object : ShortValueImpl(default) {
override fun onChanged() = this@valueImpl.onValueChanged(this)
}
}
internal fun PluginData.shortValueImpl(): SerializerAwareValue<Short> {
return object : ShortValueImpl() {
override fun onChanged() = this@shortValueImpl.onValueChanged(this)
}
}
internal fun PluginData.valueImpl(default: Int): SerializerAwareValue<Int> {
return object : IntValueImpl(default) {
override fun onChanged() = this@valueImpl.onValueChanged(this)
}
}
internal fun PluginData.intValueImpl(): SerializerAwareValue<Int> {
return object : IntValueImpl() {
override fun onChanged() = this@intValueImpl.onValueChanged(this)
}
}
internal fun PluginData.valueImpl(default: Long): SerializerAwareValue<Long> {
return object : LongValueImpl(default) {
override fun onChanged() = this@valueImpl.onValueChanged(this)
}
}
internal fun PluginData.longValueImpl(): SerializerAwareValue<Long> {
return object : LongValueImpl() {
override fun onChanged() = this@longValueImpl.onValueChanged(this)
}
}
internal fun PluginData.valueImpl(default: Float): SerializerAwareValue<Float> {
return object : FloatValueImpl(default) {
override fun onChanged() = this@valueImpl.onValueChanged(this)
}
}
internal fun PluginData.floatValueImpl(): SerializerAwareValue<Float> {
return object : FloatValueImpl() {
override fun onChanged() = this@floatValueImpl.onValueChanged(this)
}
}
internal fun PluginData.valueImpl(default: Double): SerializerAwareValue<Double> {
return object : DoubleValueImpl(default) {
override fun onChanged() = this@valueImpl.onValueChanged(this)
}
}
internal fun PluginData.doubleValueImpl(): SerializerAwareValue<Double> {
return object : DoubleValueImpl() {
override fun onChanged() = this@doubleValueImpl.onValueChanged(this)
}
}
internal fun PluginData.valueImpl(default: Char): SerializerAwareValue<Char> {
return object : CharValueImpl(default) {
override fun onChanged() = this@valueImpl.onValueChanged(this)
}
}
internal fun PluginData.charValueImpl(): SerializerAwareValue<Char> {
return object : CharValueImpl() {
override fun onChanged() = this@charValueImpl.onValueChanged(this)
}
}
internal fun PluginData.valueImpl(default: Boolean): SerializerAwareValue<Boolean> {
return object : BooleanValueImpl(default) {
override fun onChanged() = this@valueImpl.onValueChanged(this)
}
}
internal fun PluginData.booleanValueImpl(): SerializerAwareValue<Boolean> {
return object : BooleanValueImpl() {
override fun onChanged() = this@booleanValueImpl.onValueChanged(this)
}
}
internal fun PluginData.valueImpl(default: String): SerializerAwareValue<String> {
return object : StringValueImpl(default) {
override fun onChanged() = this@valueImpl.onValueChanged(this)
}
}
internal fun PluginData.stringValueImpl(): SerializerAwareValue<String> {
return object : StringValueImpl() {
override fun onChanged() = this@stringValueImpl.onValueChanged(this)
}
}
//// endregion PluginData_value_PrimitivesImpl CODEGEN ////
internal class LazyReferenceValueImpl<T> : ReferenceValue<T>, AbstractValueImpl<T>() {
private var initialied: Boolean = false
private var valueField: T? = null
@Suppress("UNCHECKED_CAST")
override var value: T
get() {
check(initialied) { "Internal error: LazyReferenceValueImpl.valueField isn't initialized" }
return valueField as T
}
set(value) {
initialied = true
valueField = value
}
override fun toString(): String {
return valueField.toString()
}
override fun equals(other: Any?): Boolean {
if (other === this) return true
if (other?.javaClass != this.javaClass) return false
other as LazyReferenceValueImpl<*>
if (other.valueField != valueField) return false
return true
}
override fun hashCode(): Int {
return valueField?.hashCode() ?: 0
}
}

View File

@ -0,0 +1,383 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import net.mamoe.mirai.console.data.*
/**
* The super class to all ValueImpl
*/
internal abstract class AbstractValueImpl<T> : Value<T> {
open fun setValueBySerializer(value: T) {
this.value = value
}
}
internal fun <T> Value<T>.setValueBySerializer(value: T) =
(this.castOrInternalError<AbstractValueImpl<T>>()).setValueBySerializer(value)
//// region PrimitiveValuesImpl CODEGEN ////
internal abstract class ByteValueImpl : ByteValue, SerializerAwareValue<Byte>, KSerializer<Unit>,
AbstractValueImpl<Byte> {
constructor()
constructor(default: Byte) {
_value = default
}
private var _value: Byte? = null
final override var value: Byte
get() = _value ?: error("ByteValue.value should be initialized before get.")
set(v) {
if (v != this._value) {
if (this._value == null) {
this._value = v
} else {
this._value = v
onChanged()
}
}
}
protected abstract fun onChanged()
final override val serializer: KSerializer<Unit> get() = this
final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.ByteSerializerDescriptor
final override fun serialize(encoder: Encoder, value: Unit) = Byte.serializer().serialize(encoder, this.value)
final override fun deserialize(decoder: Decoder) = setValueBySerializer(Byte.serializer().deserialize(decoder))
override fun toString(): String = _value?.toString() ?: "ByteValue.value not yet initialized."
override fun equals(other: Any?): Boolean =
other is ByteValueImpl && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return if (value == null) 1
else value.hashCode() * 31
}
}
internal abstract class ShortValueImpl : ShortValue, SerializerAwareValue<Short>, KSerializer<Unit>,
AbstractValueImpl<Short> {
constructor()
constructor(default: Short) {
_value = default
}
private var _value: Short? = null
final override var value: Short
get() = _value ?: error("ShortValue.value should be initialized before get.")
set(v) {
if (v != this._value) {
if (this._value == null) {
this._value = v
} else {
this._value = v
onChanged()
}
}
}
protected abstract fun onChanged()
final override val serializer: KSerializer<Unit> get() = this
final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.ShortSerializerDescriptor
final override fun serialize(encoder: Encoder, value: Unit) = Short.serializer().serialize(encoder, this.value)
final override fun deserialize(decoder: Decoder) = setValueBySerializer(Short.serializer().deserialize(decoder))
override fun toString(): String = _value?.toString() ?: "ShortValue.value not yet initialized."
override fun equals(other: Any?): Boolean =
other is ShortValueImpl && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return if (value == null) 1
else value.hashCode() * 31
}
}
internal abstract class IntValueImpl : IntValue, SerializerAwareValue<Int>, KSerializer<Unit>, AbstractValueImpl<Int> {
constructor()
constructor(default: Int) {
_value = default
}
private var _value: Int? = null
final override var value: Int
get() = _value ?: error("IntValue.value should be initialized before get.")
set(v) {
if (v != this._value) {
if (this._value == null) {
this._value = v
} else {
this._value = v
onChanged()
}
}
}
protected abstract fun onChanged()
final override val serializer: KSerializer<Unit> get() = this
final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.IntSerializerDescriptor
final override fun serialize(encoder: Encoder, value: Unit) = Int.serializer().serialize(encoder, this.value)
final override fun deserialize(decoder: Decoder) = setValueBySerializer(Int.serializer().deserialize(decoder))
override fun toString(): String = _value?.toString() ?: "IntValue.value not yet initialized."
override fun equals(other: Any?): Boolean =
other is IntValueImpl && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return if (value == null) 1
else value.hashCode() * 31
}
}
internal abstract class LongValueImpl : LongValue, SerializerAwareValue<Long>, KSerializer<Unit>,
AbstractValueImpl<Long> {
constructor()
constructor(default: Long) {
_value = default
}
private var _value: Long? = null
final override var value: Long
get() = _value ?: error("LongValue.value should be initialized before get.")
set(v) {
if (v != this._value) {
if (this._value == null) {
this._value = v
} else {
this._value = v
onChanged()
}
}
}
protected abstract fun onChanged()
final override val serializer: KSerializer<Unit> get() = this
final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.LongSerializerDescriptor
final override fun serialize(encoder: Encoder, value: Unit) = Long.serializer().serialize(encoder, this.value)
final override fun deserialize(decoder: Decoder) = setValueBySerializer(Long.serializer().deserialize(decoder))
override fun toString(): String = _value?.toString() ?: "LongValue.value not yet initialized."
override fun equals(other: Any?): Boolean =
other is LongValueImpl && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return if (value == null) 1
else value.hashCode() * 31
}
}
internal abstract class FloatValueImpl : FloatValue, SerializerAwareValue<Float>, KSerializer<Unit>,
AbstractValueImpl<Float> {
constructor()
constructor(default: Float) {
_value = default
}
private var _value: Float? = null
final override var value: Float
get() = _value ?: error("FloatValue.value should be initialized before get.")
set(v) {
if (v != this._value) {
if (this._value == null) {
this._value = v
} else {
this._value = v
onChanged()
}
}
}
protected abstract fun onChanged()
final override val serializer: KSerializer<Unit> get() = this
final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.FloatSerializerDescriptor
final override fun serialize(encoder: Encoder, value: Unit) = Float.serializer().serialize(encoder, this.value)
final override fun deserialize(decoder: Decoder) = setValueBySerializer(Float.serializer().deserialize(decoder))
override fun toString(): String = _value?.toString() ?: "FloatValue.value not yet initialized."
override fun equals(other: Any?): Boolean =
other is FloatValueImpl && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return if (value == null) 1
else value.hashCode() * 31
}
}
internal abstract class DoubleValueImpl : DoubleValue, SerializerAwareValue<Double>, KSerializer<Unit>,
AbstractValueImpl<Double> {
constructor()
constructor(default: Double) {
_value = default
}
private var _value: Double? = null
final override var value: Double
get() = _value ?: error("DoubleValue.value should be initialized before get.")
set(v) {
if (v != this._value) {
if (this._value == null) {
this._value = v
} else {
this._value = v
onChanged()
}
}
}
protected abstract fun onChanged()
final override val serializer: KSerializer<Unit> get() = this
final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.DoubleSerializerDescriptor
final override fun serialize(encoder: Encoder, value: Unit) = Double.serializer().serialize(encoder, this.value)
final override fun deserialize(decoder: Decoder) = setValueBySerializer(Double.serializer().deserialize(decoder))
override fun toString(): String = _value?.toString() ?: "DoubleValue.value not yet initialized."
override fun equals(other: Any?): Boolean =
other is DoubleValueImpl && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return if (value == null) 1
else value.hashCode() * 31
}
}
internal abstract class CharValueImpl : CharValue, SerializerAwareValue<Char>, KSerializer<Unit>,
AbstractValueImpl<Char> {
constructor()
constructor(default: Char) {
_value = default
}
private var _value: Char? = null
final override var value: Char
get() = _value ?: error("CharValue.value should be initialized before get.")
set(v) {
if (v != this._value) {
if (this._value == null) {
this._value = v
} else {
this._value = v
onChanged()
}
}
}
protected abstract fun onChanged()
final override val serializer: KSerializer<Unit> get() = this
final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.CharSerializerDescriptor
final override fun serialize(encoder: Encoder, value: Unit) = Char.serializer().serialize(encoder, this.value)
final override fun deserialize(decoder: Decoder) = setValueBySerializer(Char.serializer().deserialize(decoder))
override fun toString(): String = _value?.toString() ?: "CharValue.value not yet initialized."
override fun equals(other: Any?): Boolean =
other is CharValueImpl && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return if (value == null) 1
else value.hashCode() * 31
}
}
internal abstract class BooleanValueImpl : BooleanValue, SerializerAwareValue<Boolean>, KSerializer<Unit>,
AbstractValueImpl<Boolean> {
constructor()
constructor(default: Boolean) {
_value = default
}
private var _value: Boolean? = null
final override var value: Boolean
get() = _value ?: error("BooleanValue.value should be initialized before get.")
set(v) {
if (v != this._value) {
if (this._value == null) {
this._value = v
} else {
this._value = v
onChanged()
}
}
}
protected abstract fun onChanged()
final override val serializer: KSerializer<Unit> get() = this
final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.BooleanSerializerDescriptor
final override fun serialize(encoder: Encoder, value: Unit) = Boolean.serializer().serialize(encoder, this.value)
final override fun deserialize(decoder: Decoder) = setValueBySerializer(Boolean.serializer().deserialize(decoder))
override fun toString(): String = _value?.toString() ?: "BooleanValue.value not yet initialized."
override fun equals(other: Any?): Boolean =
other is BooleanValueImpl && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return if (value == null) 1
else value.hashCode() * 31
}
}
internal abstract class StringValueImpl : StringValue, SerializerAwareValue<String>, KSerializer<Unit>,
AbstractValueImpl<String> {
constructor()
constructor(default: String) {
_value = default
}
private var _value: String? = null
final override var value: String
get() = _value ?: error("StringValue.value should be initialized before get.")
set(v) {
if (v != this._value) {
if (this._value == null) {
this._value = v
} else {
this._value = v
onChanged()
}
}
}
protected abstract fun onChanged()
final override val serializer: KSerializer<Unit> get() = this
final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.StringSerializerDescriptor
final override fun serialize(encoder: Encoder, value: Unit) = String.serializer().serialize(encoder, this.value)
final override fun deserialize(decoder: Decoder) = setValueBySerializer(String.serializer().deserialize(decoder))
override fun toString(): String = _value ?: "StringValue.value not yet initialized."
override fun equals(other: Any?): Boolean =
other is StringValueImpl && other::class.java == this::class.java && other._value == this._value
override fun hashCode(): Int {
val value = _value
return if (value == null) 1
else value.hashCode() * 31
}
}
//// endregion PrimitiveValuesImpl CODEGEN ////

View File

@ -0,0 +1,100 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPOSED_SUPER_CLASS")
package net.mamoe.mirai.console.internal.data.builtins
import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
import net.mamoe.mirai.console.command.descriptor.InternalCommandValueArgumentParserExtensions
import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.ValueDescription
import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.yamlkt.Comment
import net.mamoe.yamlkt.YamlDynamicSerializer
@ConsoleExperimentalApi
@ValueDescription("自动登录配置")
public object AutoLoginConfig : AutoSavePluginConfig("AutoLogin") {
@Serializable
public data class Account(
@Comment("账号, 现只支持 QQ 数字账号")
val account: String,
val password: Password,
@Comment(
"""
账号配置. 可用配置列表 (注意大小写):
"protocol": "ANDROID_PHONE" / "ANDROID_PAD" / "ANDROID_WATCH"
"device": "device.json"
"enable": true
"""
)
val configuration: Map<ConfigurationKey, @Serializable(with = YamlDynamicSerializer::class) Any> = mapOf(),
) {
@Serializable
public data class Password(
@Comment("密码种类, 可选 PLAIN 或 MD5")
val kind: PasswordKind,
@Comment("密码内容, PLAIN 时为密码文本, MD5 时为 16 进制")
val value: String,
)
@Suppress("EnumEntryName")
@Serializable
public enum class ConfigurationKey {
protocol,
device,
enable,
;
public object Parser : CommandValueArgumentParser<ConfigurationKey>,
InternalCommandValueArgumentParserExtensions<ConfigurationKey>() {
override fun parse(raw: String, sender: CommandSender): ConfigurationKey {
val key = values().find { it.name.equals(raw, ignoreCase = true) }
if (key != null) return key
illegalArgument("未知配置项, 可选值: ${values().joinToString()}")
}
}
}
@Serializable
public enum class PasswordKind {
PLAIN,
MD5;
public object Parser : CommandValueArgumentParser<ConfigurationKey>,
InternalCommandValueArgumentParserExtensions<ConfigurationKey>() {
override fun parse(raw: String, sender: CommandSender): ConfigurationKey {
val key = ConfigurationKey.values().find { it.name.equals(raw, ignoreCase = true) }
if (key != null) return key
illegalArgument("未知配置项, 可选值: ${ConfigurationKey.values().joinToString()}")
}
}
}
}
public val accounts: MutableList<Account> by value(
mutableListOf(
Account(
account = "123456",
password = Account.Password(Account.PasswordKind.PLAIN, "pwd"),
configuration = mapOf(
Account.ConfigurationKey.protocol to "ANDROID_PHONE",
Account.ConfigurationKey.device to "device.json",
Account.ConfigurationKey.enable to true
)
)
)
)
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.data.builtins
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
import net.mamoe.mirai.console.data.PluginConfig
import net.mamoe.mirai.console.data.PluginData
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.utils.minutesToMillis
internal object ConsoleDataScope : CoroutineScope by MiraiConsole.childScope("ConsoleDataScope") {
private val data: List<PluginData> = mutableListOf()
private val configs: MutableList<PluginConfig> = mutableListOf(AutoLoginConfig)
fun addAndReloadConfig(config: PluginConfig) {
configs.add(config)
ConsoleBuiltInPluginConfigStorage.load(ConsoleBuiltInPluginConfigHolder, config)
}
fun reloadAll() {
data.forEach { dt ->
ConsoleBuiltInPluginDataStorage.load(ConsoleBuiltInPluginDataHolder, dt)
}
configs.forEach { config ->
ConsoleBuiltInPluginConfigStorage.load(ConsoleBuiltInPluginConfigHolder, config)
}
}
}
internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder,
CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginDataHolder") {
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
override val dataHolderName: String get() = "Console"
}
internal object ConsoleBuiltInPluginConfigHolder : AutoSavePluginDataHolder,
CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginConfigHolder") {
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
override val dataHolderName: String get() = "Console"
}
internal object ConsoleBuiltInPluginDataStorage :
PluginDataStorage by MiraiConsoleImplementationBridge.dataStorageForBuiltIns
internal object ConsoleBuiltInPluginConfigStorage :
PluginDataStorage by MiraiConsoleImplementationBridge.configStorageForBuiltIns

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.data.builtins
import net.mamoe.mirai.console.data.ReadOnlyPluginConfig
import net.mamoe.mirai.console.data.ValueDescription
import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.logging.AbstractLoggerController
internal object LoggerConfig : ReadOnlyPluginConfig("Logger") {
@ValueDescription(
"""
日志输出等级 可选值: ALL, VERBOSE, DEBUG, INFO, WARNING, ERROR, NONE
"""
)
val defaultPriority by value(AbstractLoggerController.LogPriority.INFO)
@ValueDescription(
"""
特定日志记录器输出等级
"""
)
val loggers: Map<String, AbstractLoggerController.LogPriority> by value(
mapOf(
"example.logger" to AbstractLoggerController.LogPriority.NONE,
"console.debug" to AbstractLoggerController.LogPriority.NONE,
"Bot" to AbstractLoggerController.LogPriority.ALL,
)
)
}

Some files were not shown because too many files have changed in this diff Show More