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:
commit
e1a5426546
mirai-console
.editorconfig
.github/workflows
.gitignore.gitmodulesbackend
codegen
mirai-console
README.mdbuild.gradle.kts
src
MiraiConsole.ktMiraiConsoleFrontEndDescription.ktMiraiConsoleImplementation.kt
command
AbstractCommand.ktBuiltInCommands.ktCommand.ktCommandExecuteResult.ktCommandExecutionException.ktCommandManager.ktCommandOwner.ktCommandPermissionDeniedException.ktCommandSender.ktCompositeCommand.ktIllegalCommandArgumentException.ktRawCommand.ktSimpleCommand.kt
descriptor
CommandArgumentContext.ktCommandArgumentParserBuiltins.ktCommandParameter.ktCommandSignature.ktCommandValueArgumentParser.ktExceptions.ktExperimentalCommandDescriptors.ktTypeVariant.kt
java
parse
resolve
data
AbstractPluginData.ktAutoSavePluginConfig.ktAutoSavePluginData.ktAutoSavePluginDataHolder.ktPluginConfig.ktPluginData.ktPluginDataExtensions.ktPluginDataHolder.ktPluginDataStorage.ktReadOnlyPluginConfig.ktReadOnlyPluginData.ktValue.ktValueDescription.ktValueName.kt
java
events
extension
extensions
BotConfigurationAlterer.ktCommandCallInterceptorProvider.ktCommandCallParserProvider.ktCommandCallResolverProvider.ktPermissionServiceProvider.ktPluginLoaderProvider.ktPostStartupExtension.ktSingletonExtensionSelector.kt
internal
892
mirai-console/.editorconfig
Normal file
892
mirai-console/.editorconfig
Normal 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
|
64
mirai-console/.github/workflows/DevTagPublishing.yml
vendored
Normal file
64
mirai-console/.github/workflows/DevTagPublishing.yml
vendored
Normal 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 }}
|
19
mirai-console/.github/workflows/Gradle CI.yml
vendored
Normal file
19
mirai-console/.github/workflows/Gradle CI.yml
vendored
Normal 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
|
64
mirai-console/.github/workflows/ReleasePublishing.yml
vendored
Normal file
64
mirai-console/.github/workflows/ReleasePublishing.yml
vendored
Normal 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 }}
|
30
mirai-console/.github/workflows/TagRelease.yml
vendored
Normal file
30
mirai-console/.github/workflows/TagRelease.yml
vendored
Normal 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
49
mirai-console/.gitignore
vendored
Normal 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
3
mirai-console/.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "frontend/mirai-android"]
|
||||
path = frontend/mirai-android
|
||||
url = https://github.com/mzdluo123/MiraiAndroid
|
6
mirai-console/backend/codegen/README.md
Normal file
6
mirai-console/backend/codegen/README.md
Normal 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)
|
34
mirai-console/backend/codegen/build.gradle.kts
Normal file
34
mirai-console/backend/codegen/build.gradle.kts
Normal 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`)
|
||||
}
|
130
mirai-console/backend/codegen/src/Codegen.kt
Normal file
130
mirai-console/backend/codegen/src/Codegen.kt
Normal 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()
|
203
mirai-console/backend/codegen/src/MessageScopeCodegen.kt
Normal file
203
mirai-console/backend/codegen/src/MessageScopeCodegen.kt
Normal 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)
|
||||
}
|
||||
}
|
200
mirai-console/backend/codegen/src/ValuePluginDataCodegen.kt
Normal file
200
mirai-console/backend/codegen/src/ValuePluginDataCodegen.kt
Normal 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("---------------------------------------------")
|
||||
}
|
||||
}
|
||||
}
|
92
mirai-console/backend/codegen/src/old/JSettingCodegen.kt
Normal file
92
mirai-console/backend/codegen/src/old/JSettingCodegen.kt
Normal 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())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -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()
|
||||
|
271
mirai-console/backend/codegen/src/old/ValueImplCodegen.kt
Normal file
271
mirai-console/backend/codegen/src/old/ValueImplCodegen.kt
Normal 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"
|
||||
|
271
mirai-console/backend/codegen/src/old/ValuesCodegen.kt
Normal file
271
mirai-console/backend/codegen/src/old/ValuesCodegen.kt
Normal 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>()
|
||||
"""
|
||||
)
|
||||
}
|
128
mirai-console/backend/codegen/src/util.kt
Normal file
128
mirai-console/backend/codegen/src/util.kt
Normal 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" }
|
||||
}
|
3
mirai-console/backend/mirai-console/README.md
Normal file
3
mirai-console/backend/mirai-console/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Mirai Console - Backend
|
||||
|
||||
Mirai Console 后端模块. 发布为 `net.mamoe:mirai-console`.
|
78
mirai-console/backend/mirai-console/build.gradle.kts
Normal file
78
mirai-console/backend/mirai-console/build.gradle.kts
Normal 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")
|
221
mirai-console/backend/mirai-console/src/MiraiConsole.kt
Normal file
221
mirai-console/backend/mirai-console/src/MiraiConsole.kt
Normal 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)
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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) }
|
||||
}
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
124
mirai-console/backend/mirai-console/src/command/Command.kt
Normal file
124
mirai-console/backend/mirai-console/src/command/Command.kt
Normal 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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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')"
|
||||
}
|
||||
|
@ -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)
|
@ -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)
|
||||
}
|
@ -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)"
|
||||
}
|
736
mirai-console/backend/mirai-console/src/command/CommandSender.kt
Normal file
736
mirai-console/backend/mirai-console/src/command/CommandSender.kt
Normal 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>
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
@ -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)
|
@ -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.",
|
||||
)
|
@ -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
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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) {}
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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>())
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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>?
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
||||
}
|
46
mirai-console/backend/mirai-console/src/data/PluginConfig.kt
Normal file
46
mirai-console/backend/mirai-console/src/data/PluginConfig.kt
Normal 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]
|
||||
*/
|
||||
}
|
247
mirai-console/backend/mirai-console/src/data/PluginData.kt
Normal file
247
mirai-console/backend/mirai-console/src/data/PluginData.kt
Normal 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>
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
*/
|
@ -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()
|
@ -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
|
@ -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" }
|
||||
}
|
||||
}
|
325
mirai-console/backend/mirai-console/src/data/Value.kt
Normal file
325
mirai-console/backend/mirai-console/src/data/Value.kt
Normal 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>
|
@ -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,
|
||||
)
|
35
mirai-console/backend/mirai-console/src/data/ValueName.kt
Normal file
35
mirai-console/backend/mirai-console/src/data/ValueName.kt
Normal 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)
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
*/
|
@ -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
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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) })
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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("/")
|
||||
}
|
@ -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
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
@ -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>?
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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 ////
|
@ -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
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
@ -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
|
@ -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
Loading…
Reference in New Issue
Block a user